[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> class-wp-xmlrpc-server.php (source)

   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  #[AllowDynamicProperties]
  24  class wp_xmlrpc_server extends IXR_Server {
  25      /**
  26       * Methods.
  27       *
  28       * @var array
  29       */
  30      public $methods;
  31  
  32      /**
  33       * Blog options.
  34       *
  35       * @var array
  36       */
  37      public $blog_options;
  38  
  39      /**
  40       * IXR_Error instance.
  41       *
  42       * @var IXR_Error
  43       */
  44      public $error;
  45  
  46      /**
  47       * Flags that the user authentication has failed in this instance of wp_xmlrpc_server.
  48       *
  49       * @var bool
  50       */
  51      protected $auth_failed = false;
  52  
  53      /**
  54       * Flags that XML-RPC is enabled
  55       *
  56       * @var bool
  57       */
  58      private $is_enabled;
  59  
  60      /**
  61       * Registers all of the XMLRPC methods that XMLRPC server understands.
  62       *
  63       * Sets up server and method property. Passes XMLRPC methods through the
  64       * {@see 'xmlrpc_methods'} filter to allow plugins to extend or replace
  65       * XML-RPC methods.
  66       *
  67       * @since 1.5.0
  68       */
  69  	public function __construct() {
  70          $this->methods = array(
  71              // WordPress API.
  72              'wp.getUsersBlogs'                 => 'this:wp_getUsersBlogs',
  73              'wp.newPost'                       => 'this:wp_newPost',
  74              'wp.editPost'                      => 'this:wp_editPost',
  75              'wp.deletePost'                    => 'this:wp_deletePost',
  76              'wp.getPost'                       => 'this:wp_getPost',
  77              'wp.getPosts'                      => 'this:wp_getPosts',
  78              'wp.newTerm'                       => 'this:wp_newTerm',
  79              'wp.editTerm'                      => 'this:wp_editTerm',
  80              'wp.deleteTerm'                    => 'this:wp_deleteTerm',
  81              'wp.getTerm'                       => 'this:wp_getTerm',
  82              'wp.getTerms'                      => 'this:wp_getTerms',
  83              'wp.getTaxonomy'                   => 'this:wp_getTaxonomy',
  84              'wp.getTaxonomies'                 => 'this:wp_getTaxonomies',
  85              'wp.getUser'                       => 'this:wp_getUser',
  86              'wp.getUsers'                      => 'this:wp_getUsers',
  87              'wp.getProfile'                    => 'this:wp_getProfile',
  88              'wp.editProfile'                   => 'this:wp_editProfile',
  89              'wp.getPage'                       => 'this:wp_getPage',
  90              'wp.getPages'                      => 'this:wp_getPages',
  91              'wp.newPage'                       => 'this:wp_newPage',
  92              'wp.deletePage'                    => 'this:wp_deletePage',
  93              'wp.editPage'                      => 'this:wp_editPage',
  94              'wp.getPageList'                   => 'this:wp_getPageList',
  95              'wp.getAuthors'                    => 'this:wp_getAuthors',
  96              'wp.getCategories'                 => 'this:mw_getCategories',     // Alias.
  97              'wp.getTags'                       => 'this:wp_getTags',
  98              'wp.newCategory'                   => 'this:wp_newCategory',
  99              'wp.deleteCategory'                => 'this:wp_deleteCategory',
 100              'wp.suggestCategories'             => 'this:wp_suggestCategories',
 101              'wp.uploadFile'                    => 'this:mw_newMediaObject',    // Alias.
 102              'wp.deleteFile'                    => 'this:wp_deletePost',        // Alias.
 103              'wp.getCommentCount'               => 'this:wp_getCommentCount',
 104              'wp.getPostStatusList'             => 'this:wp_getPostStatusList',
 105              'wp.getPageStatusList'             => 'this:wp_getPageStatusList',
 106              'wp.getPageTemplates'              => 'this:wp_getPageTemplates',
 107              'wp.getOptions'                    => 'this:wp_getOptions',
 108              'wp.setOptions'                    => 'this:wp_setOptions',
 109              'wp.getComment'                    => 'this:wp_getComment',
 110              'wp.getComments'                   => 'this:wp_getComments',
 111              'wp.deleteComment'                 => 'this:wp_deleteComment',
 112              'wp.editComment'                   => 'this:wp_editComment',
 113              'wp.newComment'                    => 'this:wp_newComment',
 114              'wp.getCommentStatusList'          => 'this:wp_getCommentStatusList',
 115              'wp.getMediaItem'                  => 'this:wp_getMediaItem',
 116              'wp.getMediaLibrary'               => 'this:wp_getMediaLibrary',
 117              'wp.getPostFormats'                => 'this:wp_getPostFormats',
 118              'wp.getPostType'                   => 'this:wp_getPostType',
 119              'wp.getPostTypes'                  => 'this:wp_getPostTypes',
 120              'wp.getRevisions'                  => 'this:wp_getRevisions',
 121              'wp.restoreRevision'               => 'this:wp_restoreRevision',
 122  
 123              // Blogger API.
 124              'blogger.getUsersBlogs'            => 'this:blogger_getUsersBlogs',
 125              'blogger.getUserInfo'              => 'this:blogger_getUserInfo',
 126              'blogger.getPost'                  => 'this:blogger_getPost',
 127              'blogger.getRecentPosts'           => 'this:blogger_getRecentPosts',
 128              'blogger.newPost'                  => 'this:blogger_newPost',
 129              'blogger.editPost'                 => 'this:blogger_editPost',
 130              'blogger.deletePost'               => 'this:blogger_deletePost',
 131  
 132              // MetaWeblog API (with MT extensions to structs).
 133              'metaWeblog.newPost'               => 'this:mw_newPost',
 134              'metaWeblog.editPost'              => 'this:mw_editPost',
 135              'metaWeblog.getPost'               => 'this:mw_getPost',
 136              'metaWeblog.getRecentPosts'        => 'this:mw_getRecentPosts',
 137              'metaWeblog.getCategories'         => 'this:mw_getCategories',
 138              'metaWeblog.newMediaObject'        => 'this:mw_newMediaObject',
 139  
 140              /*
 141               * MetaWeblog API aliases for Blogger API.
 142               * See http://www.xmlrpc.com/stories/storyReader$2460
 143               */
 144              'metaWeblog.deletePost'            => 'this:blogger_deletePost',
 145              'metaWeblog.getUsersBlogs'         => 'this:blogger_getUsersBlogs',
 146  
 147              // MovableType API.
 148              'mt.getCategoryList'               => 'this:mt_getCategoryList',
 149              'mt.getRecentPostTitles'           => 'this:mt_getRecentPostTitles',
 150              'mt.getPostCategories'             => 'this:mt_getPostCategories',
 151              'mt.setPostCategories'             => 'this:mt_setPostCategories',
 152              'mt.supportedMethods'              => 'this:mt_supportedMethods',
 153              'mt.supportedTextFilters'          => 'this:mt_supportedTextFilters',
 154              'mt.getTrackbackPings'             => 'this:mt_getTrackbackPings',
 155              'mt.publishPost'                   => 'this:mt_publishPost',
 156  
 157              // Pingback.
 158              'pingback.ping'                    => 'this:pingback_ping',
 159              'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks',
 160  
 161              'demo.sayHello'                    => 'this:sayHello',
 162              'demo.addTwoNumbers'               => 'this:addTwoNumbers',
 163          );
 164  
 165          $this->initialise_blog_option_info();
 166  
 167          /**
 168           * Filters the methods exposed by the XML-RPC server.
 169           *
 170           * This filter can be used to add new methods, and remove built-in methods.
 171           *
 172           * @since 1.5.0
 173           *
 174           * @param string[] $methods An array of XML-RPC methods, keyed by their methodName.
 175           */
 176          $this->methods = apply_filters( 'xmlrpc_methods', $this->methods );
 177  
 178          $this->set_is_enabled();
 179      }
 180  
 181      /**
 182       * Sets wp_xmlrpc_server::$is_enabled property.
 183       *
 184       * Determines whether the xmlrpc server is enabled on this WordPress install
 185       * and set the is_enabled property accordingly.
 186       *
 187       * @since 5.7.3
 188       */
 189  	private function set_is_enabled() {
 190          /*
 191           * Respect old get_option() filters left for back-compat when the 'enable_xmlrpc'
 192           * option was deprecated in 3.5.0. Use the {@see 'xmlrpc_enabled'} hook instead.
 193           */
 194          $is_enabled = apply_filters( 'pre_option_enable_xmlrpc', false );
 195          if ( false === $is_enabled ) {
 196              $is_enabled = apply_filters( 'option_enable_xmlrpc', true );
 197          }
 198  
 199          /**
 200           * Filters whether XML-RPC methods requiring authentication are enabled.
 201           *
 202           * Contrary to the way it's named, this filter does not control whether XML-RPC is *fully*
 203           * enabled, rather, it only controls whether XML-RPC methods requiring authentication -
 204           * such as for publishing purposes - are enabled.
 205           *
 206           * Further, the filter does not control whether pingbacks or other custom endpoints that don't
 207           * require authentication are enabled. This behavior is expected, and due to how parity was matched
 208           * with the `enable_xmlrpc` UI option the filter replaced when it was introduced in 3.5.
 209           *
 210           * To disable XML-RPC methods that require authentication, use:
 211           *
 212           *     add_filter( 'xmlrpc_enabled', '__return_false' );
 213           *
 214           * For more granular control over all XML-RPC methods and requests, see the {@see 'xmlrpc_methods'}
 215           * and {@see 'xmlrpc_element_limit'} hooks.
 216           *
 217           * @since 3.5.0
 218           *
 219           * @param bool $is_enabled Whether XML-RPC is enabled. Default true.
 220           */
 221          $this->is_enabled = apply_filters( 'xmlrpc_enabled', $is_enabled );
 222      }
 223  
 224      /**
 225       * Makes private/protected methods readable for backward compatibility.
 226       *
 227       * @since 4.0.0
 228       *
 229       * @param string $name      Method to call.
 230       * @param array  $arguments Arguments to pass when calling.
 231       * @return array|IXR_Error|false Return value of the callback, false otherwise.
 232       */
 233  	public function __call( $name, $arguments ) {
 234          if ( '_multisite_getUsersBlogs' === $name ) {
 235              return $this->_multisite_getUsersBlogs( ...$arguments );
 236          }
 237          return false;
 238      }
 239  
 240      /**
 241       * Serves the XML-RPC request.
 242       *
 243       * @since 2.9.0
 244       */
 245  	public function serve_request() {
 246          $this->IXR_Server( $this->methods );
 247      }
 248  
 249      /**
 250       * Tests XMLRPC API by saying, "Hello!" to client.
 251       *
 252       * @since 1.5.0
 253       *
 254       * @return string Hello string response.
 255       */
 256  	public function sayHello() {
 257          return 'Hello!';
 258      }
 259  
 260      /**
 261       * Tests XMLRPC API by adding two numbers for client.
 262       *
 263       * @since 1.5.0
 264       *
 265       * @param array $args {
 266       *     Method arguments. Note: arguments must be ordered as documented.
 267       *
 268       *     @type int $0 A number to add.
 269       *     @type int $1 A second number to add.
 270       * }
 271       * @return int Sum of the two given numbers.
 272       */
 273  	public function addTwoNumbers( $args ) {
 274          $number1 = $args[0];
 275          $number2 = $args[1];
 276          return $number1 + $number2;
 277      }
 278  
 279      /**
 280       * Logs user in.
 281       *
 282       * @since 2.8.0
 283       *
 284       * @param string $username User's username.
 285       * @param string $password User's password.
 286       * @return WP_User|false WP_User object if authentication passed, false otherwise.
 287       */
 288  	public function login( $username, $password ) {
 289          if ( ! $this->is_enabled ) {
 290              $this->error = new IXR_Error( 405, sprintf( __( 'XML-RPC services are disabled on this site.' ) ) );
 291              return false;
 292          }
 293  
 294          if ( $this->auth_failed ) {
 295              $user = new WP_Error( 'login_prevented' );
 296          } else {
 297              $user = wp_authenticate( $username, $password );
 298          }
 299  
 300          if ( is_wp_error( $user ) ) {
 301              $this->error = new IXR_Error( 403, __( 'Incorrect username or password.' ) );
 302  
 303              // Flag that authentication has failed once on this wp_xmlrpc_server instance.
 304              $this->auth_failed = true;
 305  
 306              /**
 307               * Filters the XML-RPC user login error message.
 308               *
 309               * @since 3.5.0
 310               *
 311               * @param IXR_Error $error The XML-RPC error message.
 312               * @param WP_Error  $user  WP_Error object.
 313               */
 314              $this->error = apply_filters( 'xmlrpc_login_error', $this->error, $user );
 315              return false;
 316          }
 317  
 318          wp_set_current_user( $user->ID );
 319          return $user;
 320      }
 321  
 322      /**
 323       * Checks user's credentials. Deprecated.
 324       *
 325       * @since 1.5.0
 326       * @deprecated 2.8.0 Use wp_xmlrpc_server::login()
 327       * @see wp_xmlrpc_server::login()
 328       *
 329       * @param string $username User's username.
 330       * @param string $password User's password.
 331       * @return bool Whether authentication passed.
 332       */
 333  	public function login_pass_ok( $username, $password ) {
 334          return (bool) $this->login( $username, $password );
 335      }
 336  
 337      /**
 338       * Escapes string or array of strings for database.
 339       *
 340       * @since 1.5.2
 341       *
 342       * @param string|array $data Escape single string or array of strings.
 343       * @return string|void Returns with string is passed, alters by-reference
 344       *                     when array is passed.
 345       */
 346  	public function escape( &$data ) {
 347          if ( ! is_array( $data ) ) {
 348              return wp_slash( $data );
 349          }
 350  
 351          foreach ( $data as &$v ) {
 352              if ( is_array( $v ) ) {
 353                  $this->escape( $v );
 354              } elseif ( ! is_object( $v ) ) {
 355                  $v = wp_slash( $v );
 356              }
 357          }
 358      }
 359  
 360      /**
 361       * Sends error response to client.
 362       *
 363       * Sends an XML error response to the client. If the endpoint is enabled
 364       * an HTTP 200 response is always sent per the XML-RPC specification.
 365       *
 366       * @since 5.7.3
 367       *
 368       * @param IXR_Error|string $error   Error code or an error object.
 369       * @param false            $message Error message. Optional.
 370       */
 371  	public function error( $error, $message = false ) {
 372          // Accepts either an error object or an error code and message
 373          if ( $message && ! is_object( $error ) ) {
 374              $error = new IXR_Error( $error, $message );
 375          }
 376  
 377          if ( ! $this->is_enabled ) {
 378              status_header( $error->code );
 379          }
 380  
 381          $this->output( $error->getXml() );
 382      }
 383  
 384      /**
 385       * Retrieves custom fields for post.
 386       *
 387       * @since 2.5.0
 388       *
 389       * @param int $post_id Post ID.
 390       * @return array Custom fields, if exist.
 391       */
 392  	public function get_custom_fields( $post_id ) {
 393          $post_id = (int) $post_id;
 394  
 395          $custom_fields = array();
 396  
 397          foreach ( (array) has_meta( $post_id ) as $meta ) {
 398              // Don't expose protected fields.
 399              if ( ! current_user_can( 'edit_post_meta', $post_id, $meta['meta_key'] ) ) {
 400                  continue;
 401              }
 402  
 403              $custom_fields[] = array(
 404                  'id'    => $meta['meta_id'],
 405                  'key'   => $meta['meta_key'],
 406                  'value' => $meta['meta_value'],
 407              );
 408          }
 409  
 410          return $custom_fields;
 411      }
 412  
 413      /**
 414       * Sets custom fields for post.
 415       *
 416       * @since 2.5.0
 417       *
 418       * @param int   $post_id Post ID.
 419       * @param array $fields  Custom fields.
 420       */
 421  	public function set_custom_fields( $post_id, $fields ) {
 422          $post_id = (int) $post_id;
 423  
 424          foreach ( (array) $fields as $meta ) {
 425              if ( isset( $meta['id'] ) ) {
 426                  $meta['id'] = (int) $meta['id'];
 427                  $pmeta      = get_metadata_by_mid( 'post', $meta['id'] );
 428  
 429                  if ( ! $pmeta || $pmeta->post_id != $post_id ) {
 430                      continue;
 431                  }
 432  
 433                  if ( isset( $meta['key'] ) ) {
 434                      $meta['key'] = wp_unslash( $meta['key'] );
 435                      if ( $meta['key'] !== $pmeta->meta_key ) {
 436                          continue;
 437                      }
 438                      $meta['value'] = wp_unslash( $meta['value'] );
 439                      if ( current_user_can( 'edit_post_meta', $post_id, $meta['key'] ) ) {
 440                          update_metadata_by_mid( 'post', $meta['id'], $meta['value'] );
 441                      }
 442                  } elseif ( current_user_can( 'delete_post_meta', $post_id, $pmeta->meta_key ) ) {
 443                      delete_metadata_by_mid( 'post', $meta['id'] );
 444                  }
 445              } elseif ( current_user_can( 'add_post_meta', $post_id, wp_unslash( $meta['key'] ) ) ) {
 446                  add_post_meta( $post_id, $meta['key'], $meta['value'] );
 447              }
 448          }
 449      }
 450  
 451      /**
 452       * Retrieves custom fields for a term.
 453       *
 454       * @since 4.9.0
 455       *
 456       * @param int $term_id Term ID.
 457       * @return array Array of custom fields, if they exist.
 458       */
 459  	public function get_term_custom_fields( $term_id ) {
 460          $term_id = (int) $term_id;
 461  
 462          $custom_fields = array();
 463  
 464          foreach ( (array) has_term_meta( $term_id ) as $meta ) {
 465  
 466              if ( ! current_user_can( 'edit_term_meta', $term_id ) ) {
 467                  continue;
 468              }
 469  
 470              $custom_fields[] = array(
 471                  'id'    => $meta['meta_id'],
 472                  'key'   => $meta['meta_key'],
 473                  'value' => $meta['meta_value'],
 474              );
 475          }
 476  
 477          return $custom_fields;
 478      }
 479  
 480      /**
 481       * Sets custom fields for a term.
 482       *
 483       * @since 4.9.0
 484       *
 485       * @param int   $term_id Term ID.
 486       * @param array $fields  Custom fields.
 487       */
 488  	public function set_term_custom_fields( $term_id, $fields ) {
 489          $term_id = (int) $term_id;
 490  
 491          foreach ( (array) $fields as $meta ) {
 492              if ( isset( $meta['id'] ) ) {
 493                  $meta['id'] = (int) $meta['id'];
 494                  $pmeta      = get_metadata_by_mid( 'term', $meta['id'] );
 495                  if ( isset( $meta['key'] ) ) {
 496                      $meta['key'] = wp_unslash( $meta['key'] );
 497                      if ( $meta['key'] !== $pmeta->meta_key ) {
 498                          continue;
 499                      }
 500                      $meta['value'] = wp_unslash( $meta['value'] );
 501                      if ( current_user_can( 'edit_term_meta', $term_id ) ) {
 502                          update_metadata_by_mid( 'term', $meta['id'], $meta['value'] );
 503                      }
 504                  } elseif ( current_user_can( 'delete_term_meta', $term_id ) ) {
 505                      delete_metadata_by_mid( 'term', $meta['id'] );
 506                  }
 507              } elseif ( current_user_can( 'add_term_meta', $term_id ) ) {
 508                  add_term_meta( $term_id, $meta['key'], $meta['value'] );
 509              }
 510          }
 511      }
 512  
 513      /**
 514       * Sets up blog options property.
 515       *
 516       * Passes property through {@see 'xmlrpc_blog_options'} filter.
 517       *
 518       * @since 2.6.0
 519       */
 520  	public function initialise_blog_option_info() {
 521          $this->blog_options = array(
 522              // Read-only options.
 523              'software_name'           => array(
 524                  'desc'     => __( 'Software Name' ),
 525                  'readonly' => true,
 526                  'value'    => 'WordPress',
 527              ),
 528              'software_version'        => array(
 529                  'desc'     => __( 'Software Version' ),
 530                  'readonly' => true,
 531                  'value'    => get_bloginfo( 'version' ),
 532              ),
 533              'blog_url'                => array(
 534                  'desc'     => __( 'WordPress Address (URL)' ),
 535                  'readonly' => true,
 536                  'option'   => 'siteurl',
 537              ),
 538              'home_url'                => array(
 539                  'desc'     => __( 'Site Address (URL)' ),
 540                  'readonly' => true,
 541                  'option'   => 'home',
 542              ),
 543              'login_url'               => array(
 544                  'desc'     => __( 'Login Address (URL)' ),
 545                  'readonly' => true,
 546                  'value'    => wp_login_url(),
 547              ),
 548              'admin_url'               => array(
 549                  'desc'     => __( 'The URL to the admin area' ),
 550                  'readonly' => true,
 551                  'value'    => get_admin_url(),
 552              ),
 553              'image_default_link_type' => array(
 554                  'desc'     => __( 'Image default link type' ),
 555                  'readonly' => true,
 556                  'option'   => 'image_default_link_type',
 557              ),
 558              'image_default_size'      => array(
 559                  'desc'     => __( 'Image default size' ),
 560                  'readonly' => true,
 561                  'option'   => 'image_default_size',
 562              ),
 563              'image_default_align'     => array(
 564                  'desc'     => __( 'Image default align' ),
 565                  'readonly' => true,
 566                  'option'   => 'image_default_align',
 567              ),
 568              'template'                => array(
 569                  'desc'     => __( 'Template' ),
 570                  'readonly' => true,
 571                  'option'   => 'template',
 572              ),
 573              'stylesheet'              => array(
 574                  'desc'     => __( 'Stylesheet' ),
 575                  'readonly' => true,
 576                  'option'   => 'stylesheet',
 577              ),
 578              'post_thumbnail'          => array(
 579                  'desc'     => __( 'Post Thumbnail' ),
 580                  'readonly' => true,
 581                  'value'    => current_theme_supports( 'post-thumbnails' ),
 582              ),
 583  
 584              // Updatable options.
 585              'time_zone'               => array(
 586                  'desc'     => __( 'Time Zone' ),
 587                  'readonly' => false,
 588                  'option'   => 'gmt_offset',
 589              ),
 590              'blog_title'              => array(
 591                  'desc'     => __( 'Site Title' ),
 592                  'readonly' => false,
 593                  'option'   => 'blogname',
 594              ),
 595              'blog_tagline'            => array(
 596                  'desc'     => __( 'Site Tagline' ),
 597                  'readonly' => false,
 598                  'option'   => 'blogdescription',
 599              ),
 600              'date_format'             => array(
 601                  'desc'     => __( 'Date Format' ),
 602                  'readonly' => false,
 603                  'option'   => 'date_format',
 604              ),
 605              'time_format'             => array(
 606                  'desc'     => __( 'Time Format' ),
 607                  'readonly' => false,
 608                  'option'   => 'time_format',
 609              ),
 610              'users_can_register'      => array(
 611                  'desc'     => __( 'Allow new users to sign up' ),
 612                  'readonly' => false,
 613                  'option'   => 'users_can_register',
 614              ),
 615              'thumbnail_size_w'        => array(
 616                  'desc'     => __( 'Thumbnail Width' ),
 617                  'readonly' => false,
 618                  'option'   => 'thumbnail_size_w',
 619              ),
 620              'thumbnail_size_h'        => array(
 621                  'desc'     => __( 'Thumbnail Height' ),
 622                  'readonly' => false,
 623                  'option'   => 'thumbnail_size_h',
 624              ),
 625              'thumbnail_crop'          => array(
 626                  'desc'     => __( 'Crop thumbnail to exact dimensions' ),
 627                  'readonly' => false,
 628                  'option'   => 'thumbnail_crop',
 629              ),
 630              'medium_size_w'           => array(
 631                  'desc'     => __( 'Medium size image width' ),
 632                  'readonly' => false,
 633                  'option'   => 'medium_size_w',
 634              ),
 635              'medium_size_h'           => array(
 636                  'desc'     => __( 'Medium size image height' ),
 637                  'readonly' => false,
 638                  'option'   => 'medium_size_h',
 639              ),
 640              'medium_large_size_w'     => array(
 641                  'desc'     => __( 'Medium-Large size image width' ),
 642                  'readonly' => false,
 643                  'option'   => 'medium_large_size_w',
 644              ),
 645              'medium_large_size_h'     => array(
 646                  'desc'     => __( 'Medium-Large size image height' ),
 647                  'readonly' => false,
 648                  'option'   => 'medium_large_size_h',
 649              ),
 650              'large_size_w'            => array(
 651                  'desc'     => __( 'Large size image width' ),
 652                  'readonly' => false,
 653                  'option'   => 'large_size_w',
 654              ),
 655              'large_size_h'            => array(
 656                  'desc'     => __( 'Large size image height' ),
 657                  'readonly' => false,
 658                  'option'   => 'large_size_h',
 659              ),
 660              'default_comment_status'  => array(
 661                  'desc'     => __( 'Allow people to submit comments on new posts.' ),
 662                  'readonly' => false,
 663                  'option'   => 'default_comment_status',
 664              ),
 665              'default_ping_status'     => array(
 666                  'desc'     => __( 'Allow link notifications from other blogs (pingbacks and trackbacks) on new posts.' ),
 667                  'readonly' => false,
 668                  'option'   => 'default_ping_status',
 669              ),
 670          );
 671  
 672          /**
 673           * Filters the XML-RPC blog options property.
 674           *
 675           * @since 2.6.0
 676           *
 677           * @param array $blog_options An array of XML-RPC blog options.
 678           */
 679          $this->blog_options = apply_filters( 'xmlrpc_blog_options', $this->blog_options );
 680      }
 681  
 682      /**
 683       * Retrieves the blogs of the user.
 684       *
 685       * @since 2.6.0
 686       *
 687       * @param array $args {
 688       *     Method arguments. Note: arguments must be ordered as documented.
 689       *
 690       *     @type string $0 Username.
 691       *     @type string $1 Password.
 692       * }
 693       * @return array|IXR_Error Array contains:
 694       *  - 'isAdmin'
 695       *  - 'isPrimary' - whether the blog is the user's primary blog
 696       *  - 'url'
 697       *  - 'blogid'
 698       *  - 'blogName'
 699       *  - 'xmlrpc' - url of xmlrpc endpoint
 700       */
 701  	public function wp_getUsersBlogs( $args ) {
 702          if ( ! $this->minimum_args( $args, 2 ) ) {
 703              return $this->error;
 704          }
 705  
 706          // If this isn't on WPMU then just use blogger_getUsersBlogs().
 707          if ( ! is_multisite() ) {
 708              array_unshift( $args, 1 );
 709              return $this->blogger_getUsersBlogs( $args );
 710          }
 711  
 712          $this->escape( $args );
 713  
 714          $username = $args[0];
 715          $password = $args[1];
 716  
 717          $user = $this->login( $username, $password );
 718          if ( ! $user ) {
 719              return $this->error;
 720          }
 721  
 722          /**
 723           * Fires after the XML-RPC user has been authenticated but before the rest of
 724           * the method logic begins.
 725           *
 726           * All built-in XML-RPC methods use the action xmlrpc_call, with a parameter
 727           * equal to the method's name, e.g., wp.getUsersBlogs, wp.newPost, etc.
 728           *
 729           * @since 2.5.0
 730           * @since 5.7.0 Added the `$args` and `$server` parameters.
 731           *
 732           * @param string           $name   The method name.
 733           * @param array|string     $args   The escaped arguments passed to the method.
 734           * @param wp_xmlrpc_server $server The XML-RPC server instance.
 735           */
 736          do_action( 'xmlrpc_call', 'wp.getUsersBlogs', $args, $this );
 737  
 738          $blogs           = (array) get_blogs_of_user( $user->ID );
 739          $struct          = array();
 740          $primary_blog_id = 0;
 741          $active_blog     = get_active_blog_for_user( $user->ID );
 742          if ( $active_blog ) {
 743              $primary_blog_id = (int) $active_blog->blog_id;
 744          }
 745  
 746          foreach ( $blogs as $blog ) {
 747              // Don't include blogs that aren't hosted at this site.
 748              if ( get_current_network_id() != $blog->site_id ) {
 749                  continue;
 750              }
 751  
 752              $blog_id = $blog->userblog_id;
 753  
 754              switch_to_blog( $blog_id );
 755  
 756              $is_admin   = current_user_can( 'manage_options' );
 757              $is_primary = ( (int) $blog_id === $primary_blog_id );
 758  
 759              $struct[] = array(
 760                  'isAdmin'   => $is_admin,
 761                  'isPrimary' => $is_primary,
 762                  'url'       => home_url( '/' ),
 763                  'blogid'    => (string) $blog_id,
 764                  'blogName'  => get_option( 'blogname' ),
 765                  'xmlrpc'    => site_url( 'xmlrpc.php', 'rpc' ),
 766              );
 767  
 768              restore_current_blog();
 769          }
 770  
 771          return $struct;
 772      }
 773  
 774      /**
 775       * Checks if the method received at least the minimum number of arguments.
 776       *
 777       * @since 3.4.0
 778       *
 779       * @param array $args  An array of arguments to check.
 780       * @param int   $count Minimum number of arguments.
 781       * @return bool True if `$args` contains at least `$count` arguments, false otherwise.
 782       */
 783  	protected function minimum_args( $args, $count ) {
 784          if ( ! is_array( $args ) || count( $args ) < $count ) {
 785              $this->error = new IXR_Error( 400, __( 'Insufficient arguments passed to this XML-RPC method.' ) );
 786              return false;
 787          }
 788  
 789          return true;
 790      }
 791  
 792      /**
 793       * Prepares taxonomy data for return in an XML-RPC object.
 794       *
 795       * @param WP_Taxonomy $taxonomy The unprepared taxonomy data.
 796       * @param array       $fields   The subset of taxonomy fields to return.
 797       * @return array The prepared taxonomy data.
 798       */
 799  	protected function _prepare_taxonomy( $taxonomy, $fields ) {
 800          $_taxonomy = array(
 801              'name'         => $taxonomy->name,
 802              'label'        => $taxonomy->label,
 803              'hierarchical' => (bool) $taxonomy->hierarchical,
 804              'public'       => (bool) $taxonomy->public,
 805              'show_ui'      => (bool) $taxonomy->show_ui,
 806              '_builtin'     => (bool) $taxonomy->_builtin,
 807          );
 808  
 809          if ( in_array( 'labels', $fields, true ) ) {
 810              $_taxonomy['labels'] = (array) $taxonomy->labels;
 811          }
 812  
 813          if ( in_array( 'cap', $fields, true ) ) {
 814              $_taxonomy['cap'] = (array) $taxonomy->cap;
 815          }
 816  
 817          if ( in_array( 'menu', $fields, true ) ) {
 818              $_taxonomy['show_in_menu'] = (bool) $taxonomy->show_in_menu;
 819          }
 820  
 821          if ( in_array( 'object_type', $fields, true ) ) {
 822              $_taxonomy['object_type'] = array_unique( (array) $taxonomy->object_type );
 823          }
 824  
 825          /**
 826           * Filters XML-RPC-prepared data for the given taxonomy.
 827           *
 828           * @since 3.4.0
 829           *
 830           * @param array       $_taxonomy An array of taxonomy data.
 831           * @param WP_Taxonomy $taxonomy  Taxonomy object.
 832           * @param array       $fields    The subset of taxonomy fields to return.
 833           */
 834          return apply_filters( 'xmlrpc_prepare_taxonomy', $_taxonomy, $taxonomy, $fields );
 835      }
 836  
 837      /**
 838       * Prepares term data for return in an XML-RPC object.
 839       *
 840       * @param array|object $term The unprepared term data.
 841       * @return array The prepared term data.
 842       */
 843  	protected function _prepare_term( $term ) {
 844          $_term = $term;
 845          if ( ! is_array( $_term ) ) {
 846              $_term = get_object_vars( $_term );
 847          }
 848  
 849          // For integers which may be larger than XML-RPC supports ensure we return strings.
 850          $_term['term_id']          = (string) $_term['term_id'];
 851          $_term['term_group']       = (string) $_term['term_group'];
 852          $_term['term_taxonomy_id'] = (string) $_term['term_taxonomy_id'];
 853          $_term['parent']           = (string) $_term['parent'];
 854  
 855          // Count we are happy to return as an integer because people really shouldn't use terms that much.
 856          $_term['count'] = (int) $_term['count'];
 857  
 858          // Get term meta.
 859          $_term['custom_fields'] = $this->get_term_custom_fields( $_term['term_id'] );
 860  
 861          /**
 862           * Filters XML-RPC-prepared data for the given term.
 863           *
 864           * @since 3.4.0
 865           *
 866           * @param array        $_term An array of term data.
 867           * @param array|object $term  Term object or array.
 868           */
 869          return apply_filters( 'xmlrpc_prepare_term', $_term, $term );
 870      }
 871  
 872      /**
 873       * Converts a WordPress date string to an IXR_Date object.
 874       *
 875       * @param string $date Date string to convert.
 876       * @return IXR_Date IXR_Date object.
 877       */
 878  	protected function _convert_date( $date ) {
 879          if ( '0000-00-00 00:00:00' === $date ) {
 880              return new IXR_Date( '00000000T00:00:00Z' );
 881          }
 882          return new IXR_Date( mysql2date( 'Ymd\TH:i:s', $date, false ) );
 883      }
 884  
 885      /**
 886       * Converts a WordPress GMT date string to an IXR_Date object.
 887       *
 888       * @param string $date_gmt WordPress GMT date string.
 889       * @param string $date     Date string.
 890       * @return IXR_Date IXR_Date object.
 891       */
 892  	protected function _convert_date_gmt( $date_gmt, $date ) {
 893          if ( '0000-00-00 00:00:00' !== $date && '0000-00-00 00:00:00' === $date_gmt ) {
 894              return new IXR_Date( get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $date, false ), 'Ymd\TH:i:s' ) );
 895          }
 896          return $this->_convert_date( $date_gmt );
 897      }
 898  
 899      /**
 900       * Prepares post data for return in an XML-RPC object.
 901       *
 902       * @param array $post   The unprepared post data.
 903       * @param array $fields The subset of post type fields to return.
 904       * @return array The prepared post data.
 905       */
 906  	protected function _prepare_post( $post, $fields ) {
 907          // Holds the data for this post. built up based on $fields.
 908          $_post = array( 'post_id' => (string) $post['ID'] );
 909  
 910          // Prepare common post fields.
 911          $post_fields = array(
 912              'post_title'        => $post['post_title'],
 913              'post_date'         => $this->_convert_date( $post['post_date'] ),
 914              'post_date_gmt'     => $this->_convert_date_gmt( $post['post_date_gmt'], $post['post_date'] ),
 915              'post_modified'     => $this->_convert_date( $post['post_modified'] ),
 916              'post_modified_gmt' => $this->_convert_date_gmt( $post['post_modified_gmt'], $post['post_modified'] ),
 917              'post_status'       => $post['post_status'],
 918              'post_type'         => $post['post_type'],
 919              'post_name'         => $post['post_name'],
 920              'post_author'       => $post['post_author'],
 921              'post_password'     => $post['post_password'],
 922              'post_excerpt'      => $post['post_excerpt'],
 923              'post_content'      => $post['post_content'],
 924              'post_parent'       => (string) $post['post_parent'],
 925              'post_mime_type'    => $post['post_mime_type'],
 926              'link'              => get_permalink( $post['ID'] ),
 927              'guid'              => $post['guid'],
 928              'menu_order'        => (int) $post['menu_order'],
 929              'comment_status'    => $post['comment_status'],
 930              'ping_status'       => $post['ping_status'],
 931              'sticky'            => ( 'post' === $post['post_type'] && is_sticky( $post['ID'] ) ),
 932          );
 933  
 934          // Thumbnail.
 935          $post_fields['post_thumbnail'] = array();
 936          $thumbnail_id                  = get_post_thumbnail_id( $post['ID'] );
 937          if ( $thumbnail_id ) {
 938              $thumbnail_size                = current_theme_supports( 'post-thumbnail' ) ? 'post-thumbnail' : 'thumbnail';
 939              $post_fields['post_thumbnail'] = $this->_prepare_media_item( get_post( $thumbnail_id ), $thumbnail_size );
 940          }
 941  
 942          // Consider future posts as published.
 943          if ( 'future' === $post_fields['post_status'] ) {
 944              $post_fields['post_status'] = 'publish';
 945          }
 946  
 947          // Fill in blank post format.
 948          $post_fields['post_format'] = get_post_format( $post['ID'] );
 949          if ( empty( $post_fields['post_format'] ) ) {
 950              $post_fields['post_format'] = 'standard';
 951          }
 952  
 953          // Merge requested $post_fields fields into $_post.
 954          if ( in_array( 'post', $fields, true ) ) {
 955              $_post = array_merge( $_post, $post_fields );
 956          } else {
 957              $requested_fields = array_intersect_key( $post_fields, array_flip( $fields ) );
 958              $_post            = array_merge( $_post, $requested_fields );
 959          }
 960  
 961          $all_taxonomy_fields = in_array( 'taxonomies', $fields, true );
 962  
 963          if ( $all_taxonomy_fields || in_array( 'terms', $fields, true ) ) {
 964              $post_type_taxonomies = get_object_taxonomies( $post['post_type'], 'names' );
 965              $terms                = wp_get_object_terms( $post['ID'], $post_type_taxonomies );
 966              $_post['terms']       = array();
 967              foreach ( $terms as $term ) {
 968                  $_post['terms'][] = $this->_prepare_term( $term );
 969              }
 970          }
 971  
 972          if ( in_array( 'custom_fields', $fields, true ) ) {
 973              $_post['custom_fields'] = $this->get_custom_fields( $post['ID'] );
 974          }
 975  
 976          if ( in_array( 'enclosure', $fields, true ) ) {
 977              $_post['enclosure'] = array();
 978              $enclosures         = (array) get_post_meta( $post['ID'], 'enclosure' );
 979              if ( ! empty( $enclosures ) ) {
 980                  $encdata                      = explode( "\n", $enclosures[0] );
 981                  $_post['enclosure']['url']    = trim( htmlspecialchars( $encdata[0] ) );
 982                  $_post['enclosure']['length'] = (int) trim( $encdata[1] );
 983                  $_post['enclosure']['type']   = trim( $encdata[2] );
 984              }
 985          }
 986  
 987          /**
 988           * Filters XML-RPC-prepared date for the given post.
 989           *
 990           * @since 3.4.0
 991           *
 992           * @param array $_post  An array of modified post data.
 993           * @param array $post   An array of post data.
 994           * @param array $fields An array of post fields.
 995           */
 996          return apply_filters( 'xmlrpc_prepare_post', $_post, $post, $fields );
 997      }
 998  
 999      /**
1000       * Prepares post data for return in an XML-RPC object.
1001       *
1002       * @since 3.4.0
1003       * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
1004       *
1005       * @param WP_Post_Type $post_type Post type object.
1006       * @param array        $fields    The subset of post fields to return.
1007       * @return array The prepared post type data.
1008       */
1009  	protected function _prepare_post_type( $post_type, $fields ) {
1010          $_post_type = array(
1011              'name'         => $post_type->name,
1012              'label'        => $post_type->label,
1013              'hierarchical' => (bool) $post_type->hierarchical,
1014              'public'       => (bool) $post_type->public,
1015              'show_ui'      => (bool) $post_type->show_ui,
1016              '_builtin'     => (bool) $post_type->_builtin,
1017              'has_archive'  => (bool) $post_type->has_archive,
1018              'supports'     => get_all_post_type_supports( $post_type->name ),
1019          );
1020  
1021          if ( in_array( 'labels', $fields, true ) ) {
1022              $_post_type['labels'] = (array) $post_type->labels;
1023          }
1024  
1025          if ( in_array( 'cap', $fields, true ) ) {
1026              $_post_type['cap']          = (array) $post_type->cap;
1027              $_post_type['map_meta_cap'] = (bool) $post_type->map_meta_cap;
1028          }
1029  
1030          if ( in_array( 'menu', $fields, true ) ) {
1031              $_post_type['menu_position'] = (int) $post_type->menu_position;
1032              $_post_type['menu_icon']     = $post_type->menu_icon;
1033              $_post_type['show_in_menu']  = (bool) $post_type->show_in_menu;
1034          }
1035  
1036          if ( in_array( 'taxonomies', $fields, true ) ) {
1037              $_post_type['taxonomies'] = get_object_taxonomies( $post_type->name, 'names' );
1038          }
1039  
1040          /**
1041           * Filters XML-RPC-prepared date for the given post type.
1042           *
1043           * @since 3.4.0
1044           * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
1045           *
1046           * @param array        $_post_type An array of post type data.
1047           * @param WP_Post_Type $post_type  Post type object.
1048           */
1049          return apply_filters( 'xmlrpc_prepare_post_type', $_post_type, $post_type );
1050      }
1051  
1052      /**
1053       * Prepares media item data for return in an XML-RPC object.
1054       *
1055       * @param WP_Post $media_item     The unprepared media item data.
1056       * @param string  $thumbnail_size The image size to use for the thumbnail URL.
1057       * @return array The prepared media item data.
1058       */
1059  	protected function _prepare_media_item( $media_item, $thumbnail_size = 'thumbnail' ) {
1060          $_media_item = array(
1061              'attachment_id'    => (string) $media_item->ID,
1062              'date_created_gmt' => $this->_convert_date_gmt( $media_item->post_date_gmt, $media_item->post_date ),
1063              'parent'           => $media_item->post_parent,
1064              'link'             => wp_get_attachment_url( $media_item->ID ),
1065              'title'            => $media_item->post_title,
1066              'caption'          => $media_item->post_excerpt,
1067              'description'      => $media_item->post_content,
1068              'metadata'         => wp_get_attachment_metadata( $media_item->ID ),
1069              'type'             => $media_item->post_mime_type,
1070              'alt'              => get_post_meta( $media_item->ID, '_wp_attachment_image_alt', true ),
1071          );
1072  
1073          $thumbnail_src = image_downsize( $media_item->ID, $thumbnail_size );
1074          if ( $thumbnail_src ) {
1075              $_media_item['thumbnail'] = $thumbnail_src[0];
1076          } else {
1077              $_media_item['thumbnail'] = $_media_item['link'];
1078          }
1079  
1080          /**
1081           * Filters XML-RPC-prepared data for the given media item.
1082           *
1083           * @since 3.4.0
1084           *
1085           * @param array   $_media_item    An array of media item data.
1086           * @param WP_Post $media_item     Media item object.
1087           * @param string  $thumbnail_size Image size.
1088           */
1089          return apply_filters( 'xmlrpc_prepare_media_item', $_media_item, $media_item, $thumbnail_size );
1090      }
1091  
1092      /**
1093       * Prepares page data for return in an XML-RPC object.
1094       *
1095       * @param WP_Post $page The unprepared page data.
1096       * @return array The prepared page data.
1097       */
1098  	protected function _prepare_page( $page ) {
1099          // Get all of the page content and link.
1100          $full_page = get_extended( $page->post_content );
1101          $link      = get_permalink( $page->ID );
1102  
1103          // Get info the page parent if there is one.
1104          $parent_title = '';
1105          if ( ! empty( $page->post_parent ) ) {
1106              $parent       = get_post( $page->post_parent );
1107              $parent_title = $parent->post_title;
1108          }
1109  
1110          // Determine comment and ping settings.
1111          $allow_comments = comments_open( $page->ID ) ? 1 : 0;
1112          $allow_pings    = pings_open( $page->ID ) ? 1 : 0;
1113  
1114          // Format page date.
1115          $page_date     = $this->_convert_date( $page->post_date );
1116          $page_date_gmt = $this->_convert_date_gmt( $page->post_date_gmt, $page->post_date );
1117  
1118          // Pull the categories info together.
1119          $categories = array();
1120          if ( is_object_in_taxonomy( 'page', 'category' ) ) {
1121              foreach ( wp_get_post_categories( $page->ID ) as $cat_id ) {
1122                  $categories[] = get_cat_name( $cat_id );
1123              }
1124          }
1125  
1126          // Get the author info.
1127          $author = get_userdata( $page->post_author );
1128  
1129          $page_template = get_page_template_slug( $page->ID );
1130          if ( empty( $page_template ) ) {
1131              $page_template = 'default';
1132          }
1133  
1134          $_page = array(
1135              'dateCreated'            => $page_date,
1136              'userid'                 => $page->post_author,
1137              'page_id'                => $page->ID,
1138              'page_status'            => $page->post_status,
1139              'description'            => $full_page['main'],
1140              'title'                  => $page->post_title,
1141              'link'                   => $link,
1142              'permaLink'              => $link,
1143              'categories'             => $categories,
1144              'excerpt'                => $page->post_excerpt,
1145              'text_more'              => $full_page['extended'],
1146              'mt_allow_comments'      => $allow_comments,
1147              'mt_allow_pings'         => $allow_pings,
1148              'wp_slug'                => $page->post_name,
1149              'wp_password'            => $page->post_password,
1150              'wp_author'              => $author->display_name,
1151              'wp_page_parent_id'      => $page->post_parent,
1152              'wp_page_parent_title'   => $parent_title,
1153              'wp_page_order'          => $page->menu_order,
1154              'wp_author_id'           => (string) $author->ID,
1155              'wp_author_display_name' => $author->display_name,
1156              'date_created_gmt'       => $page_date_gmt,
1157              'custom_fields'          => $this->get_custom_fields( $page->ID ),
1158              'wp_page_template'       => $page_template,
1159          );
1160  
1161          /**
1162           * Filters XML-RPC-prepared data for the given page.
1163           *
1164           * @since 3.4.0
1165           *
1166           * @param array   $_page An array of page data.
1167           * @param WP_Post $page  Page object.
1168           */
1169          return apply_filters( 'xmlrpc_prepare_page', $_page, $page );
1170      }
1171  
1172      /**
1173       * Prepares comment data for return in an XML-RPC object.
1174       *
1175       * @param WP_Comment $comment The unprepared comment data.
1176       * @return array The prepared comment data.
1177       */
1178  	protected function _prepare_comment( $comment ) {
1179          // Format page date.
1180          $comment_date_gmt = $this->_convert_date_gmt( $comment->comment_date_gmt, $comment->comment_date );
1181  
1182          if ( '0' == $comment->comment_approved ) {
1183              $comment_status = 'hold';
1184          } elseif ( 'spam' === $comment->comment_approved ) {
1185              $comment_status = 'spam';
1186          } elseif ( '1' == $comment->comment_approved ) {
1187              $comment_status = 'approve';
1188          } else {
1189              $comment_status = $comment->comment_approved;
1190          }
1191          $_comment = array(
1192              'date_created_gmt' => $comment_date_gmt,
1193              'user_id'          => $comment->user_id,
1194              'comment_id'       => $comment->comment_ID,
1195              'parent'           => $comment->comment_parent,
1196              'status'           => $comment_status,
1197              'content'          => $comment->comment_content,
1198              'link'             => get_comment_link( $comment ),
1199              'post_id'          => $comment->comment_post_ID,
1200              'post_title'       => get_the_title( $comment->comment_post_ID ),
1201              'author'           => $comment->comment_author,
1202              'author_url'       => $comment->comment_author_url,
1203              'author_email'     => $comment->comment_author_email,
1204              'author_ip'        => $comment->comment_author_IP,
1205              'type'             => $comment->comment_type,
1206          );
1207  
1208          /**
1209           * Filters XML-RPC-prepared data for the given comment.
1210           *
1211           * @since 3.4.0
1212           *
1213           * @param array      $_comment An array of prepared comment data.
1214           * @param WP_Comment $comment  Comment object.
1215           */
1216          return apply_filters( 'xmlrpc_prepare_comment', $_comment, $comment );
1217      }
1218  
1219      /**
1220       * Prepares user data for return in an XML-RPC object.
1221       *
1222       * @param WP_User $user   The unprepared user object.
1223       * @param array   $fields The subset of user fields to return.
1224       * @return array The prepared user data.
1225       */
1226  	protected function _prepare_user( $user, $fields ) {
1227          $_user = array( 'user_id' => (string) $user->ID );
1228  
1229          $user_fields = array(
1230              'username'     => $user->user_login,
1231              'first_name'   => $user->user_firstname,
1232              'last_name'    => $user->user_lastname,
1233              'registered'   => $this->_convert_date( $user->user_registered ),
1234              'bio'          => $user->user_description,
1235              'email'        => $user->user_email,
1236              'nickname'     => $user->nickname,
1237              'nicename'     => $user->user_nicename,
1238              'url'          => $user->user_url,
1239              'display_name' => $user->display_name,
1240              'roles'        => $user->roles,
1241          );
1242  
1243          if ( in_array( 'all', $fields, true ) ) {
1244              $_user = array_merge( $_user, $user_fields );
1245          } else {
1246              if ( in_array( 'basic', $fields, true ) ) {
1247                  $basic_fields = array( 'username', 'email', 'registered', 'display_name', 'nicename' );
1248                  $fields       = array_merge( $fields, $basic_fields );
1249              }
1250              $requested_fields = array_intersect_key( $user_fields, array_flip( $fields ) );
1251              $_user            = array_merge( $_user, $requested_fields );
1252          }
1253  
1254          /**
1255           * Filters XML-RPC-prepared data for the given user.
1256           *
1257           * @since 3.5.0
1258           *
1259           * @param array   $_user  An array of user data.
1260           * @param WP_User $user   User object.
1261           * @param array   $fields An array of user fields.
1262           */
1263          return apply_filters( 'xmlrpc_prepare_user', $_user, $user, $fields );
1264      }
1265  
1266      /**
1267       * Creates a new post for any registered post type.
1268       *
1269       * @since 3.4.0
1270       *
1271       * @link https://en.wikipedia.org/wiki/RSS_enclosure for information on RSS enclosures.
1272       *
1273       * @param array $args {
1274       *     Method arguments. Note: top-level arguments must be ordered as documented.
1275       *
1276       *     @type int    $0 Blog ID (unused).
1277       *     @type string $1 Username.
1278       *     @type string $2 Password.
1279       *     @type array  $3 {
1280       *         Content struct for adding a new post. See wp_insert_post() for information on
1281       *         additional post fields
1282       *
1283       *         @type string $post_type      Post type. Default 'post'.
1284       *         @type string $post_status    Post status. Default 'draft'
1285       *         @type string $post_title     Post title.
1286       *         @type int    $post_author    Post author ID.
1287       *         @type string $post_excerpt   Post excerpt.
1288       *         @type string $post_content   Post content.
1289       *         @type string $post_date_gmt  Post date in GMT.
1290       *         @type string $post_date      Post date.
1291       *         @type string $post_password  Post password (20-character limit).
1292       *         @type string $comment_status Post comment enabled status. Accepts 'open' or 'closed'.
1293       *         @type string $ping_status    Post ping status. Accepts 'open' or 'closed'.
1294       *         @type bool   $sticky         Whether the post should be sticky. Automatically false if
1295       *                                      `$post_status` is 'private'.
1296       *         @type int    $post_thumbnail ID of an image to use as the post thumbnail/featured image.
1297       *         @type array  $custom_fields  Array of meta key/value pairs to add to the post.
1298       *         @type array  $terms          Associative array with taxonomy names as keys and arrays
1299       *                                      of term IDs as values.
1300       *         @type array  $terms_names    Associative array with taxonomy names as keys and arrays
1301       *                                      of term names as values.
1302       *         @type array  $enclosure      {
1303       *             Array of feed enclosure data to add to post meta.
1304       *
1305       *             @type string $url    URL for the feed enclosure.
1306       *             @type int    $length Size in bytes of the enclosure.
1307       *             @type string $type   Mime-type for the enclosure.
1308       *         }
1309       *     }
1310       * }
1311       * @return int|IXR_Error Post ID on success, IXR_Error instance otherwise.
1312       */
1313  	public function wp_newPost( $args ) {
1314          if ( ! $this->minimum_args( $args, 4 ) ) {
1315              return $this->error;
1316          }
1317  
1318          $this->escape( $args );
1319  
1320          $username       = $args[1];
1321          $password       = $args[2];
1322          $content_struct = $args[3];
1323  
1324          $user = $this->login( $username, $password );
1325          if ( ! $user ) {
1326              return $this->error;
1327          }
1328  
1329          // Convert the date field back to IXR form.
1330          if ( isset( $content_struct['post_date'] ) && ! ( $content_struct['post_date'] instanceof IXR_Date ) ) {
1331              $content_struct['post_date'] = $this->_convert_date( $content_struct['post_date'] );
1332          }
1333  
1334          /*
1335           * Ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct,
1336           * since _insert_post() will ignore the non-GMT date if the GMT date is set.
1337           */
1338          if ( isset( $content_struct['post_date_gmt'] ) && ! ( $content_struct['post_date_gmt'] instanceof IXR_Date ) ) {
1339              if ( '0000-00-00 00:00:00' === $content_struct['post_date_gmt'] || isset( $content_struct['post_date'] ) ) {
1340                  unset( $content_struct['post_date_gmt'] );
1341              } else {
1342                  $content_struct['post_date_gmt'] = $this->_convert_date( $content_struct['post_date_gmt'] );
1343              }
1344          }
1345  
1346          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1347          do_action( 'xmlrpc_call', 'wp.newPost', $args, $this );
1348  
1349          unset( $content_struct['ID'] );
1350  
1351          return $this->_insert_post( $user, $content_struct );
1352      }
1353  
1354      /**
1355       * Helper method for filtering out elements from an array.
1356       *
1357       * @since 3.4.0
1358       *
1359       * @param int $count Number to compare to one.
1360       * @return bool True if the number is greater than one, false otherwise.
1361       */
1362  	private function _is_greater_than_one( $count ) {
1363          return $count > 1;
1364      }
1365  
1366      /**
1367       * Encapsulates the logic for sticking a post and determining if
1368       * the user has permission to do so.
1369       *
1370       * @since 4.3.0
1371       *
1372       * @param array $post_data
1373       * @param bool  $update
1374       * @return void|IXR_Error
1375       */
1376  	private function _toggle_sticky( $post_data, $update = false ) {
1377          $post_type = get_post_type_object( $post_data['post_type'] );
1378  
1379          // Private and password-protected posts cannot be stickied.
1380          if ( 'private' === $post_data['post_status'] || ! empty( $post_data['post_password'] ) ) {
1381              // Error if the client tried to stick the post, otherwise, silently unstick.
1382              if ( ! empty( $post_data['sticky'] ) ) {
1383                  return new IXR_Error( 401, __( 'Sorry, you cannot stick a private post.' ) );
1384              }
1385  
1386              if ( $update ) {
1387                  unstick_post( $post_data['ID'] );
1388              }
1389          } elseif ( isset( $post_data['sticky'] ) ) {
1390              if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
1391                  return new IXR_Error( 401, __( 'Sorry, you are not allowed to make posts sticky.' ) );
1392              }
1393  
1394              $sticky = wp_validate_boolean( $post_data['sticky'] );
1395              if ( $sticky ) {
1396                  stick_post( $post_data['ID'] );
1397              } else {
1398                  unstick_post( $post_data['ID'] );
1399              }
1400          }
1401      }
1402  
1403      /**
1404       * Helper method for wp_newPost() and wp_editPost(), containing shared logic.
1405       *
1406       * @since 3.4.0
1407       *
1408       * @see wp_insert_post()
1409       *
1410       * @param WP_User         $user           The post author if post_author isn't set in $content_struct.
1411       * @param array|IXR_Error $content_struct Post data to insert.
1412       * @return IXR_Error|string
1413       */
1414  	protected function _insert_post( $user, $content_struct ) {
1415          $defaults = array(
1416              'post_status'    => 'draft',
1417              'post_type'      => 'post',
1418              'post_author'    => 0,
1419              'post_password'  => '',
1420              'post_excerpt'   => '',
1421              'post_content'   => '',
1422              'post_title'     => '',
1423              'post_date'      => '',
1424              'post_date_gmt'  => '',
1425              'post_format'    => null,
1426              'post_name'      => null,
1427              'post_thumbnail' => null,
1428              'post_parent'    => 0,
1429              'ping_status'    => '',
1430              'comment_status' => '',
1431              'custom_fields'  => null,
1432              'terms_names'    => null,
1433              'terms'          => null,
1434              'sticky'         => null,
1435              'enclosure'      => null,
1436              'ID'             => null,
1437          );
1438  
1439          $post_data = wp_parse_args( array_intersect_key( $content_struct, $defaults ), $defaults );
1440  
1441          $post_type = get_post_type_object( $post_data['post_type'] );
1442          if ( ! $post_type ) {
1443              return new IXR_Error( 403, __( 'Invalid post type.' ) );
1444          }
1445  
1446          $update = ! empty( $post_data['ID'] );
1447  
1448          if ( $update ) {
1449              if ( ! get_post( $post_data['ID'] ) ) {
1450                  return new IXR_Error( 401, __( 'Invalid post ID.' ) );
1451              }
1452              if ( ! current_user_can( 'edit_post', $post_data['ID'] ) ) {
1453                  return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
1454              }
1455              if ( get_post_type( $post_data['ID'] ) !== $post_data['post_type'] ) {
1456                  return new IXR_Error( 401, __( 'The post type may not be changed.' ) );
1457              }
1458          } else {
1459              if ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( $post_type->cap->edit_posts ) ) {
1460                  return new IXR_Error( 401, __( 'Sorry, you are not allowed to post on this site.' ) );
1461              }
1462          }
1463  
1464          switch ( $post_data['post_status'] ) {
1465              case 'draft':
1466              case 'pending':
1467                  break;
1468              case 'private':
1469                  if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
1470                      return new IXR_Error( 401, __( 'Sorry, you are not allowed to create private posts in this post type.' ) );
1471                  }
1472                  break;
1473              case 'publish':
1474              case 'future':
1475                  if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
1476                      return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts in this post type.' ) );
1477                  }
1478                  break;
1479              default:
1480                  if ( ! get_post_status_object( $post_data['post_status'] ) ) {
1481                      $post_data['post_status'] = 'draft';
1482                  }
1483                  break;
1484          }
1485  
1486          if ( ! empty( $post_data['post_password'] ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
1487              return new IXR_Error( 401, __( 'Sorry, you are not allowed to create password protected posts in this post type.' ) );
1488          }
1489  
1490          $post_data['post_author'] = absint( $post_data['post_author'] );
1491          if ( ! empty( $post_data['post_author'] ) && $post_data['post_author'] != $user->ID ) {
1492              if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
1493                  return new IXR_Error( 401, __( 'Sorry, you are not allowed to create posts as this user.' ) );
1494              }
1495  
1496              $author = get_userdata( $post_data['post_author'] );
1497  
1498              if ( ! $author ) {
1499                  return new IXR_Error( 404, __( 'Invalid author ID.' ) );
1500              }
1501          } else {
1502              $post_data['post_author'] = $user->ID;
1503          }
1504  
1505          if ( 'open' !== $post_data['comment_status'] && 'closed' !== $post_data['comment_status'] ) {
1506              unset( $post_data['comment_status'] );
1507          }
1508  
1509          if ( 'open' !== $post_data['ping_status'] && 'closed' !== $post_data['ping_status'] ) {
1510              unset( $post_data['ping_status'] );
1511          }
1512  
1513          // Do some timestamp voodoo.
1514          if ( ! empty( $post_data['post_date_gmt'] ) ) {
1515              // We know this is supposed to be GMT, so we're going to slap that Z on there by force.
1516              $dateCreated = rtrim( $post_data['post_date_gmt']->getIso(), 'Z' ) . 'Z';
1517          } elseif ( ! empty( $post_data['post_date'] ) ) {
1518              $dateCreated = $post_data['post_date']->getIso();
1519          }
1520  
1521          // Default to not flagging the post date to be edited unless it's intentional.
1522          $post_data['edit_date'] = false;
1523  
1524          if ( ! empty( $dateCreated ) ) {
1525              $post_data['post_date']     = iso8601_to_datetime( $dateCreated );
1526              $post_data['post_date_gmt'] = iso8601_to_datetime( $dateCreated, 'gmt' );
1527  
1528              // Flag the post date to be edited.
1529              $post_data['edit_date'] = true;
1530          }
1531  
1532          if ( ! isset( $post_data['ID'] ) ) {
1533              $post_data['ID'] = get_default_post_to_edit( $post_data['post_type'], true )->ID;
1534          }
1535          $post_id = $post_data['ID'];
1536  
1537          if ( 'post' === $post_data['post_type'] ) {
1538              $error = $this->_toggle_sticky( $post_data, $update );
1539              if ( $error ) {
1540                  return $error;
1541              }
1542          }
1543  
1544          if ( isset( $post_data['post_thumbnail'] ) ) {
1545              // Empty value deletes, non-empty value adds/updates.
1546              if ( ! $post_data['post_thumbnail'] ) {
1547                  delete_post_thumbnail( $post_id );
1548              } elseif ( ! get_post( absint( $post_data['post_thumbnail'] ) ) ) {
1549                  return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
1550              }
1551              set_post_thumbnail( $post_id, $post_data['post_thumbnail'] );
1552              unset( $content_struct['post_thumbnail'] );
1553          }
1554  
1555          if ( isset( $post_data['custom_fields'] ) ) {
1556              $this->set_custom_fields( $post_id, $post_data['custom_fields'] );
1557          }
1558  
1559          if ( isset( $post_data['terms'] ) || isset( $post_data['terms_names'] ) ) {
1560              $post_type_taxonomies = get_object_taxonomies( $post_data['post_type'], 'objects' );
1561  
1562              // Accumulate term IDs from terms and terms_names.
1563              $terms = array();
1564  
1565              // First validate the terms specified by ID.
1566              if ( isset( $post_data['terms'] ) && is_array( $post_data['terms'] ) ) {
1567                  $taxonomies = array_keys( $post_data['terms'] );
1568  
1569                  // Validating term IDs.
1570                  foreach ( $taxonomies as $taxonomy ) {
1571                      if ( ! array_key_exists( $taxonomy, $post_type_taxonomies ) ) {
1572                          return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
1573                      }
1574  
1575                      if ( ! current_user_can( $post_type_taxonomies[ $taxonomy ]->cap->assign_terms ) ) {
1576                          return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
1577                      }
1578  
1579                      $term_ids           = $post_data['terms'][ $taxonomy ];
1580                      $terms[ $taxonomy ] = array();
1581                      foreach ( $term_ids as $term_id ) {
1582                          $term = get_term_by( 'id', $term_id, $taxonomy );
1583  
1584                          if ( ! $term ) {
1585                              return new IXR_Error( 403, __( 'Invalid term ID.' ) );
1586                          }
1587  
1588                          $terms[ $taxonomy ][] = (int) $term_id;
1589                      }
1590                  }
1591              }
1592  
1593              // Now validate terms specified by name.
1594              if ( isset( $post_data['terms_names'] ) && is_array( $post_data['terms_names'] ) ) {
1595                  $taxonomies = array_keys( $post_data['terms_names'] );
1596  
1597                  foreach ( $taxonomies as $taxonomy ) {
1598                      if ( ! array_key_exists( $taxonomy, $post_type_taxonomies ) ) {
1599                          return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
1600                      }
1601  
1602                      if ( ! current_user_can( $post_type_taxonomies[ $taxonomy ]->cap->assign_terms ) ) {
1603                          return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
1604                      }
1605  
1606                      /*
1607                       * For hierarchical taxonomies, we can't assign a term when multiple terms
1608                       * in the hierarchy share the same name.
1609                       */
1610                      $ambiguous_terms = array();
1611                      if ( is_taxonomy_hierarchical( $taxonomy ) ) {
1612                          $tax_term_names = get_terms(
1613                              array(
1614                                  'taxonomy'   => $taxonomy,
1615                                  'fields'     => 'names',
1616                                  'hide_empty' => false,
1617                              )
1618                          );
1619  
1620                          // Count the number of terms with the same name.
1621                          $tax_term_names_count = array_count_values( $tax_term_names );
1622  
1623                          // Filter out non-ambiguous term names.
1624                          $ambiguous_tax_term_counts = array_filter( $tax_term_names_count, array( $this, '_is_greater_than_one' ) );
1625  
1626                          $ambiguous_terms = array_keys( $ambiguous_tax_term_counts );
1627                      }
1628  
1629                      $term_names = $post_data['terms_names'][ $taxonomy ];
1630                      foreach ( $term_names as $term_name ) {
1631                          if ( in_array( $term_name, $ambiguous_terms, true ) ) {
1632                              return new IXR_Error( 401, __( 'Ambiguous term name used in a hierarchical taxonomy. Please use term ID instead.' ) );
1633                          }
1634  
1635                          $term = get_term_by( 'name', $term_name, $taxonomy );
1636  
1637                          if ( ! $term ) {
1638                              // Term doesn't exist, so check that the user is allowed to create new terms.
1639                              if ( ! current_user_can( $post_type_taxonomies[ $taxonomy ]->cap->edit_terms ) ) {
1640                                  return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a term to one of the given taxonomies.' ) );
1641                              }
1642  
1643                              // Create the new term.
1644                              $term_info = wp_insert_term( $term_name, $taxonomy );
1645                              if ( is_wp_error( $term_info ) ) {
1646                                  return new IXR_Error( 500, $term_info->get_error_message() );
1647                              }
1648  
1649                              $terms[ $taxonomy ][] = (int) $term_info['term_id'];
1650                          } else {
1651                              $terms[ $taxonomy ][] = (int) $term->term_id;
1652                          }
1653                      }
1654                  }
1655              }
1656  
1657              $post_data['tax_input'] = $terms;
1658              unset( $post_data['terms'], $post_data['terms_names'] );
1659          }
1660  
1661          if ( isset( $post_data['post_format'] ) ) {
1662              $format = set_post_format( $post_id, $post_data['post_format'] );
1663  
1664              if ( is_wp_error( $format ) ) {
1665                  return new IXR_Error( 500, $format->get_error_message() );
1666              }
1667  
1668              unset( $post_data['post_format'] );
1669          }
1670  
1671          // Handle enclosures.
1672          $enclosure = isset( $post_data['enclosure'] ) ? $post_data['enclosure'] : null;
1673          $this->add_enclosure_if_new( $post_id, $enclosure );
1674  
1675          $this->attach_uploads( $post_id, $post_data['post_content'] );
1676  
1677          /**
1678           * Filters post data array to be inserted via XML-RPC.
1679           *
1680           * @since 3.4.0
1681           *
1682           * @param array $post_data      Parsed array of post data.
1683           * @param array $content_struct Post data array.
1684           */
1685          $post_data = apply_filters( 'xmlrpc_wp_insert_post_data', $post_data, $content_struct );
1686  
1687          // Remove all null values to allow for using the insert/update post default values for those keys instead.
1688          $post_data = array_filter(
1689              $post_data,
1690              static function ( $value ) {
1691                  return null !== $value;
1692              }
1693          );
1694  
1695          $post_id = $update ? wp_update_post( $post_data, true ) : wp_insert_post( $post_data, true );
1696          if ( is_wp_error( $post_id ) ) {
1697              return new IXR_Error( 500, $post_id->get_error_message() );
1698          }
1699  
1700          if ( ! $post_id ) {
1701              if ( $update ) {
1702                  return new IXR_Error( 401, __( 'Sorry, the post could not be updated.' ) );
1703              } else {
1704                  return new IXR_Error( 401, __( 'Sorry, the post could not be created.' ) );
1705              }
1706          }
1707  
1708          return (string) $post_id;
1709      }
1710  
1711      /**
1712       * Edits a post for any registered post type.
1713       *
1714       * The $content_struct parameter only needs to contain fields that
1715       * should be changed. All other fields will retain their existing values.
1716       *
1717       * @since 3.4.0
1718       *
1719       * @param array $args {
1720       *     Method arguments. Note: arguments must be ordered as documented.
1721       *
1722       *     @type int    $0 Blog ID (unused).
1723       *     @type string $1 Username.
1724       *     @type string $2 Password.
1725       *     @type int    $3 Post ID.
1726       *     @type array  $4 Extra content arguments.
1727       * }
1728       * @return true|IXR_Error True on success, IXR_Error on failure.
1729       */
1730  	public function wp_editPost( $args ) {
1731          if ( ! $this->minimum_args( $args, 5 ) ) {
1732              return $this->error;
1733          }
1734  
1735          $this->escape( $args );
1736  
1737          $username       = $args[1];
1738          $password       = $args[2];
1739          $post_id        = (int) $args[3];
1740          $content_struct = $args[4];
1741  
1742          $user = $this->login( $username, $password );
1743          if ( ! $user ) {
1744              return $this->error;
1745          }
1746  
1747          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1748          do_action( 'xmlrpc_call', 'wp.editPost', $args, $this );
1749  
1750          $post = get_post( $post_id, ARRAY_A );
1751  
1752          if ( empty( $post['ID'] ) ) {
1753              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1754          }
1755  
1756          if ( isset( $content_struct['if_not_modified_since'] ) ) {
1757              // If the post has been modified since the date provided, return an error.
1758              if ( mysql2date( 'U', $post['post_modified_gmt'] ) > $content_struct['if_not_modified_since']->getTimestamp() ) {
1759                  return new IXR_Error( 409, __( 'There is a revision of this post that is more recent.' ) );
1760              }
1761          }
1762  
1763          // Convert the date field back to IXR form.
1764          $post['post_date'] = $this->_convert_date( $post['post_date'] );
1765  
1766          /*
1767           * Ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct,
1768           * since _insert_post() will ignore the non-GMT date if the GMT date is set.
1769           */
1770          if ( '0000-00-00 00:00:00' === $post['post_date_gmt'] || isset( $content_struct['post_date'] ) ) {
1771              unset( $post['post_date_gmt'] );
1772          } else {
1773              $post['post_date_gmt'] = $this->_convert_date( $post['post_date_gmt'] );
1774          }
1775  
1776          /*
1777           * If the API client did not provide 'post_date', then we must not perpetuate the value that
1778           * was stored in the database, or it will appear to be an intentional edit. Conveying it here
1779           * as if it was coming from the API client will cause an otherwise zeroed out 'post_date_gmt'
1780           * to get set with the value that was originally stored in the database when the draft was created.
1781           */
1782          if ( ! isset( $content_struct['post_date'] ) ) {
1783              unset( $post['post_date'] );
1784          }
1785  
1786          $this->escape( $post );
1787          $merged_content_struct = array_merge( $post, $content_struct );
1788  
1789          $retval = $this->_insert_post( $user, $merged_content_struct );
1790          if ( $retval instanceof IXR_Error ) {
1791              return $retval;
1792          }
1793  
1794          return true;
1795      }
1796  
1797      /**
1798       * Deletes a post for any registered post type.
1799       *
1800       * @since 3.4.0
1801       *
1802       * @see wp_delete_post()
1803       *
1804       * @param array $args {
1805       *     Method arguments. Note: arguments must be ordered as documented.
1806       *
1807       *     @type int    $0 Blog ID (unused).
1808       *     @type string $1 Username.
1809       *     @type string $2 Password.
1810       *     @type int    $3 Post ID.
1811       * }
1812       * @return true|IXR_Error True on success, IXR_Error instance on failure.
1813       */
1814  	public function wp_deletePost( $args ) {
1815          if ( ! $this->minimum_args( $args, 4 ) ) {
1816              return $this->error;
1817          }
1818  
1819          $this->escape( $args );
1820  
1821          $username = $args[1];
1822          $password = $args[2];
1823          $post_id  = (int) $args[3];
1824  
1825          $user = $this->login( $username, $password );
1826          if ( ! $user ) {
1827              return $this->error;
1828          }
1829  
1830          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1831          do_action( 'xmlrpc_call', 'wp.deletePost', $args, $this );
1832  
1833          $post = get_post( $post_id, ARRAY_A );
1834          if ( empty( $post['ID'] ) ) {
1835              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1836          }
1837  
1838          if ( ! current_user_can( 'delete_post', $post_id ) ) {
1839              return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this post.' ) );
1840          }
1841  
1842          $result = wp_delete_post( $post_id );
1843  
1844          if ( ! $result ) {
1845              return new IXR_Error( 500, __( 'Sorry, the post could not be deleted.' ) );
1846          }
1847  
1848          return true;
1849      }
1850  
1851      /**
1852       * Retrieves a post.
1853       *
1854       * @since 3.4.0
1855       *
1856       * The optional $fields parameter specifies what fields will be included
1857       * in the response array. This should be a list of field names. 'post_id' will
1858       * always be included in the response regardless of the value of $fields.
1859       *
1860       * Instead of, or in addition to, individual field names, conceptual group
1861       * names can be used to specify multiple fields. The available conceptual
1862       * groups are 'post' (all basic fields), 'taxonomies', 'custom_fields',
1863       * and 'enclosure'.
1864       *
1865       * @see get_post()
1866       *
1867       * @param array $args {
1868       *     Method arguments. Note: arguments must be ordered as documented.
1869       *
1870       *     @type int    $0 Blog ID (unused).
1871       *     @type string $1 Username.
1872       *     @type string $2 Password.
1873       *     @type int    $3 Post ID.
1874       *     @type array  $4 Optional. The subset of post type fields to return.
1875       * }
1876       * @return array|IXR_Error Array contains (based on $fields parameter):
1877       *  - 'post_id'
1878       *  - 'post_title'
1879       *  - 'post_date'
1880       *  - 'post_date_gmt'
1881       *  - 'post_modified'
1882       *  - 'post_modified_gmt'
1883       *  - 'post_status'
1884       *  - 'post_type'
1885       *  - 'post_name'
1886       *  - 'post_author'
1887       *  - 'post_password'
1888       *  - 'post_excerpt'
1889       *  - 'post_content'
1890       *  - 'link'
1891       *  - 'comment_status'
1892       *  - 'ping_status'
1893       *  - 'sticky'
1894       *  - 'custom_fields'
1895       *  - 'terms'
1896       *  - 'categories'
1897       *  - 'tags'
1898       *  - 'enclosure'
1899       */
1900  	public function wp_getPost( $args ) {
1901          if ( ! $this->minimum_args( $args, 4 ) ) {
1902              return $this->error;
1903          }
1904  
1905          $this->escape( $args );
1906  
1907          $username = $args[1];
1908          $password = $args[2];
1909          $post_id  = (int) $args[3];
1910  
1911          if ( isset( $args[4] ) ) {
1912              $fields = $args[4];
1913          } else {
1914              /**
1915               * Filters the default post query fields used by the given XML-RPC method.
1916               *
1917               * @since 3.4.0
1918               *
1919               * @param array  $fields An array of post fields to retrieve. By default,
1920               *                       contains 'post', 'terms', and 'custom_fields'.
1921               * @param string $method Method name.
1922               */
1923              $fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPost' );
1924          }
1925  
1926          $user = $this->login( $username, $password );
1927          if ( ! $user ) {
1928              return $this->error;
1929          }
1930  
1931          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1932          do_action( 'xmlrpc_call', 'wp.getPost', $args, $this );
1933  
1934          $post = get_post( $post_id, ARRAY_A );
1935  
1936          if ( empty( $post['ID'] ) ) {
1937              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1938          }
1939  
1940          if ( ! current_user_can( 'edit_post', $post_id ) ) {
1941              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
1942          }
1943  
1944          return $this->_prepare_post( $post, $fields );
1945      }
1946  
1947      /**
1948       * Retrieves posts.
1949       *
1950       * @since 3.4.0
1951       *
1952       * @see wp_get_recent_posts()
1953       * @see wp_getPost() for more on `$fields`
1954       * @see get_posts() for more on `$filter` values
1955       *
1956       * @param array $args {
1957       *     Method arguments. Note: arguments must be ordered as documented.
1958       *
1959       *     @type int    $0 Blog ID (unused).
1960       *     @type string $1 Username.
1961       *     @type string $2 Password.
1962       *     @type array  $3 Optional. Modifies the query used to retrieve posts. Accepts 'post_type',
1963       *                     'post_status', 'number', 'offset', 'orderby', 's', and 'order'.
1964       *                     Default empty array.
1965       *     @type array  $4 Optional. The subset of post type fields to return in the response array.
1966       * }
1967       * @return array|IXR_Error Array containing a collection of posts.
1968       */
1969  	public function wp_getPosts( $args ) {
1970          if ( ! $this->minimum_args( $args, 3 ) ) {
1971              return $this->error;
1972          }
1973  
1974          $this->escape( $args );
1975  
1976          $username = $args[1];
1977          $password = $args[2];
1978          $filter   = isset( $args[3] ) ? $args[3] : array();
1979  
1980          if ( isset( $args[4] ) ) {
1981              $fields = $args[4];
1982          } else {
1983              /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1984              $fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPosts' );
1985          }
1986  
1987          $user = $this->login( $username, $password );
1988          if ( ! $user ) {
1989              return $this->error;
1990          }
1991  
1992          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1993          do_action( 'xmlrpc_call', 'wp.getPosts', $args, $this );
1994  
1995          $query = array();
1996  
1997          if ( isset( $filter['post_type'] ) ) {
1998              $post_type = get_post_type_object( $filter['post_type'] );
1999              if ( ! ( (bool) $post_type ) ) {
2000                  return new IXR_Error( 403, __( 'Invalid post type.' ) );
2001              }
2002          } else {
2003              $post_type = get_post_type_object( 'post' );
2004          }
2005  
2006          if ( ! current_user_can( $post_type->cap->edit_posts ) ) {
2007              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type.' ) );
2008          }
2009  
2010          $query['post_type'] = $post_type->name;
2011  
2012          if ( isset( $filter['post_status'] ) ) {
2013              $query['post_status'] = $filter['post_status'];
2014          }
2015  
2016          if ( isset( $filter['number'] ) ) {
2017              $query['numberposts'] = absint( $filter['number'] );
2018          }
2019  
2020          if ( isset( $filter['offset'] ) ) {
2021              $query['offset'] = absint( $filter['offset'] );
2022          }
2023  
2024          if ( isset( $filter['orderby'] ) ) {
2025              $query['orderby'] = $filter['orderby'];
2026  
2027              if ( isset( $filter['order'] ) ) {
2028                  $query['order'] = $filter['order'];
2029              }
2030          }
2031  
2032          if ( isset( $filter['s'] ) ) {
2033              $query['s'] = $filter['s'];
2034          }
2035  
2036          $posts_list = wp_get_recent_posts( $query );
2037  
2038          if ( ! $posts_list ) {
2039              return array();
2040          }
2041  
2042          // Holds all the posts data.
2043          $struct = array();
2044  
2045          foreach ( $posts_list as $post ) {
2046              if ( ! current_user_can( 'edit_post', $post['ID'] ) ) {
2047                  continue;
2048              }
2049  
2050              $struct[] = $this->_prepare_post( $post, $fields );
2051          }
2052  
2053          return $struct;
2054      }
2055  
2056      /**
2057       * Creates a new term.
2058       *
2059       * @since 3.4.0
2060       *
2061       * @see wp_insert_term()
2062       *
2063       * @param array $args {
2064       *     Method arguments. Note: arguments must be ordered as documented.
2065       *
2066       *     @type int    $0 Blog ID (unused).
2067       *     @type string $1 Username.
2068       *     @type string $2 Password.
2069       *     @type array  $3 Content struct for adding a new term. The struct must contain
2070       *                     the term 'name' and 'taxonomy'. Optional accepted values include
2071       *                     'parent', 'description', and 'slug'.
2072       * }
2073       * @return int|IXR_Error The term ID on success, or an IXR_Error object on failure.
2074       */
2075  	public function wp_newTerm( $args ) {
2076          if ( ! $this->minimum_args( $args, 4 ) ) {
2077              return $this->error;
2078          }
2079  
2080          $this->escape( $args );
2081  
2082          $username       = $args[1];
2083          $password       = $args[2];
2084          $content_struct = $args[3];
2085  
2086          $user = $this->login( $username, $password );
2087          if ( ! $user ) {
2088              return $this->error;
2089          }
2090  
2091          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2092          do_action( 'xmlrpc_call', 'wp.newTerm', $args, $this );
2093  
2094          if ( ! taxonomy_exists( $content_struct['taxonomy'] ) ) {
2095              return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2096          }
2097  
2098          $taxonomy = get_taxonomy( $content_struct['taxonomy'] );
2099  
2100          if ( ! current_user_can( $taxonomy->cap->edit_terms ) ) {
2101              return new IXR_Error( 401, __( 'Sorry, you are not allowed to create terms in this taxonomy.' ) );
2102          }
2103  
2104          $taxonomy = (array) $taxonomy;
2105  
2106          // Hold the data of the term.
2107          $term_data = array();
2108  
2109          $term_data['name'] = trim( $content_struct['name'] );
2110          if ( empty( $term_data['name'] ) ) {
2111              return new IXR_Error( 403, __( 'The term name cannot be empty.' ) );
2112          }
2113  
2114          if ( isset( $content_struct['parent'] ) ) {
2115              if ( ! $taxonomy['hierarchical'] ) {
2116                  return new IXR_Error( 403, __( 'This taxonomy is not hierarchical.' ) );
2117              }
2118  
2119              $parent_term_id = (int) $content_struct['parent'];
2120              $parent_term    = get_term( $parent_term_id, $taxonomy['name'] );
2121  
2122              if ( is_wp_error( $parent_term ) ) {
2123                  return new IXR_Error( 500, $parent_term->get_error_message() );
2124              }
2125  
2126              if ( ! $parent_term ) {
2127                  return new IXR_Error( 403, __( 'Parent term does not exist.' ) );
2128              }
2129  
2130              $term_data['parent'] = $content_struct['parent'];
2131          }
2132  
2133          if ( isset( $content_struct['description'] ) ) {
2134              $term_data['description'] = $content_struct['description'];
2135          }
2136  
2137          if ( isset( $content_struct['slug'] ) ) {
2138              $term_data['slug'] = $content_struct['slug'];
2139          }
2140  
2141          $term = wp_insert_term( $term_data['name'], $taxonomy['name'], $term_data );
2142  
2143          if ( is_wp_error( $term ) ) {
2144              return new IXR_Error( 500, $term->get_error_message() );
2145          }
2146  
2147          if ( ! $term ) {
2148              return new IXR_Error( 500, __( 'Sorry, the term could not be created.' ) );
2149          }
2150  
2151          // Add term meta.
2152          if ( isset( $content_struct['custom_fields'] ) ) {
2153              $this->set_term_custom_fields( $term['term_id'], $content_struct['custom_fields'] );
2154          }
2155  
2156          return (string) $term['term_id'];
2157      }
2158  
2159      /**
2160       * Edits a term.
2161       *
2162       * @since 3.4.0
2163       *
2164       * @see wp_update_term()
2165       *
2166       * @param array $args {
2167       *     Method arguments. Note: arguments must be ordered as documented.
2168       *
2169       *     @type int    $0 Blog ID (unused).
2170       *     @type string $1 Username.
2171       *     @type string $2 Password.
2172       *     @type int    $3 Term ID.
2173       *     @type array  $4 Content struct for editing a term. The struct must contain the
2174       *                     term 'taxonomy'. Optional accepted values include 'name', 'parent',
2175       *                     'description', and 'slug'.
2176       * }
2177       * @return true|IXR_Error True on success, IXR_Error instance on failure.
2178       */
2179  	public function wp_editTerm( $args ) {
2180          if ( ! $this->minimum_args( $args, 5 ) ) {
2181              return $this->error;
2182          }
2183  
2184          $this->escape( $args );
2185  
2186          $username       = $args[1];
2187          $password       = $args[2];
2188          $term_id        = (int) $args[3];
2189          $content_struct = $args[4];
2190  
2191          $user = $this->login( $username, $password );
2192          if ( ! $user ) {
2193              return $this->error;
2194          }
2195  
2196          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2197          do_action( 'xmlrpc_call', 'wp.editTerm', $args, $this );
2198  
2199          if ( ! taxonomy_exists( $content_struct['taxonomy'] ) ) {
2200              return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2201          }
2202  
2203          $taxonomy = get_taxonomy( $content_struct['taxonomy'] );
2204  
2205          $taxonomy = (array) $taxonomy;
2206  
2207          // Hold the data of the term.
2208          $term_data = array();
2209  
2210          $term = get_term( $term_id, $content_struct['taxonomy'] );
2211  
2212          if ( is_wp_error( $term ) ) {
2213              return new IXR_Error( 500, $term->get_error_message() );
2214          }
2215  
2216          if ( ! $term ) {
2217              return new IXR_Error( 404, __( 'Invalid term ID.' ) );
2218          }
2219  
2220          if ( ! current_user_can( 'edit_term', $term_id ) ) {
2221              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this term.' ) );
2222          }
2223  
2224          if ( isset( $content_struct['name'] ) ) {
2225              $term_data['name'] = trim( $content_struct['name'] );
2226  
2227              if ( empty( $term_data['name'] ) ) {
2228                  return new IXR_Error( 403, __( 'The term name cannot be empty.' ) );
2229              }
2230          }
2231  
2232          if ( ! empty( $content_struct['parent'] ) ) {
2233              if ( ! $taxonomy['hierarchical'] ) {
2234                  return new IXR_Error( 403, __( 'Cannot set parent term, taxonomy is not hierarchical.' ) );
2235              }
2236  
2237              $parent_term_id = (int) $content_struct['parent'];
2238              $parent_term    = get_term( $parent_term_id, $taxonomy['name'] );
2239  
2240              if ( is_wp_error( $parent_term ) ) {
2241                  return new IXR_Error( 500, $parent_term->get_error_message() );
2242              }
2243  
2244              if ( ! $parent_term ) {
2245                  return new IXR_Error( 403, __( 'Parent term does not exist.' ) );
2246              }
2247  
2248              $term_data['parent'] = $content_struct['parent'];
2249          }
2250  
2251          if ( isset( $content_struct['description'] ) ) {
2252              $term_data['description'] = $content_struct['description'];
2253          }
2254  
2255          if ( isset( $content_struct['slug'] ) ) {
2256              $term_data['slug'] = $content_struct['slug'];
2257          }
2258  
2259          $term = wp_update_term( $term_id, $taxonomy['name'], $term_data );
2260  
2261          if ( is_wp_error( $term ) ) {
2262              return new IXR_Error( 500, $term->get_error_message() );
2263          }
2264  
2265          if ( ! $term ) {
2266              return new IXR_Error( 500, __( 'Sorry, editing the term failed.' ) );
2267          }
2268  
2269          // Update term meta.
2270          if ( isset( $content_struct['custom_fields'] ) ) {
2271              $this->set_term_custom_fields( $term_id, $content_struct['custom_fields'] );
2272          }
2273  
2274          return true;
2275      }
2276  
2277      /**
2278       * Deletes a term.
2279       *
2280       * @since 3.4.0
2281       *
2282       * @see wp_delete_term()
2283       *
2284       * @param array $args {
2285       *     Method arguments. Note: arguments must be ordered as documented.
2286       *
2287       *     @type int    $0 Blog ID (unused).
2288       *     @type string $1 Username.
2289       *     @type string $2 Password.
2290       *     @type string $3 Taxonomy name.
2291       *     @type int    $4 Term ID.
2292       * }
2293       * @return true|IXR_Error True on success, IXR_Error instance on failure.
2294       */
2295  	public function wp_deleteTerm( $args ) {
2296          if ( ! $this->minimum_args( $args, 5 ) ) {
2297              return $this->error;
2298          }
2299  
2300          $this->escape( $args );
2301  
2302          $username = $args[1];
2303          $password = $args[2];
2304          $taxonomy = $args[3];
2305          $term_id  = (int) $args[4];
2306  
2307          $user = $this->login( $username, $password );
2308          if ( ! $user ) {
2309              return $this->error;
2310          }
2311  
2312          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2313          do_action( 'xmlrpc_call', 'wp.deleteTerm', $args, $this );
2314  
2315          if ( ! taxonomy_exists( $taxonomy ) ) {
2316              return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2317          }
2318  
2319          $taxonomy = get_taxonomy( $taxonomy );
2320          $term     = get_term( $term_id, $taxonomy->name );
2321  
2322          if ( is_wp_error( $term ) ) {
2323              return new IXR_Error( 500, $term->get_error_message() );
2324          }
2325  
2326          if ( ! $term ) {
2327              return new IXR_Error( 404, __( 'Invalid term ID.' ) );
2328          }
2329  
2330          if ( ! current_user_can( 'delete_term', $term_id ) ) {
2331              return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this term.' ) );
2332          }
2333  
2334          $result = wp_delete_term( $term_id, $taxonomy->name );
2335  
2336          if ( is_wp_error( $result ) ) {
2337              return new IXR_Error( 500, $term->get_error_message() );
2338          }
2339  
2340          if ( ! $result ) {
2341              return new IXR_Error( 500, __( 'Sorry, deleting the term failed.' ) );
2342          }
2343  
2344          return $result;
2345      }
2346  
2347      /**
2348       * Retrieves a term.
2349       *
2350       * @since 3.4.0
2351       *
2352       * @see get_term()
2353       *
2354       * @param array $args {
2355       *     Method arguments. Note: arguments must be ordered as documented.
2356       *
2357       *     @type int    $0 Blog ID (unused).
2358       *     @type string $1 Username.
2359       *     @type string $2 Password.
2360       *     @type string $3 Taxonomy name.
2361       *     @type int    $4 Term ID.
2362       * }
2363       * @return array|IXR_Error IXR_Error on failure, array on success, containing:
2364       *  - 'term_id'
2365       *  - 'name'
2366       *  - 'slug'
2367       *  - 'term_group'
2368       *  - 'term_taxonomy_id'
2369       *  - 'taxonomy'
2370       *  - 'description'
2371       *  - 'parent'
2372       *  - 'count'
2373       */
2374  	public function wp_getTerm( $args ) {
2375          if ( ! $this->minimum_args( $args, 5 ) ) {
2376              return $this->error;
2377          }
2378  
2379          $this->escape( $args );
2380  
2381          $username = $args[1];
2382          $password = $args[2];
2383          $taxonomy = $args[3];
2384          $term_id  = (int) $args[4];
2385  
2386          $user = $this->login( $username, $password );
2387          if ( ! $user ) {
2388              return $this->error;
2389          }
2390  
2391          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2392          do_action( 'xmlrpc_call', 'wp.getTerm', $args, $this );
2393  
2394          if ( ! taxonomy_exists( $taxonomy ) ) {
2395              return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2396          }
2397  
2398          $taxonomy = get_taxonomy( $taxonomy );
2399  
2400          $term = get_term( $term_id, $taxonomy->name, ARRAY_A );
2401  
2402          if ( is_wp_error( $term ) ) {
2403              return new IXR_Error( 500, $term->get_error_message() );
2404          }
2405  
2406          if ( ! $term ) {
2407              return new IXR_Error( 404, __( 'Invalid term ID.' ) );
2408          }
2409  
2410          if ( ! current_user_can( 'assign_term', $term_id ) ) {
2411              return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign this term.' ) );
2412          }
2413  
2414          return $this->_prepare_term( $term );
2415      }
2416  
2417      /**
2418       * Retrieves all terms for a taxonomy.
2419       *
2420       * @since 3.4.0
2421       *
2422       * The optional $filter parameter modifies the query used to retrieve terms.
2423       * Accepted keys are 'number', 'offset', 'orderby', 'order', 'hide_empty', and 'search'.
2424       *
2425       * @see get_terms()
2426       *
2427       * @param array $args {
2428       *     Method arguments. Note: arguments must be ordered as documented.
2429       *
2430       *     @type int    $0 Blog ID (unused).
2431       *     @type string $1 Username.
2432       *     @type string $2 Password.
2433       *     @type string $3 Taxonomy name.
2434       *     @type array  $4 Optional. Modifies the query used to retrieve posts. Accepts 'number',
2435       *                     'offset', 'orderby', 'order', 'hide_empty', and 'search'. Default empty array.
2436       * }
2437       * @return array|IXR_Error An associative array of terms data on success, IXR_Error instance otherwise.
2438       */
2439  	public function wp_getTerms( $args ) {
2440          if ( ! $this->minimum_args( $args, 4 ) ) {
2441              return $this->error;
2442          }
2443  
2444          $this->escape( $args );
2445  
2446          $username = $args[1];
2447          $password = $args[2];
2448          $taxonomy = $args[3];
2449          $filter   = isset( $args[4] ) ? $args[4] : array();
2450  
2451          $user = $this->login( $username, $password );
2452          if ( ! $user ) {
2453              return $this->error;
2454          }
2455  
2456          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2457          do_action( 'xmlrpc_call', 'wp.getTerms', $args, $this );
2458  
2459          if ( ! taxonomy_exists( $taxonomy ) ) {
2460              return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2461          }
2462  
2463          $taxonomy = get_taxonomy( $taxonomy );
2464  
2465          if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) {
2466              return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) );
2467          }
2468  
2469          $query = array( 'taxonomy' => $taxonomy->name );
2470  
2471          if ( isset( $filter['number'] ) ) {
2472              $query['number'] = absint( $filter['number'] );
2473          }
2474  
2475          if ( isset( $filter['offset'] ) ) {
2476              $query['offset'] = absint( $filter['offset'] );
2477          }
2478  
2479          if ( isset( $filter['orderby'] ) ) {
2480              $query['orderby'] = $filter['orderby'];
2481  
2482              if ( isset( $filter['order'] ) ) {
2483                  $query['order'] = $filter['order'];
2484              }
2485          }
2486  
2487          if ( isset( $filter['hide_empty'] ) ) {
2488              $query['hide_empty'] = $filter['hide_empty'];
2489          } else {
2490              $query['get'] = 'all';
2491          }
2492  
2493          if ( isset( $filter['search'] ) ) {
2494              $query['search'] = $filter['search'];
2495          }
2496  
2497          $terms = get_terms( $query );
2498  
2499          if ( is_wp_error( $terms ) ) {
2500              return new IXR_Error( 500, $terms->get_error_message() );
2501          }
2502  
2503          $struct = array();
2504  
2505          foreach ( $terms as $term ) {
2506              $struct[] = $this->_prepare_term( $term );
2507          }
2508  
2509          return $struct;
2510      }
2511  
2512      /**
2513       * Retrieves a taxonomy.
2514       *
2515       * @since 3.4.0
2516       *
2517       * @see get_taxonomy()
2518       *
2519       * @param array $args {
2520       *     Method arguments. Note: arguments must be ordered as documented.
2521       *
2522       *     @type int    $0 Blog ID (unused).
2523       *     @type string $1 Username.
2524       *     @type string $2 Password.
2525       *     @type string $3 Taxonomy name.
2526       *     @type array  $4 Optional. Array of taxonomy fields to limit to in the return.
2527       *                     Accepts 'labels', 'cap', 'menu', and 'object_type'.
2528       *                     Default empty array.
2529       * }
2530       * @return array|IXR_Error An array of taxonomy data on success, IXR_Error instance otherwise.
2531       */
2532  	public function wp_getTaxonomy( $args ) {
2533          if ( ! $this->minimum_args( $args, 4 ) ) {
2534              return $this->error;
2535          }
2536  
2537          $this->escape( $args );
2538  
2539          $username = $args[1];
2540          $password = $args[2];
2541          $taxonomy = $args[3];
2542  
2543          if ( isset( $args[4] ) ) {
2544              $fields = $args[4];
2545          } else {
2546              /**
2547               * Filters the default taxonomy query fields used by the given XML-RPC method.
2548               *
2549               * @since 3.4.0
2550               *
2551               * @param array  $fields An array of taxonomy fields to retrieve. By default,
2552               *                       contains 'labels', 'cap', and 'object_type'.
2553               * @param string $method The method name.
2554               */
2555              $fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomy' );
2556          }
2557  
2558          $user = $this->login( $username, $password );
2559          if ( ! $user ) {
2560              return $this->error;
2561          }
2562  
2563          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2564          do_action( 'xmlrpc_call', 'wp.getTaxonomy', $args, $this );
2565  
2566          if ( ! taxonomy_exists( $taxonomy ) ) {
2567              return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2568          }
2569  
2570          $taxonomy = get_taxonomy( $taxonomy );
2571  
2572          if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) {
2573              return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) );
2574          }
2575  
2576          return $this->_prepare_taxonomy( $taxonomy, $fields );
2577      }
2578  
2579      /**
2580       * Retrieves all taxonomies.
2581       *
2582       * @since 3.4.0
2583       *
2584       * @see get_taxonomies()
2585       *
2586       * @param array $args {
2587       *     Method arguments. Note: arguments must be ordered as documented.
2588       *
2589       *     @type int    $0 Blog ID (unused).
2590       *     @type string $1 Username.
2591       *     @type string $2 Password.
2592       *     @type array  $3 Optional. An array of arguments for retrieving taxonomies.
2593       *     @type array  $4 Optional. The subset of taxonomy fields to return.
2594       * }
2595       * @return array|IXR_Error An associative array of taxonomy data with returned fields determined
2596       *                         by `$fields`, or an IXR_Error instance on failure.
2597       */
2598  	public function wp_getTaxonomies( $args ) {
2599          if ( ! $this->minimum_args( $args, 3 ) ) {
2600              return $this->error;
2601          }
2602  
2603          $this->escape( $args );
2604  
2605          $username = $args[1];
2606          $password = $args[2];
2607          $filter   = isset( $args[3] ) ? $args[3] : array( 'public' => true );
2608  
2609          if ( isset( $args[4] ) ) {
2610              $fields = $args[4];
2611          } else {
2612              /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2613              $fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomies' );
2614          }
2615  
2616          $user = $this->login( $username, $password );
2617          if ( ! $user ) {
2618              return $this->error;
2619          }
2620  
2621          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2622          do_action( 'xmlrpc_call', 'wp.getTaxonomies', $args, $this );
2623  
2624          $taxonomies = get_taxonomies( $filter, 'objects' );
2625  
2626          // Holds all the taxonomy data.
2627          $struct = array();
2628  
2629          foreach ( $taxonomies as $taxonomy ) {
2630              // Capability check for post types.
2631              if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) {
2632                  continue;
2633              }
2634  
2635              $struct[] = $this->_prepare_taxonomy( $taxonomy, $fields );
2636          }
2637  
2638          return $struct;
2639      }
2640  
2641      /**
2642       * Retrieves a user.
2643       *
2644       * The optional $fields parameter specifies what fields will be included
2645       * in the response array. This should be a list of field names. 'user_id' will
2646       * always be included in the response regardless of the value of $fields.
2647       *
2648       * Instead of, or in addition to, individual field names, conceptual group
2649       * names can be used to specify multiple fields. The available conceptual
2650       * groups are 'basic' and 'all'.
2651       *
2652       * @uses get_userdata()
2653       *
2654       * @param array $args {
2655       *     Method arguments. Note: arguments must be ordered as documented.
2656       *
2657       *     @type int    $0 Blog ID (unused).
2658       *     @type string $1 Username.
2659       *     @type string $2 Password.
2660       *     @type int    $3 User ID.
2661       *     @type array  $4 Optional. Array of fields to return.
2662       * }
2663       * @return array|IXR_Error Array contains (based on $fields parameter):
2664       *  - 'user_id'
2665       *  - 'username'
2666       *  - 'first_name'
2667       *  - 'last_name'
2668       *  - 'registered'
2669       *  - 'bio'
2670       *  - 'email'
2671       *  - 'nickname'
2672       *  - 'nicename'
2673       *  - 'url'
2674       *  - 'display_name'
2675       *  - 'roles'
2676       */
2677  	public function wp_getUser( $args ) {
2678          if ( ! $this->minimum_args( $args, 4 ) ) {
2679              return $this->error;
2680          }
2681  
2682          $this->escape( $args );
2683  
2684          $username = $args[1];
2685          $password = $args[2];
2686          $user_id  = (int) $args[3];
2687  
2688          if ( isset( $args[4] ) ) {
2689              $fields = $args[4];
2690          } else {
2691              /**
2692               * Filters the default user query fields used by the given XML-RPC method.
2693               *
2694               * @since 3.5.0
2695               *
2696               * @param array  $fields An array of user fields to retrieve. By default, contains 'all'.
2697               * @param string $method The method name.
2698               */
2699              $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUser' );
2700          }
2701  
2702          $user = $this->login( $username, $password );
2703          if ( ! $user ) {
2704              return $this->error;
2705          }
2706  
2707          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2708          do_action( 'xmlrpc_call', 'wp.getUser', $args, $this );
2709  
2710          if ( ! current_user_can( 'edit_user', $user_id ) ) {
2711              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this user.' ) );
2712          }
2713  
2714          $user_data = get_userdata( $user_id );
2715  
2716          if ( ! $user_data ) {
2717              return new IXR_Error( 404, __( 'Invalid user ID.' ) );
2718          }
2719  
2720          return $this->_prepare_user( $user_data, $fields );
2721      }
2722  
2723      /**
2724       * Retrieves users.
2725       *
2726       * The optional $filter parameter modifies the query used to retrieve users.
2727       * Accepted keys are 'number' (default: 50), 'offset' (default: 0), 'role',
2728       * 'who', 'orderby', and 'order'.
2729       *
2730       * The optional $fields parameter specifies what fields will be included
2731       * in the response array.
2732       *
2733       * @uses get_users()
2734       * @see wp_getUser() for more on $fields and return values
2735       *
2736       * @param array $args {
2737       *     Method arguments. Note: arguments must be ordered as documented.
2738       *
2739       *     @type int    $0 Blog ID (unused).
2740       *     @type string $1 Username.
2741       *     @type string $2 Password.
2742       *     @type array  $3 Optional. Arguments for the user query.
2743       *     @type array  $4 Optional. Fields to return.
2744       * }
2745       * @return array|IXR_Error users data
2746       */
2747  	public function wp_getUsers( $args ) {
2748          if ( ! $this->minimum_args( $args, 3 ) ) {
2749              return $this->error;
2750          }
2751  
2752          $this->escape( $args );
2753  
2754          $username = $args[1];
2755          $password = $args[2];
2756          $filter   = isset( $args[3] ) ? $args[3] : array();
2757  
2758          if ( isset( $args[4] ) ) {
2759              $fields = $args[4];
2760          } else {
2761              /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2762              $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUsers' );
2763          }
2764  
2765          $user = $this->login( $username, $password );
2766          if ( ! $user ) {
2767              return $this->error;
2768          }
2769  
2770          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2771          do_action( 'xmlrpc_call', 'wp.getUsers', $args, $this );
2772  
2773          if ( ! current_user_can( 'list_users' ) ) {
2774              return new IXR_Error( 401, __( 'Sorry, you are not allowed to list users.' ) );
2775          }
2776  
2777          $query = array( 'fields' => 'all_with_meta' );
2778  
2779          $query['number'] = ( isset( $filter['number'] ) ) ? absint( $filter['number'] ) : 50;
2780          $query['offset'] = ( isset( $filter['offset'] ) ) ? absint( $filter['offset'] ) : 0;
2781  
2782          if ( isset( $filter['orderby'] ) ) {
2783              $query['orderby'] = $filter['orderby'];
2784  
2785              if ( isset( $filter['order'] ) ) {
2786                  $query['order'] = $filter['order'];
2787              }
2788          }
2789  
2790          if ( isset( $filter['role'] ) ) {
2791              if ( get_role( $filter['role'] ) === null ) {
2792                  return new IXR_Error( 403, __( 'Invalid role.' ) );
2793              }
2794  
2795              $query['role'] = $filter['role'];
2796          }
2797  
2798          if ( isset( $filter['who'] ) ) {
2799              $query['who'] = $filter['who'];
2800          }
2801  
2802          $users = get_users( $query );
2803  
2804          $_users = array();
2805          foreach ( $users as $user_data ) {
2806              if ( current_user_can( 'edit_user', $user_data->ID ) ) {
2807                  $_users[] = $this->_prepare_user( $user_data, $fields );
2808              }
2809          }
2810          return $_users;
2811      }
2812  
2813      /**
2814       * Retrieves information about the requesting user.
2815       *
2816       * @uses get_userdata()
2817       *
2818       * @param array $args {
2819       *     Method arguments. Note: arguments must be ordered as documented.
2820       *
2821       *     @type int    $0 Blog ID (unused).
2822       *     @type string $1 Username
2823       *     @type string $2 Password
2824       *     @type array  $3 Optional. Fields to return.
2825       * }
2826       * @return array|IXR_Error (@see wp_getUser)
2827       */
2828  	public function wp_getProfile( $args ) {
2829          if ( ! $this->minimum_args( $args, 3 ) ) {
2830              return $this->error;
2831          }
2832  
2833          $this->escape( $args );
2834  
2835          $username = $args[1];
2836          $password = $args[2];
2837  
2838          if ( isset( $args[3] ) ) {
2839              $fields = $args[3];
2840          } else {
2841              /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2842              $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getProfile' );
2843          }
2844  
2845          $user = $this->login( $username, $password );
2846          if ( ! $user ) {
2847              return $this->error;
2848          }
2849  
2850          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2851          do_action( 'xmlrpc_call', 'wp.getProfile', $args, $this );
2852  
2853          if ( ! current_user_can( 'edit_user', $user->ID ) ) {
2854              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit your profile.' ) );
2855          }
2856  
2857          $user_data = get_userdata( $user->ID );
2858  
2859          return $this->_prepare_user( $user_data, $fields );
2860      }
2861  
2862      /**
2863       * Edits user's profile.
2864       *
2865       * @uses wp_update_user()
2866       *
2867       * @param array $args {
2868       *     Method arguments. Note: arguments must be ordered as documented.
2869       *
2870       *     @type int    $0 Blog ID (unused).
2871       *     @type string $1 Username.
2872       *     @type string $2 Password.
2873       *     @type array  $3 Content struct. It can optionally contain:
2874       *      - 'first_name'
2875       *      - 'last_name'
2876       *      - 'website'
2877       *      - 'display_name'
2878       *      - 'nickname'
2879       *      - 'nicename'
2880       *      - 'bio'
2881       * }
2882       * @return true|IXR_Error True, on success.
2883       */
2884  	public function wp_editProfile( $args ) {
2885          if ( ! $this->minimum_args( $args, 4 ) ) {
2886              return $this->error;
2887          }
2888  
2889          $this->escape( $args );
2890  
2891          $username       = $args[1];
2892          $password       = $args[2];
2893          $content_struct = $args[3];
2894  
2895          $user = $this->login( $username, $password );
2896          if ( ! $user ) {
2897              return $this->error;
2898          }
2899  
2900          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2901          do_action( 'xmlrpc_call', 'wp.editProfile', $args, $this );
2902  
2903          if ( ! current_user_can( 'edit_user', $user->ID ) ) {
2904              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit your profile.' ) );
2905          }
2906  
2907          // Holds data of the user.
2908          $user_data       = array();
2909          $user_data['ID'] = $user->ID;
2910  
2911          // Only set the user details if they were given.
2912          if ( isset( $content_struct['first_name'] ) ) {
2913              $user_data['first_name'] = $content_struct['first_name'];
2914          }
2915  
2916          if ( isset( $content_struct['last_name'] ) ) {
2917              $user_data['last_name'] = $content_struct['last_name'];
2918          }
2919  
2920          if ( isset( $content_struct['url'] ) ) {
2921              $user_data['user_url'] = $content_struct['url'];
2922          }
2923  
2924          if ( isset( $content_struct['display_name'] ) ) {
2925              $user_data['display_name'] = $content_struct['display_name'];
2926          }
2927  
2928          if ( isset( $content_struct['nickname'] ) ) {
2929              $user_data['nickname'] = $content_struct['nickname'];
2930          }
2931  
2932          if ( isset( $content_struct['nicename'] ) ) {
2933              $user_data['user_nicename'] = $content_struct['nicename'];
2934          }
2935  
2936          if ( isset( $content_struct['bio'] ) ) {
2937              $user_data['description'] = $content_struct['bio'];
2938          }
2939  
2940          $result = wp_update_user( $user_data );
2941  
2942          if ( is_wp_error( $result ) ) {
2943              return new IXR_Error( 500, $result->get_error_message() );
2944          }
2945  
2946          if ( ! $result ) {
2947              return new IXR_Error( 500, __( 'Sorry, the user could not be updated.' ) );
2948          }
2949  
2950          return true;
2951      }
2952  
2953      /**
2954       * Retrieves a page.
2955       *
2956       * @since 2.2.0
2957       *
2958       * @param array $args {
2959       *     Method arguments. Note: arguments must be ordered as documented.
2960       *
2961       *     @type int    $0 Blog ID (unused).
2962       *     @type int    $1 Page ID.
2963       *     @type string $2 Username.
2964       *     @type string $3 Password.
2965       * }
2966       * @return array|IXR_Error
2967       */
2968  	public function wp_getPage( $args ) {
2969          $this->escape( $args );
2970  
2971          $page_id  = (int) $args[1];
2972          $username = $args[2];
2973          $password = $args[3];
2974  
2975          $user = $this->login( $username, $password );
2976          if ( ! $user ) {
2977              return $this->error;
2978          }
2979  
2980          $page = get_post( $page_id );
2981          if ( ! $page ) {
2982              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
2983          }
2984  
2985          if ( ! current_user_can( 'edit_page', $page_id ) ) {
2986              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this page.' ) );
2987          }
2988  
2989          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2990          do_action( 'xmlrpc_call', 'wp.getPage', $args, $this );
2991  
2992          // If we found the page then format the data.
2993          if ( $page->ID && ( 'page' === $page->post_type ) ) {
2994              return $this->_prepare_page( $page );
2995          } else {
2996              // If the page doesn't exist, indicate that.
2997              return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
2998          }
2999      }
3000  
3001      /**
3002       * Retrieves Pages.
3003       *
3004       * @since 2.2.0
3005       *
3006       * @param array $args {
3007       *     Method arguments. Note: arguments must be ordered as documented.
3008       *
3009       *     @type int    $0 Blog ID (unused).
3010       *     @type string $1 Username.
3011       *     @type string $2 Password.
3012       *     @type int    $3 Optional. Number of pages. Default 10.
3013       * }
3014       * @return array|IXR_Error
3015       */
3016  	public function wp_getPages( $args ) {
3017          $this->escape( $args );
3018  
3019          $username  = $args[1];
3020          $password  = $args[2];
3021          $num_pages = isset( $args[3] ) ? (int) $args[3] : 10;
3022  
3023          $user = $this->login( $username, $password );
3024          if ( ! $user ) {
3025              return $this->error;
3026          }
3027  
3028          if ( ! current_user_can( 'edit_pages' ) ) {
3029              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit pages.' ) );
3030          }
3031  
3032          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3033          do_action( 'xmlrpc_call', 'wp.getPages', $args, $this );
3034  
3035          $pages     = get_posts(
3036              array(
3037                  'post_type'   => 'page',
3038                  'post_status' => 'any',
3039                  'numberposts' => $num_pages,
3040              )
3041          );
3042          $num_pages = count( $pages );
3043  
3044          // If we have pages, put together their info.
3045          if ( $num_pages >= 1 ) {
3046              $pages_struct = array();
3047  
3048              foreach ( $pages as $page ) {
3049                  if ( current_user_can( 'edit_page', $page->ID ) ) {
3050                      $pages_struct[] = $this->_prepare_page( $page );
3051                  }
3052              }
3053  
3054              return $pages_struct;
3055          }
3056  
3057          return array();
3058      }
3059  
3060      /**
3061       * Creates a new page.
3062       *
3063       * @since 2.2.0
3064       *
3065       * @see wp_xmlrpc_server::mw_newPost()
3066       *
3067       * @param array $args {
3068       *     Method arguments. Note: arguments must be ordered as documented.
3069       *
3070       *     @type int    $0 Blog ID (unused).
3071       *     @type string $1 Username.
3072       *     @type string $2 Password.
3073       *     @type array  $3 Content struct.
3074       * }
3075       * @return int|IXR_Error
3076       */
3077  	public function wp_newPage( $args ) {
3078          // Items not escaped here will be escaped in wp_newPost().
3079          $username = $this->escape( $args[1] );
3080          $password = $this->escape( $args[2] );
3081  
3082          $user = $this->login( $username, $password );
3083          if ( ! $user ) {
3084              return $this->error;
3085          }
3086  
3087          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3088          do_action( 'xmlrpc_call', 'wp.newPage', $args, $this );
3089  
3090          // Mark this as content for a page.
3091          $args[3]['post_type'] = 'page';
3092  
3093          // Let mw_newPost() do all of the heavy lifting.
3094          return $this->mw_newPost( $args );
3095      }
3096  
3097      /**
3098       * Deletes a page.
3099       *
3100       * @since 2.2.0
3101       *
3102       * @param array $args {
3103       *     Method arguments. Note: arguments must be ordered as documented.
3104       *
3105       *     @type int    $0 Blog ID (unused).
3106       *     @type string $1 Username.
3107       *     @type string $2 Password.
3108       *     @type int    $3 Page ID.
3109       * }
3110       * @return true|IXR_Error True, if success.
3111       */
3112  	public function wp_deletePage( $args ) {
3113          $this->escape( $args );
3114  
3115          $username = $args[1];
3116          $password = $args[2];
3117          $page_id  = (int) $args[3];
3118  
3119          $user = $this->login( $username, $password );
3120          if ( ! $user ) {
3121              return $this->error;
3122          }
3123  
3124          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3125          do_action( 'xmlrpc_call', 'wp.deletePage', $args, $this );
3126  
3127          /*
3128           * Get the current page based on the 'page_id' and
3129           * make sure it is a page and not a post.
3130           */
3131          $actual_page = get_post( $page_id, ARRAY_A );
3132          if ( ! $actual_page || ( 'page' !== $actual_page['post_type'] ) ) {
3133              return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
3134          }
3135  
3136          // Make sure the user can delete pages.
3137          if ( ! current_user_can( 'delete_page', $page_id ) ) {
3138              return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this page.' ) );
3139          }
3140  
3141          // Attempt to delete the page.
3142          $result = wp_delete_post( $page_id );
3143          if ( ! $result ) {
3144              return new IXR_Error( 500, __( 'Failed to delete the page.' ) );
3145          }
3146  
3147          /**
3148           * Fires after a page has been successfully deleted via XML-RPC.
3149           *
3150           * @since 3.4.0
3151           *
3152           * @param int   $page_id ID of the deleted page.
3153           * @param array $args    An array of arguments to delete the page.
3154           */
3155          do_action( 'xmlrpc_call_success_wp_deletePage', $page_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
3156  
3157          return true;
3158      }
3159  
3160      /**
3161       * Edits a page.
3162       *
3163       * @since 2.2.0
3164       *
3165       * @param array $args {
3166       *     Method arguments. Note: arguments must be ordered as documented.
3167       *
3168       *     @type int    $0 Blog ID (unused).
3169       *     @type int    $1 Page ID.
3170       *     @type string $2 Username.
3171       *     @type string $3 Password.
3172       *     @type string $4 Content.
3173       *     @type int    $5 Publish flag. 0 for draft, 1 for publish.
3174       * }
3175       * @return array|IXR_Error
3176       */
3177  	public function wp_editPage( $args ) {
3178          // Items will be escaped in mw_editPost().
3179          $page_id  = (int) $args[1];
3180          $username = $args[2];
3181          $password = $args[3];
3182          $content  = $args[4];
3183          $publish  = $args[5];
3184  
3185          $escaped_username = $this->escape( $username );
3186          $escaped_password = $this->escape( $password );
3187  
3188          $user = $this->login( $escaped_username, $escaped_password );
3189          if ( ! $user ) {
3190              return $this->error;
3191          }
3192  
3193          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3194          do_action( 'xmlrpc_call', 'wp.editPage', $args, $this );
3195  
3196          // Get the page data and make sure it is a page.
3197          $actual_page = get_post( $page_id, ARRAY_A );
3198          if ( ! $actual_page || ( 'page' !== $actual_page['post_type'] ) ) {
3199              return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
3200          }
3201  
3202          // Make sure the user is allowed to edit pages.
3203          if ( ! current_user_can( 'edit_page', $page_id ) ) {
3204              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this page.' ) );
3205          }
3206  
3207          // Mark this as content for a page.
3208          $content['post_type'] = 'page';
3209  
3210          // Arrange args in the way mw_editPost() understands.
3211          $args = array(
3212              $page_id,
3213              $username,
3214              $password,
3215              $content,
3216              $publish,
3217          );
3218  
3219          // Let mw_editPost() do all of the heavy lifting.
3220          return $this->mw_editPost( $args );
3221      }
3222  
3223      /**
3224       * Retrieves page list.
3225       *
3226       * @since 2.2.0
3227       *
3228       * @global wpdb $wpdb WordPress database abstraction object.
3229       *
3230       * @param array $args {
3231       *     Method arguments. Note: arguments must be ordered as documented.
3232       *
3233       *     @type int    $0 Blog ID (unused).
3234       *     @type string $1 Username.
3235       *     @type string $2 Password.
3236       * }
3237       * @return array|IXR_Error
3238       */
3239  	public function wp_getPageList( $args ) {
3240          global $wpdb;
3241  
3242          $this->escape( $args );
3243  
3244          $username = $args[1];
3245          $password = $args[2];
3246  
3247          $user = $this->login( $username, $password );
3248          if ( ! $user ) {
3249              return $this->error;
3250          }
3251  
3252          if ( ! current_user_can( 'edit_pages' ) ) {
3253              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit pages.' ) );
3254          }
3255  
3256          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3257          do_action( 'xmlrpc_call', 'wp.getPageList', $args, $this );
3258  
3259          // Get list of page IDs and titles.
3260          $page_list = $wpdb->get_results(
3261              "
3262              SELECT ID page_id,
3263                  post_title page_title,
3264                  post_parent page_parent_id,
3265                  post_date_gmt,
3266                  post_date,
3267                  post_status
3268              FROM {$wpdb->posts}
3269              WHERE post_type = 'page'
3270              ORDER BY ID
3271          "
3272          );
3273  
3274          // The date needs to be formatted properly.
3275          $num_pages = count( $page_list );
3276          for ( $i = 0; $i < $num_pages; $i++ ) {
3277              $page_list[ $i ]->dateCreated      = $this->_convert_date( $page_list[ $i ]->post_date );
3278              $page_list[ $i ]->date_created_gmt = $this->_convert_date_gmt( $page_list[ $i ]->post_date_gmt, $page_list[ $i ]->post_date );
3279  
3280              unset( $page_list[ $i ]->post_date_gmt );
3281              unset( $page_list[ $i ]->post_date );
3282              unset( $page_list[ $i ]->post_status );
3283          }
3284  
3285          return $page_list;
3286      }
3287  
3288      /**
3289       * Retrieves authors list.
3290       *
3291       * @since 2.2.0
3292       *
3293       * @param array $args {
3294       *     Method arguments. Note: arguments must be ordered as documented.
3295       *
3296       *     @type int    $0 Blog ID (unused).
3297       *     @type string $1 Username.
3298       *     @type string $2 Password.
3299       * }
3300       * @return array|IXR_Error
3301       */
3302  	public function wp_getAuthors( $args ) {
3303          $this->escape( $args );
3304  
3305          $username = $args[1];
3306          $password = $args[2];
3307  
3308          $user = $this->login( $username, $password );
3309          if ( ! $user ) {
3310              return $this->error;
3311          }
3312  
3313          if ( ! current_user_can( 'edit_posts' ) ) {
3314              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
3315          }
3316  
3317          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3318          do_action( 'xmlrpc_call', 'wp.getAuthors', $args, $this );
3319  
3320          $authors = array();
3321          foreach ( get_users( array( 'fields' => array( 'ID', 'user_login', 'display_name' ) ) ) as $user ) {
3322              $authors[] = array(
3323                  'user_id'      => $user->ID,
3324                  'user_login'   => $user->user_login,
3325                  'display_name' => $user->display_name,
3326              );
3327          }
3328  
3329          return $authors;
3330      }
3331  
3332      /**
3333       * Gets the list of all tags.
3334       *
3335       * @since 2.7.0
3336       *
3337       * @param array $args {
3338       *     Method arguments. Note: arguments must be ordered as documented.
3339       *
3340       *     @type int    $0 Blog ID (unused).
3341       *     @type string $1 Username.
3342       *     @type string $2 Password.
3343       * }
3344       * @return array|IXR_Error
3345       */
3346  	public function wp_getTags( $args ) {
3347          $this->escape( $args );
3348  
3349          $username = $args[1];
3350          $password = $args[2];
3351  
3352          $user = $this->login( $username, $password );
3353          if ( ! $user ) {
3354              return $this->error;
3355          }
3356  
3357          if ( ! current_user_can( 'edit_posts' ) ) {
3358              return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view tags.' ) );
3359          }
3360  
3361          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3362          do_action( 'xmlrpc_call', 'wp.getKeywords', $args, $this );
3363  
3364          $tags = array();
3365  
3366          $all_tags = get_tags();
3367          if ( $all_tags ) {
3368              foreach ( (array) $all_tags as $tag ) {
3369                  $struct             = array();
3370                  $struct['tag_id']   = $tag->term_id;
3371                  $struct['name']     = $tag->name;
3372                  $struct['count']    = $tag->count;
3373                  $struct['slug']     = $tag->slug;
3374                  $struct['html_url'] = esc_html( get_tag_link( $tag->term_id ) );
3375                  $struct['rss_url']  = esc_html( get_tag_feed_link( $tag->term_id ) );
3376  
3377                  $tags[] = $struct;
3378              }
3379          }
3380  
3381          return $tags;
3382      }
3383  
3384      /**
3385       * Creates a new category.
3386       *
3387       * @since 2.2.0
3388       *
3389       * @param array $args {
3390       *     Method arguments. Note: arguments must be ordered as documented.
3391       *
3392       *     @type int    $0 Blog ID (unused).
3393       *     @type string $1 Username.
3394       *     @type string $2 Password.
3395       *     @type array  $3 Category.
3396       * }
3397       * @return int|IXR_Error Category ID.
3398       */
3399  	public function wp_newCategory( $args ) {
3400          $this->escape( $args );
3401  
3402          $username = $args[1];
3403          $password = $args[2];
3404          $category = $args[3];
3405  
3406          $user = $this->login( $username, $password );
3407          if ( ! $user ) {
3408              return $this->error;
3409          }
3410  
3411          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3412          do_action( 'xmlrpc_call', 'wp.newCategory', $args, $this );
3413  
3414          // Make sure the user is allowed to add a category.
3415          if ( ! current_user_can( 'manage_categories' ) ) {
3416              return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a category.' ) );
3417          }
3418  
3419          /*
3420           * If no slug was provided, make it empty
3421           * so that WordPress will generate one.
3422           */
3423          if ( empty( $category['slug'] ) ) {
3424              $category['slug'] = '';
3425          }
3426  
3427          /*
3428           * If no parent_id was provided, make it empty
3429           * so that it will be a top-level page (no parent).
3430           */
3431          if ( ! isset( $category['parent_id'] ) ) {
3432              $category['parent_id'] = '';
3433          }
3434  
3435          // If no description was provided, make it empty.
3436          if ( empty( $category['description'] ) ) {
3437              $category['description'] = '';
3438          }
3439  
3440          $new_category = array(
3441              'cat_name'             => $category['name'],
3442              'category_nicename'    => $category['slug'],
3443              'category_parent'      => $category['parent_id'],
3444              'category_description' => $category['description'],
3445          );
3446  
3447          $cat_id = wp_insert_category( $new_category, true );
3448          if ( is_wp_error( $cat_id ) ) {
3449              if ( 'term_exists' === $cat_id->get_error_code() ) {
3450                  return (int) $cat_id->get_error_data();
3451              } else {
3452                  return new IXR_Error( 500, __( 'Sorry, the category could not be created.' ) );
3453              }
3454          } elseif ( ! $cat_id ) {
3455              return new IXR_Error( 500, __( 'Sorry, the category could not be created.' ) );
3456          }
3457  
3458          /**
3459           * Fires after a new category has been successfully created via XML-RPC.
3460           *
3461           * @since 3.4.0
3462           *
3463           * @param int   $cat_id ID of the new category.
3464           * @param array $args   An array of new category arguments.
3465           */
3466          do_action( 'xmlrpc_call_success_wp_newCategory', $cat_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
3467  
3468          return $cat_id;
3469      }
3470  
3471      /**
3472       * Deletes a category.
3473       *
3474       * @since 2.5.0
3475       *
3476       * @param array $args {
3477       *     Method arguments. Note: arguments must be ordered as documented.
3478       *
3479       *     @type int    $0 Blog ID (unused).
3480       *     @type string $1 Username.
3481       *     @type string $2 Password.
3482       *     @type int    $3 Category ID.
3483       * }
3484       * @return bool|IXR_Error See wp_delete_term() for return info.
3485       */
3486  	public function wp_deleteCategory( $args ) {
3487          $this->escape( $args );
3488  
3489          $username    = $args[1];
3490          $password    = $args[2];
3491          $category_id = (int) $args[3];
3492  
3493          $user = $this->login( $username, $password );
3494          if ( ! $user ) {
3495              return $this->error;
3496          }
3497  
3498          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3499          do_action( 'xmlrpc_call', 'wp.deleteCategory', $args, $this );
3500  
3501          if ( ! current_user_can( 'delete_term', $category_id ) ) {
3502              return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this category.' ) );
3503          }
3504  
3505          $status = wp_delete_term( $category_id, 'category' );
3506  
3507          if ( true == $status ) {
3508              /**
3509               * Fires after a category has been successfully deleted via XML-RPC.
3510               *
3511               * @since 3.4.0
3512               *
3513               * @param int   $category_id ID of the deleted category.
3514               * @param array $args        An array of arguments to delete the category.
3515               */
3516              do_action( 'xmlrpc_call_success_wp_deleteCategory', $category_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
3517          }
3518  
3519          return $status;
3520      }
3521  
3522      /**
3523       * Retrieves category list.
3524       *
3525       * @since 2.2.0
3526       *
3527       * @param array $args {
3528       *     Method arguments. Note: arguments must be ordered as documented.
3529       *
3530       *     @type int    $0 Blog ID (unused).
3531       *     @type string $1 Username.
3532       *     @type string $2 Password.
3533       *     @type array  $3 Category
3534       *     @type int    $4 Max number of results.
3535       * }
3536       * @return array|IXR_Error
3537       */
3538  	public function wp_suggestCategories( $args ) {
3539          $this->escape( $args );
3540  
3541          $username    = $args[1];
3542          $password    = $args[2];
3543          $category    = $args[3];
3544          $max_results = (int) $args[4];
3545  
3546          $user = $this->login( $username, $password );
3547          if ( ! $user ) {
3548              return $this->error;
3549          }
3550  
3551          if ( ! current_user_can( 'edit_posts' ) ) {
3552              return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
3553          }
3554  
3555          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3556          do_action( 'xmlrpc_call', 'wp.suggestCategories', $args, $this );
3557  
3558          $category_suggestions = array();
3559          $args                 = array(
3560              'get'        => 'all',
3561              'number'     => $max_results,
3562              'name__like' => $category,
3563          );
3564          foreach ( (array) get_categories( $args ) as $cat ) {
3565              $category_suggestions[] = array(
3566                  'category_id'   => $cat->term_id,
3567                  'category_name' => $cat->name,
3568              );
3569          }
3570  
3571          return $category_suggestions;
3572      }
3573  
3574      /**
3575       * Retrieves a comment.
3576       *
3577       * @since 2.7.0
3578       *
3579       * @param array $args {
3580       *     Method arguments. Note: arguments must be ordered as documented.
3581       *
3582       *     @type int    $0 Blog ID (unused).
3583       *     @type string $1 Username.
3584       *     @type string $2 Password.
3585       *     @type int    $3 Comment ID.
3586       * }
3587       * @return array|IXR_Error
3588       */
3589  	public function wp_getComment( $args ) {
3590          $this->escape( $args );
3591  
3592          $username   = $args[1];
3593          $password   = $args[2];
3594          $comment_id = (int) $args[3];
3595  
3596          $user = $this->login( $username, $password );
3597          if ( ! $user ) {
3598              return $this->error;
3599          }
3600  
3601          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3602          do_action( 'xmlrpc_call', 'wp.getComment', $args, $this );
3603  
3604          $comment = get_comment( $comment_id );
3605          if ( ! $comment ) {
3606              return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
3607          }
3608  
3609          if ( ! current_user_can( 'edit_comment', $comment_id ) ) {
3610              return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) );
3611          }
3612  
3613          return $this->_prepare_comment( $comment );
3614      }
3615  
3616      /**
3617       * Retrieves comments.
3618       *
3619       * Besides the common blog_id (unused), username, and password arguments,
3620       * it takes a filter array as the last argument.
3621       *
3622       * Accepted 'filter' keys are 'status', 'post_id', 'offset', and 'number'.
3623       *
3624       * The defaults are as follows:
3625       * - 'status'  - Default is ''. Filter by status (e.g., 'approve', 'hold')
3626       * - 'post_id' - Default is ''. The post where the comment is posted.
3627       *               Empty string shows all comments.
3628       * - 'number'  - Default is 10. Total number of media items to retrieve.
3629       * - 'offset'  - Default is 0. See WP_Query::query() for more.
3630       *
3631       * @since 2.7.0
3632       *
3633       * @param array $args {
3634       *     Method arguments. Note: arguments must be ordered as documented.
3635       *
3636       *     @type int    $0 Blog ID (unused).
3637       *     @type string $1 Username.
3638       *     @type string $2 Password.
3639       *     @type array  $3 Optional. Query arguments.
3640       * }
3641       * @return array|IXR_Error Array containing a collection of comments.
3642       *                         See wp_xmlrpc_server::wp_getComment() for a description
3643       *                         of each item contents.
3644       */
3645  	public function wp_getComments( $args ) {
3646          $this->escape( $args );
3647  
3648          $username = $args[1];
3649          $password = $args[2];
3650          $struct   = isset( $args[3] ) ? $args[3] : array();
3651  
3652          $user = $this->login( $username, $password );
3653          if ( ! $user ) {
3654              return $this->error;
3655          }
3656  
3657          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3658          do_action( 'xmlrpc_call', 'wp.getComments', $args, $this );
3659  
3660          if ( isset( $struct['status'] ) ) {
3661              $status = $struct['status'];
3662          } else {
3663              $status = '';
3664          }
3665  
3666          if ( ! current_user_can( 'moderate_comments' ) && 'approve' !== $status ) {
3667              return new IXR_Error( 401, __( 'Invalid comment status.' ) );
3668          }
3669  
3670          $post_id = '';
3671          if ( isset( $struct['post_id'] ) ) {
3672              $post_id = absint( $struct['post_id'] );
3673          }
3674  
3675          $post_type = '';
3676          if ( isset( $struct['post_type'] ) ) {
3677              $post_type_object = get_post_type_object( $struct['post_type'] );
3678              if ( ! $post_type_object || ! post_type_supports( $post_type_object->name, 'comments' ) ) {
3679                  return new IXR_Error( 404, __( 'Invalid post type.' ) );
3680              }
3681              $post_type = $struct['post_type'];
3682          }
3683  
3684          $offset = 0;
3685          if ( isset( $struct['offset'] ) ) {
3686              $offset = absint( $struct['offset'] );
3687          }
3688  
3689          $number = 10;
3690          if ( isset( $struct['number'] ) ) {
3691              $number = absint( $struct['number'] );
3692          }
3693  
3694          $comments = get_comments(
3695              array(
3696                  'status'    => $status,
3697                  'post_id'   => $post_id,
3698                  'offset'    => $offset,
3699                  'number'    => $number,
3700                  'post_type' => $post_type,
3701              )
3702          );
3703  
3704          $comments_struct = array();
3705          if ( is_array( $comments ) ) {
3706              foreach ( $comments as $comment ) {
3707                  $comments_struct[] = $this->_prepare_comment( $comment );
3708              }
3709          }
3710  
3711          return $comments_struct;
3712      }
3713  
3714      /**
3715       * Deletes a comment.
3716       *
3717       * By default, the comment will be moved to the Trash instead of deleted.
3718       * See wp_delete_comment() for more information on this behavior.
3719       *
3720       * @since 2.7.0
3721       *
3722       * @param array $args {
3723       *     Method arguments. Note: arguments must be ordered as documented.
3724       *
3725       *     @type int    $0 Blog ID (unused).
3726       *     @type string $1 Username.
3727       *     @type string $2 Password.
3728       *     @type int    $3 Comment ID.
3729       * }
3730       * @return bool|IXR_Error See wp_delete_comment().
3731       */
3732  	public function wp_deleteComment( $args ) {
3733          $this->escape( $args );
3734  
3735          $username   = $args[1];
3736          $password   = $args[2];
3737          $comment_id = (int) $args[3];
3738  
3739          $user = $this->login( $username, $password );
3740          if ( ! $user ) {
3741              return $this->error;
3742          }
3743  
3744          if ( ! get_comment( $comment_id ) ) {
3745              return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
3746          }
3747  
3748          if ( ! current_user_can( 'edit_comment', $comment_id ) ) {
3749              return new IXR_Error( 403, __( 'Sorry, you are not allowed to delete this comment.' ) );
3750          }
3751  
3752          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3753          do_action( 'xmlrpc_call', 'wp.deleteComment', $args, $this );
3754  
3755          $status = wp_delete_comment( $comment_id );
3756  
3757          if ( $status ) {
3758              /**
3759               * Fires after a comment has been successfully deleted via XML-RPC.
3760               *
3761               * @since 3.4.0
3762               *
3763               * @param int   $comment_id ID of the deleted comment.
3764               * @param array $args       An array of arguments to delete the comment.
3765               */
3766              do_action( 'xmlrpc_call_success_wp_deleteComment', $comment_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
3767          }
3768  
3769          return $status;
3770      }
3771  
3772      /**
3773       * Edits a comment.
3774       *
3775       * Besides the common blog_id (unused), username, and password arguments,
3776       * it takes a comment_id integer and a content_struct array as the last argument.
3777       *
3778       * The allowed keys in the content_struct array are:
3779       *  - 'author'
3780       *  - 'author_url'
3781       *  - 'author_email'
3782       *  - 'content'
3783       *  - 'date_created_gmt'
3784       *  - 'status'. Common statuses are 'approve', 'hold', 'spam'. See get_comment_statuses() for more details.
3785       *
3786       * @since 2.7.0
3787       *
3788       * @param array $args {
3789       *     Method arguments. Note: arguments must be ordered as documented.
3790       *
3791       *     @type int    $0 Blog ID (unused).
3792       *     @type string $1 Username.
3793       *     @type string $2 Password.
3794       *     @type int    $3 Comment ID.
3795       *     @type array  $4 Content structure.
3796       * }
3797       * @return true|IXR_Error True, on success.
3798       */
3799  	public function wp_editComment( $args ) {
3800          $this->escape( $args );
3801  
3802          $username       = $args[1];
3803          $password       = $args[2];
3804          $comment_id     = (int) $args[3];
3805          $content_struct = $args[4];
3806  
3807          $user = $this->login( $username, $password );
3808          if ( ! $user ) {
3809              return $this->error;
3810          }
3811  
3812          if ( ! get_comment( $comment_id ) ) {
3813              return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
3814          }
3815  
3816          if ( ! current_user_can( 'edit_comment', $comment_id ) ) {
3817              return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) );
3818          }
3819  
3820          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3821          do_action( 'xmlrpc_call', 'wp.editComment', $args, $this );
3822          $comment = array(
3823              'comment_ID' => $comment_id,
3824          );
3825  
3826          if ( isset( $content_struct['status'] ) ) {
3827              $statuses = get_comment_statuses();
3828              $statuses = array_keys( $statuses );
3829  
3830              if ( ! in_array( $content_struct['status'], $statuses, true ) ) {
3831                  return new IXR_Error( 401, __( 'Invalid comment status.' ) );
3832              }
3833  
3834              $comment['comment_approved'] = $content_struct['status'];
3835          }
3836  
3837          // Do some timestamp voodoo.
3838          if ( ! empty( $content_struct['date_created_gmt'] ) ) {
3839              // We know this is supposed to be GMT, so we're going to slap that Z on there by force.
3840              $dateCreated                 = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
3841              $comment['comment_date']     = get_date_from_gmt( $dateCreated );
3842              $comment['comment_date_gmt'] = iso8601_to_datetime( $dateCreated, 'gmt' );
3843          }
3844  
3845          if ( isset( $content_struct['content'] ) ) {
3846              $comment['comment_content'] = $content_struct['content'];
3847          }
3848  
3849          if ( isset( $content_struct['author'] ) ) {
3850              $comment['comment_author'] = $content_struct['author'];
3851          }
3852  
3853          if ( isset( $content_struct['author_url'] ) ) {
3854              $comment['comment_author_url'] = $content_struct['author_url'];
3855          }
3856  
3857          if ( isset( $content_struct['author_email'] ) ) {
3858              $comment['comment_author_email'] = $content_struct['author_email'];
3859          }
3860  
3861          $result = wp_update_comment( $comment, true );
3862          if ( is_wp_error( $result ) ) {
3863              return new IXR_Error( 500, $result->get_error_message() );
3864          }
3865  
3866          if ( ! $result ) {
3867              return new IXR_Error( 500, __( 'Sorry, the comment could not be updated.' ) );
3868          }
3869  
3870          /**
3871           * Fires after a comment has been successfully updated via XML-RPC.
3872           *
3873           * @since 3.4.0
3874           *
3875           * @param int   $comment_id ID of the updated comment.
3876           * @param array $args       An array of arguments to update the comment.
3877           */
3878          do_action( 'xmlrpc_call_success_wp_editComment', $comment_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
3879  
3880          return true;
3881      }
3882  
3883      /**
3884       * Creates a new comment.
3885       *
3886       * @since 2.7.0
3887       *
3888       * @param array $args {
3889       *     Method arguments. Note: arguments must be ordered as documented.
3890       *
3891       *     @type int        $0 Blog ID (unused).
3892       *     @type string     $1 Username.
3893       *     @type string     $2 Password.
3894       *     @type string|int $3 Post ID or URL.
3895       *     @type array      $4 Content structure.
3896       * }
3897       * @return int|IXR_Error See wp_new_comment().
3898       */
3899  	public function wp_newComment( $args ) {
3900          $this->escape( $args );
3901  
3902          $username       = $args[1];
3903          $password       = $args[2];
3904          $post           = $args[3];
3905          $content_struct = $args[4];
3906  
3907          /**
3908           * Filters whether to allow anonymous comments over XML-RPC.
3909           *
3910           * @since 2.7.0
3911           *
3912           * @param bool $allow Whether to allow anonymous commenting via XML-RPC.
3913           *                    Default false.
3914           */
3915          $allow_anon = apply_filters( 'xmlrpc_allow_anonymous_comments', false );
3916  
3917          $user = $this->login( $username, $password );
3918  
3919          if ( ! $user ) {
3920              $logged_in = false;
3921              if ( $allow_anon && get_option( 'comment_registration' ) ) {
3922                  return new IXR_Error( 403, __( 'Sorry, you must be logged in to comment.' ) );
3923              } elseif ( ! $allow_anon ) {
3924                  return $this->error;
3925              }
3926          } else {
3927              $logged_in = true;
3928          }
3929  
3930          if ( is_numeric( $post ) ) {
3931              $post_id = absint( $post );
3932          } else {
3933              $post_id = url_to_postid( $post );
3934          }
3935  
3936          if ( ! $post_id ) {
3937              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
3938          }
3939  
3940          if ( ! get_post( $post_id ) ) {
3941              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
3942          }
3943  
3944          if ( ! comments_open( $post_id ) ) {
3945              return new IXR_Error( 403, __( 'Sorry, comments are closed for this item.' ) );
3946          }
3947  
3948          if (
3949              'publish' === get_post_status( $post_id ) &&
3950              ! current_user_can( 'edit_post', $post_id ) &&
3951              post_password_required( $post_id )
3952          ) {
3953              return new IXR_Error( 403, __( 'Sorry, you are not allowed to comment on this post.' ) );
3954          }
3955  
3956          if (
3957              'private' === get_post_status( $post_id ) &&
3958              ! current_user_can( 'read_post', $post_id )
3959          ) {
3960              return new IXR_Error( 403, __( 'Sorry, you are not allowed to comment on this post.' ) );
3961          }
3962  
3963          $comment = array(
3964              'comment_post_ID' => $post_id,
3965              'comment_content' => trim( $content_struct['content'] ),
3966          );
3967  
3968          if ( $logged_in ) {
3969              $display_name = $user->display_name;
3970              $user_email   = $user->user_email;
3971              $user_url     = $user->user_url;
3972  
3973              $comment['comment_author']       = $this->escape( $display_name );
3974              $comment['comment_author_email'] = $this->escape( $user_email );
3975              $comment['comment_author_url']   = $this->escape( $user_url );
3976              $comment['user_id']              = $user->ID;
3977          } else {
3978              $comment['comment_author'] = '';
3979              if ( isset( $content_struct['author'] ) ) {
3980                  $comment['comment_author'] = $content_struct['author'];
3981              }
3982  
3983              $comment['comment_author_email'] = '';
3984              if ( isset( $content_struct['author_email'] ) ) {
3985                  $comment['comment_author_email'] = $content_struct['author_email'];
3986              }
3987  
3988              $comment['comment_author_url'] = '';
3989              if ( isset( $content_struct['author_url'] ) ) {
3990                  $comment['comment_author_url'] = $content_struct['author_url'];
3991              }
3992  
3993              $comment['user_id'] = 0;
3994  
3995              if ( get_option( 'require_name_email' ) ) {
3996                  if ( strlen( $comment['comment_author_email'] ) < 6 || '' === $comment['comment_author'] ) {
3997                      return new IXR_Error( 403, __( 'Comment author name and email are required.' ) );
3998                  } elseif ( ! is_email( $comment['comment_author_email'] ) ) {
3999                      return new IXR_Error( 403, __( 'A valid email address is required.' ) );
4000                  }
4001              }
4002          }
4003  
4004          $comment['comment_parent'] = isset( $content_struct['comment_parent'] ) ? absint( $content_struct['comment_parent'] ) : 0;
4005  
4006          /** This filter is documented in wp-includes/comment.php */
4007          $allow_empty = apply_filters( 'allow_empty_comment', false, $comment );
4008  
4009          if ( ! $allow_empty && '' === $comment['comment_content'] ) {
4010              return new IXR_Error( 403, __( 'Comment is required.' ) );
4011          }
4012  
4013          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4014          do_action( 'xmlrpc_call', 'wp.newComment', $args, $this );
4015  
4016          $comment_id = wp_new_comment( $comment, true );
4017          if ( is_wp_error( $comment_id ) ) {
4018              return new IXR_Error( 403, $comment_id->get_error_message() );
4019          }
4020  
4021          if ( ! $comment_id ) {
4022              return new IXR_Error( 403, __( 'Something went wrong.' ) );
4023          }
4024  
4025          /**
4026           * Fires after a new comment has been successfully created via XML-RPC.
4027           *
4028           * @since 3.4.0
4029           *
4030           * @param int   $comment_id ID of the new comment.
4031           * @param array $args       An array of new comment arguments.
4032           */
4033          do_action( 'xmlrpc_call_success_wp_newComment', $comment_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
4034  
4035          return $comment_id;
4036      }
4037  
4038      /**
4039       * Retrieves all of the comment status.
4040       *
4041       * @since 2.7.0
4042       *
4043       * @param array $args {
4044       *     Method arguments. Note: arguments must be ordered as documented.
4045       *
4046       *     @type int    $0 Blog ID (unused).
4047       *     @type string $1 Username.
4048       *     @type string $2 Password.
4049       * }
4050       * @return array|IXR_Error
4051       */
4052  	public function wp_getCommentStatusList( $args ) {
4053          $this->escape( $args );
4054  
4055          $username = $args[1];
4056          $password = $args[2];
4057  
4058          $user = $this->login( $username, $password );
4059          if ( ! $user ) {
4060              return $this->error;
4061          }
4062  
4063          if ( ! current_user_can( 'publish_posts' ) ) {
4064              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4065          }
4066  
4067          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4068          do_action( 'xmlrpc_call', 'wp.getCommentStatusList', $args, $this );
4069  
4070          return get_comment_statuses();
4071      }
4072  
4073      /**
4074       * Retrieves comment counts.
4075       *
4076       * @since 2.5.0
4077       *
4078       * @param array $args {
4079       *     Method arguments. Note: arguments must be ordered as documented.
4080       *
4081       *     @type int    $0 Blog ID (unused).
4082       *     @type string $1 Username.
4083       *     @type string $2 Password.
4084       *     @type int    $3 Post ID.
4085       * }
4086       * @return array|IXR_Error
4087       */
4088  	public function wp_getCommentCount( $args ) {
4089          $this->escape( $args );
4090  
4091          $username = $args[1];
4092          $password = $args[2];
4093          $post_id  = (int) $args[3];
4094  
4095          $user = $this->login( $username, $password );
4096          if ( ! $user ) {
4097              return $this->error;
4098          }
4099  
4100          $post = get_post( $post_id, ARRAY_A );
4101          if ( empty( $post['ID'] ) ) {
4102              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4103          }
4104  
4105          if ( ! current_user_can( 'edit_post', $post_id ) ) {
4106              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details of this post.' ) );
4107          }
4108  
4109          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4110          do_action( 'xmlrpc_call', 'wp.getCommentCount', $args, $this );
4111  
4112          $count = wp_count_comments( $post_id );
4113  
4114          return array(
4115              'approved'            => $count->approved,
4116              'awaiting_moderation' => $count->moderated,
4117              'spam'                => $count->spam,
4118              'total_comments'      => $count->total_comments,
4119          );
4120      }
4121  
4122      /**
4123       * Retrieves post statuses.
4124       *
4125       * @since 2.5.0
4126       *
4127       * @param array $args {
4128       *     Method arguments. Note: arguments must be ordered as documented.
4129       *
4130       *     @type int    $0 Blog ID (unused).
4131       *     @type string $1 Username.
4132       *     @type string $2 Password.
4133       * }
4134       * @return array|IXR_Error
4135       */
4136  	public function wp_getPostStatusList( $args ) {
4137          $this->escape( $args );
4138  
4139          $username = $args[1];
4140          $password = $args[2];
4141  
4142          $user = $this->login( $username, $password );
4143          if ( ! $user ) {
4144              return $this->error;
4145          }
4146  
4147          if ( ! current_user_can( 'edit_posts' ) ) {
4148              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4149          }
4150  
4151          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4152          do_action( 'xmlrpc_call', 'wp.getPostStatusList', $args, $this );
4153  
4154          return get_post_statuses();
4155      }
4156  
4157      /**
4158       * Retrieves page statuses.
4159       *
4160       * @since 2.5.0
4161       *
4162       * @param array $args {
4163       *     Method arguments. Note: arguments must be ordered as documented.
4164       *
4165       *     @type int    $0 Blog ID (unused).
4166       *     @type string $1 Username.
4167       *     @type string $2 Password.
4168       * }
4169       * @return array|IXR_Error
4170       */
4171  	public function wp_getPageStatusList( $args ) {
4172          $this->escape( $args );
4173  
4174          $username = $args[1];
4175          $password = $args[2];
4176  
4177          $user = $this->login( $username, $password );
4178          if ( ! $user ) {
4179              return $this->error;
4180          }
4181  
4182          if ( ! current_user_can( 'edit_pages' ) ) {
4183              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4184          }
4185  
4186          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4187          do_action( 'xmlrpc_call', 'wp.getPageStatusList', $args, $this );
4188  
4189          return get_page_statuses();
4190      }
4191  
4192      /**
4193       * Retrieves page templates.
4194       *
4195       * @since 2.6.0
4196       *
4197       * @param array $args {
4198       *     Method arguments. Note: arguments must be ordered as documented.
4199       *
4200       *     @type int    $0 Blog ID (unused).
4201       *     @type string $1 Username.
4202       *     @type string $2 Password.
4203       * }
4204       * @return array|IXR_Error
4205       */
4206  	public function wp_getPageTemplates( $args ) {
4207          $this->escape( $args );
4208  
4209          $username = $args[1];
4210          $password = $args[2];
4211  
4212          $user = $this->login( $username, $password );
4213          if ( ! $user ) {
4214              return $this->error;
4215          }
4216  
4217          if ( ! current_user_can( 'edit_pages' ) ) {
4218              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4219          }
4220  
4221          $templates            = get_page_templates();
4222          $templates['Default'] = 'default';
4223  
4224          return $templates;
4225      }
4226  
4227      /**
4228       * Retrieves blog options.
4229       *
4230       * @since 2.6.0
4231       *
4232       * @param array $args {
4233       *     Method arguments. Note: arguments must be ordered as documented.
4234       *
4235       *     @type int    $0 Blog ID (unused).
4236       *     @type string $1 Username.
4237       *     @type string $2 Password.
4238       *     @type array  $3 Optional. Options.
4239       * }
4240       * @return array|IXR_Error
4241       */
4242  	public function wp_getOptions( $args ) {
4243          $this->escape( $args );
4244  
4245          $username = $args[1];
4246          $password = $args[2];
4247          $options  = isset( $args[3] ) ? (array) $args[3] : array();
4248  
4249          $user = $this->login( $username, $password );
4250          if ( ! $user ) {
4251              return $this->error;
4252          }
4253  
4254          // If no specific options where asked for, return all of them.
4255          if ( count( $options ) === 0 ) {
4256              $options = array_keys( $this->blog_options );
4257          }
4258  
4259          return $this->_getOptions( $options );
4260      }
4261  
4262      /**
4263       * Retrieves blog options value from list.
4264       *
4265       * @since 2.6.0
4266       *
4267       * @param array $options Options to retrieve.
4268       * @return array
4269       */
4270  	public function _getOptions( $options ) {
4271          $data       = array();
4272          $can_manage = current_user_can( 'manage_options' );
4273          foreach ( $options as $option ) {
4274              if ( array_key_exists( $option, $this->blog_options ) ) {
4275                  $data[ $option ] = $this->blog_options[ $option ];
4276                  // Is the value static or dynamic?
4277                  if ( isset( $data[ $option ]['option'] ) ) {
4278                      $data[ $option ]['value'] = get_option( $data[ $option ]['option'] );
4279                      unset( $data[ $option ]['option'] );
4280                  }
4281  
4282                  if ( ! $can_manage ) {
4283                      $data[ $option ]['readonly'] = true;
4284                  }
4285              }
4286          }
4287  
4288          return $data;
4289      }
4290  
4291      /**
4292       * Updates blog options.
4293       *
4294       * @since 2.6.0
4295       *
4296       * @param array $args {
4297       *     Method arguments. Note: arguments must be ordered as documented.
4298       *
4299       *     @type int    $0 Blog ID (unused).
4300       *     @type string $1 Username.
4301       *     @type string $2 Password.
4302       *     @type array  $3 Options.
4303       * }
4304       * @return array|IXR_Error
4305       */
4306  	public function wp_setOptions( $args ) {
4307          $this->escape( $args );
4308  
4309          $username = $args[1];
4310          $password = $args[2];
4311          $options  = (array) $args[3];
4312  
4313          $user = $this->login( $username, $password );
4314          if ( ! $user ) {
4315              return $this->error;
4316          }
4317  
4318          if ( ! current_user_can( 'manage_options' ) ) {
4319              return new IXR_Error( 403, __( 'Sorry, you are not allowed to update options.' ) );
4320          }
4321  
4322          $option_names = array();
4323          foreach ( $options as $o_name => $o_value ) {
4324              $option_names[] = $o_name;
4325              if ( ! array_key_exists( $o_name, $this->blog_options ) ) {
4326                  continue;
4327              }
4328  
4329              if ( true == $this->blog_options[ $o_name ]['readonly'] ) {
4330                  continue;
4331              }
4332  
4333              update_option( $this->blog_options[ $o_name ]['option'], wp_unslash( $o_value ) );
4334          }
4335  
4336          // Now return the updated values.
4337          return $this->_getOptions( $option_names );
4338      }
4339  
4340      /**
4341       * Retrieves a media item by ID.
4342       *
4343       * @since 3.1.0
4344       *
4345       * @param array $args {
4346       *     Method arguments. Note: arguments must be ordered as documented.
4347       *
4348       *     @type int    $0 Blog ID (unused).
4349       *     @type string $1 Username.
4350       *     @type string $2 Password.
4351       *     @type int    $3 Attachment ID.
4352       * }
4353       * @return array|IXR_Error Associative array contains:
4354       *  - 'date_created_gmt'
4355       *  - 'parent'
4356       *  - 'link'
4357       *  - 'thumbnail'
4358       *  - 'title'
4359       *  - 'caption'
4360       *  - 'description'
4361       *  - 'metadata'
4362       */
4363  	public function wp_getMediaItem( $args ) {
4364          $this->escape( $args );
4365  
4366          $username      = $args[1];
4367          $password      = $args[2];
4368          $attachment_id = (int) $args[3];
4369  
4370          $user = $this->login( $username, $password );
4371          if ( ! $user ) {
4372              return $this->error;
4373          }
4374  
4375          if ( ! current_user_can( 'upload_files' ) ) {
4376              return new IXR_Error( 403, __( 'Sorry, you are not allowed to upload files.' ) );
4377          }
4378  
4379          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4380          do_action( 'xmlrpc_call', 'wp.getMediaItem', $args, $this );
4381  
4382          $attachment = get_post( $attachment_id );
4383          if ( ! $attachment || 'attachment' !== $attachment->post_type ) {
4384              return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
4385          }
4386  
4387          return $this->_prepare_media_item( $attachment );
4388      }
4389  
4390      /**
4391       * Retrieves a collection of media library items (or attachments).
4392       *
4393       * Besides the common blog_id (unused), username, and password arguments,
4394       * it takes a filter array as the last argument.
4395       *
4396       * Accepted 'filter' keys are 'parent_id', 'mime_type', 'offset', and 'number'.
4397       *
4398       * The defaults are as follows:
4399       * - 'number'    - Default is 5. Total number of media items to retrieve.
4400       * - 'offset'    - Default is 0. See WP_Query::query() for more.
4401       * - 'parent_id' - Default is ''. The post where the media item is attached.
4402       *                 Empty string shows all media items. 0 shows unattached media items.
4403       * - 'mime_type' - Default is ''. Filter by mime type (e.g., 'image/jpeg', 'application/pdf')
4404       *
4405       * @since 3.1.0
4406       *
4407       * @param array $args {
4408       *     Method arguments. Note: arguments must be ordered as documented.
4409       *
4410       *     @type int    $0 Blog ID (unused).
4411       *     @type string $1 Username.
4412       *     @type string $2 Password.
4413       *     @type array  $3 Optional. Query arguments.
4414       * }
4415       * @return array|IXR_Error Array containing a collection of media items.
4416       *                         See wp_xmlrpc_server::wp_getMediaItem() for a description
4417       *                         of each item contents.
4418       */
4419  	public function wp_getMediaLibrary( $args ) {
4420          $this->escape( $args );
4421  
4422          $username = $args[1];
4423          $password = $args[2];
4424          $struct   = isset( $args[3] ) ? $args[3] : array();
4425  
4426          $user = $this->login( $username, $password );
4427          if ( ! $user ) {
4428              return $this->error;
4429          }
4430  
4431          if ( ! current_user_can( 'upload_files' ) ) {
4432              return new IXR_Error( 401, __( 'Sorry, you are not allowed to upload files.' ) );
4433          }
4434  
4435          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4436          do_action( 'xmlrpc_call', 'wp.getMediaLibrary', $args, $this );
4437  
4438          $parent_id = ( isset( $struct['parent_id'] ) ) ? absint( $struct['parent_id'] ) : '';
4439          $mime_type = ( isset( $struct['mime_type'] ) ) ? $struct['mime_type'] : '';
4440          $offset    = ( isset( $struct['offset'] ) ) ? absint( $struct['offset'] ) : 0;
4441          $number    = ( isset( $struct['number'] ) ) ? absint( $struct['number'] ) : -1;
4442  
4443          $attachments = get_posts(
4444              array(
4445                  'post_type'      => 'attachment',
4446                  'post_parent'    => $parent_id,
4447                  'offset'         => $offset,
4448                  'numberposts'    => $number,
4449                  'post_mime_type' => $mime_type,
4450              )
4451          );
4452  
4453          $attachments_struct = array();
4454  
4455          foreach ( $attachments as $attachment ) {
4456              $attachments_struct[] = $this->_prepare_media_item( $attachment );
4457          }
4458  
4459          return $attachments_struct;
4460      }
4461  
4462      /**
4463       * Retrieves a list of post formats used by the site.
4464       *
4465       * @since 3.1.0
4466       *
4467       * @param array $args {
4468       *     Method arguments. Note: arguments must be ordered as documented.
4469       *
4470       *     @type int    $0 Blog ID (unused).
4471       *     @type string $1 Username.
4472       *     @type string $2 Password.
4473       * }
4474       * @return array|IXR_Error List of post formats, otherwise IXR_Error object.
4475       */
4476  	public function wp_getPostFormats( $args ) {
4477          $this->escape( $args );
4478  
4479          $username = $args[1];
4480          $password = $args[2];
4481  
4482          $user = $this->login( $username, $password );
4483          if ( ! $user ) {
4484              return $this->error;
4485          }
4486  
4487          if ( ! current_user_can( 'edit_posts' ) ) {
4488              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4489          }
4490  
4491          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4492          do_action( 'xmlrpc_call', 'wp.getPostFormats', $args, $this );
4493  
4494          $formats = get_post_format_strings();
4495  
4496          // Find out if they want a list of currently supports formats.
4497          if ( isset( $args[3] ) && is_array( $args[3] ) ) {
4498              if ( $args[3]['show-supported'] ) {
4499                  if ( current_theme_supports( 'post-formats' ) ) {
4500                      $supported = get_theme_support( 'post-formats' );
4501  
4502                      $data              = array();
4503                      $data['all']       = $formats;
4504                      $data['supported'] = $supported[0];
4505  
4506                      $formats = $data;
4507                  }
4508              }
4509          }
4510  
4511          return $formats;
4512      }
4513  
4514      /**
4515       * Retrieves a post type.
4516       *
4517       * @since 3.4.0
4518       *
4519       * @see get_post_type_object()
4520       *
4521       * @param array $args {
4522       *     Method arguments. Note: arguments must be ordered as documented.
4523       *
4524       *     @type int    $0 Blog ID (unused).
4525       *     @type string $1 Username.
4526       *     @type string $2 Password.
4527       *     @type string $3 Post type name.
4528       *     @type array  $4 Optional. Fields to fetch.
4529       * }
4530       * @return array|IXR_Error Array contains:
4531       *  - 'labels'
4532       *  - 'description'
4533       *  - 'capability_type'
4534       *  - 'cap'
4535       *  - 'map_meta_cap'
4536       *  - 'hierarchical'
4537       *  - 'menu_position'
4538       *  - 'taxonomies'
4539       *  - 'supports'
4540       */
4541  	public function wp_getPostType( $args ) {
4542          if ( ! $this->minimum_args( $args, 4 ) ) {
4543              return $this->error;
4544          }
4545  
4546          $this->escape( $args );
4547  
4548          $username       = $args[1];
4549          $password       = $args[2];
4550          $post_type_name = $args[3];
4551  
4552          if ( isset( $args[4] ) ) {
4553              $fields = $args[4];
4554          } else {
4555              /**
4556               * Filters the default post type query fields used by the given XML-RPC method.
4557               *
4558               * @since 3.4.0
4559               *
4560               * @param array  $fields An array of post type fields to retrieve. By default,
4561               *                       contains 'labels', 'cap', and 'taxonomies'.
4562               * @param string $method The method name.
4563               */
4564              $fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostType' );
4565          }
4566  
4567          $user = $this->login( $username, $password );
4568          if ( ! $user ) {
4569              return $this->error;
4570          }
4571  
4572          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4573          do_action( 'xmlrpc_call', 'wp.getPostType', $args, $this );
4574  
4575          if ( ! post_type_exists( $post_type_name ) ) {
4576              return new IXR_Error( 403, __( 'Invalid post type.' ) );
4577          }
4578  
4579          $post_type = get_post_type_object( $post_type_name );
4580  
4581          if ( ! current_user_can( $post_type->cap->edit_posts ) ) {
4582              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type.' ) );
4583          }
4584  
4585          return $this->_prepare_post_type( $post_type, $fields );
4586      }
4587  
4588      /**
4589       * Retrieves post types.
4590       *
4591       * @since 3.4.0
4592       *
4593       * @see get_post_types()
4594       *
4595       * @param array $args {
4596       *     Method arguments. Note: arguments must be ordered as documented.
4597       *
4598       *     @type int    $0 Blog ID (unused).
4599       *     @type string $1 Username.
4600       *     @type string $2 Password.
4601       *     @type array  $3 Optional. Query arguments.
4602       *     @type array  $4 Optional. Fields to fetch.
4603       * }
4604       * @return array|IXR_Error
4605       */
4606  	public function wp_getPostTypes( $args ) {
4607          if ( ! $this->minimum_args( $args, 3 ) ) {
4608              return $this->error;
4609          }
4610  
4611          $this->escape( $args );
4612  
4613          $username = $args[1];
4614          $password = $args[2];
4615          $filter   = isset( $args[3] ) ? $args[3] : array( 'public' => true );
4616  
4617          if ( isset( $args[4] ) ) {
4618              $fields = $args[4];
4619          } else {
4620              /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4621              $fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostTypes' );
4622          }
4623  
4624          $user = $this->login( $username, $password );
4625          if ( ! $user ) {
4626              return $this->error;
4627          }
4628  
4629          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4630          do_action( 'xmlrpc_call', 'wp.getPostTypes', $args, $this );
4631  
4632          $post_types = get_post_types( $filter, 'objects' );
4633  
4634          $struct = array();
4635  
4636          foreach ( $post_types as $post_type ) {
4637              if ( ! current_user_can( $post_type->cap->edit_posts ) ) {
4638                  continue;
4639              }
4640  
4641              $struct[ $post_type->name ] = $this->_prepare_post_type( $post_type, $fields );
4642          }
4643  
4644          return $struct;
4645      }
4646  
4647      /**
4648       * Retrieves revisions for a specific post.
4649       *
4650       * @since 3.5.0
4651       *
4652       * The optional $fields parameter specifies what fields will be included
4653       * in the response array.
4654       *
4655       * @uses wp_get_post_revisions()
4656       * @see wp_getPost() for more on $fields
4657       *
4658       * @param array $args {
4659       *     Method arguments. Note: arguments must be ordered as documented.
4660       *
4661       *     @type int    $0 Blog ID (unused).
4662       *     @type string $1 Username.
4663       *     @type string $2 Password.
4664       *     @type int    $3 Post ID.
4665       *     @type array  $4 Optional. Fields to fetch.
4666       * }
4667       * @return array|IXR_Error Array containing a collection of posts.
4668       */
4669  	public function wp_getRevisions( $args ) {
4670          if ( ! $this->minimum_args( $args, 4 ) ) {
4671              return $this->error;
4672          }
4673  
4674          $this->escape( $args );
4675  
4676          $username = $args[1];
4677          $password = $args[2];
4678          $post_id  = (int) $args[3];
4679  
4680          if ( isset( $args[4] ) ) {
4681              $fields = $args[4];
4682          } else {
4683              /**
4684               * Filters the default revision query fields used by the given XML-RPC method.
4685               *
4686               * @since 3.5.0
4687               *
4688               * @param array  $field  An array of revision fields to retrieve. By default,
4689               *                       contains 'post_date' and 'post_date_gmt'.
4690               * @param string $method The method name.
4691               */
4692              $fields = apply_filters( 'xmlrpc_default_revision_fields', array( 'post_date', 'post_date_gmt' ), 'wp.getRevisions' );
4693          }
4694  
4695          $user = $this->login( $username, $password );
4696          if ( ! $user ) {
4697              return $this->error;
4698          }
4699  
4700          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4701          do_action( 'xmlrpc_call', 'wp.getRevisions', $args, $this );
4702  
4703          $post = get_post( $post_id );
4704          if ( ! $post ) {
4705              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4706          }
4707  
4708          if ( ! current_user_can( 'edit_post', $post_id ) ) {
4709              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
4710          }
4711  
4712          // Check if revisions are enabled.
4713          if ( ! wp_revisions_enabled( $post ) ) {
4714              return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) );
4715          }
4716  
4717          $revisions = wp_get_post_revisions( $post_id );
4718  
4719          if ( ! $revisions ) {
4720              return array();
4721          }
4722  
4723          $struct = array();
4724  
4725          foreach ( $revisions as $revision ) {
4726              if ( ! current_user_can( 'read_post', $revision->ID ) ) {
4727                  continue;
4728              }
4729  
4730              // Skip autosaves.
4731              if ( wp_is_post_autosave( $revision ) ) {
4732                  continue;
4733              }
4734  
4735              $struct[] = $this->_prepare_post( get_object_vars( $revision ), $fields );
4736          }
4737  
4738          return $struct;
4739      }
4740  
4741      /**
4742       * Restores a post revision.
4743       *
4744       * @since 3.5.0
4745       *
4746       * @uses wp_restore_post_revision()
4747       *
4748       * @param array $args {
4749       *     Method arguments. Note: arguments must be ordered as documented.
4750       *
4751       *     @type int    $0 Blog ID (unused).
4752       *     @type string $1 Username.
4753       *     @type string $2 Password.
4754       *     @type int    $3 Revision ID.
4755       * }
4756       * @return bool|IXR_Error false if there was an error restoring, true if success.
4757       */
4758  	public function wp_restoreRevision( $args ) {
4759          if ( ! $this->minimum_args( $args, 3 ) ) {
4760              return $this->error;
4761          }
4762  
4763          $this->escape( $args );
4764  
4765          $username    = $args[1];
4766          $password    = $args[2];
4767          $revision_id = (int) $args[3];
4768  
4769          $user = $this->login( $username, $password );
4770          if ( ! $user ) {
4771              return $this->error;
4772          }
4773  
4774          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4775          do_action( 'xmlrpc_call', 'wp.restoreRevision', $args, $this );
4776  
4777          $revision = wp_get_post_revision( $revision_id );
4778          if ( ! $revision ) {
4779              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4780          }
4781  
4782          if ( wp_is_post_autosave( $revision ) ) {
4783              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4784          }
4785  
4786          $post = get_post( $revision->post_parent );
4787          if ( ! $post ) {
4788              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4789          }
4790  
4791          if ( ! current_user_can( 'edit_post', $revision->post_parent ) ) {
4792              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
4793          }
4794  
4795          // Check if revisions are disabled.
4796          if ( ! wp_revisions_enabled( $post ) ) {
4797              return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) );
4798          }
4799  
4800          $post = wp_restore_post_revision( $revision_id );
4801  
4802          return (bool) $post;
4803      }
4804  
4805      /*
4806       * Blogger API functions.
4807       * Specs on http://plant.blogger.com/api and https://groups.yahoo.com/group/bloggerDev/
4808       */
4809  
4810      /**
4811       * Retrieves blogs that user owns.
4812       *
4813       * Will make more sense once we support multiple blogs.
4814       *
4815       * @since 1.5.0
4816       *
4817       * @param array $args {
4818       *     Method arguments. Note: arguments must be ordered as documented.
4819       *
4820       *     @type int    $0 Blog ID (unused).
4821       *     @type string $1 Username.
4822       *     @type string $2 Password.
4823       * }
4824       * @return array|IXR_Error
4825       */
4826  	public function blogger_getUsersBlogs( $args ) {
4827          if ( ! $this->minimum_args( $args, 3 ) ) {
4828              return $this->error;
4829          }
4830  
4831          if ( is_multisite() ) {
4832              return $this->_multisite_getUsersBlogs( $args );
4833          }
4834  
4835          $this->escape( $args );
4836  
4837          $username = $args[1];
4838          $password = $args[2];
4839  
4840          $user = $this->login( $username, $password );
4841          if ( ! $user ) {
4842              return $this->error;
4843          }
4844  
4845          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4846          do_action( 'xmlrpc_call', 'blogger.getUsersBlogs', $args, $this );
4847  
4848          $is_admin = current_user_can( 'manage_options' );
4849  
4850          $struct = array(
4851              'isAdmin'  => $is_admin,
4852              'url'      => get_option( 'home' ) . '/',
4853              'blogid'   => '1',
4854              'blogName' => get_option( 'blogname' ),
4855              'xmlrpc'   => site_url( 'xmlrpc.php', 'rpc' ),
4856          );
4857  
4858          return array( $struct );
4859      }
4860  
4861      /**
4862       * Private function for retrieving a users blogs for multisite setups.
4863       *
4864       * @since 3.0.0
4865       *
4866       * @param array $args {
4867       *     Method arguments. Note: arguments must be ordered as documented.
4868       *
4869       *     @type int    $0 Blog ID (unused).
4870       *     @type string $1 Username.
4871       *     @type string $2 Password.
4872       * }
4873       * @return array|IXR_Error
4874       */
4875  	protected function _multisite_getUsersBlogs( $args ) {
4876          $current_blog = get_site();
4877  
4878          $domain = $current_blog->domain;
4879          $path   = $current_blog->path . 'xmlrpc.php';
4880  
4881          $blogs = $this->wp_getUsersBlogs( $args );
4882          if ( $blogs instanceof IXR_Error ) {
4883              return $blogs;
4884          }
4885  
4886          if ( $_SERVER['HTTP_HOST'] == $domain && $_SERVER['REQUEST_URI'] == $path ) {
4887              return $blogs;
4888          } else {
4889              foreach ( (array) $blogs as $blog ) {
4890                  if ( str_contains( $blog['url'], $_SERVER['HTTP_HOST'] ) ) {
4891                      return array( $blog );
4892                  }
4893              }
4894              return array();
4895          }
4896      }
4897  
4898      /**
4899       * Retrieves user's data.
4900       *
4901       * Gives your client some info about you, so you don't have to.
4902       *
4903       * @since 1.5.0
4904       *
4905       * @param array $args {
4906       *     Method arguments. Note: arguments must be ordered as documented.
4907       *
4908       *     @type int    $0 Blog ID (unused).
4909       *     @type string $1 Username.
4910       *     @type string $2 Password.
4911       * }
4912       * @return array|IXR_Error
4913       */
4914  	public function blogger_getUserInfo( $args ) {
4915          $this->escape( $args );
4916  
4917          $username = $args[1];
4918          $password = $args[2];
4919  
4920          $user = $this->login( $username, $password );
4921          if ( ! $user ) {
4922              return $this->error;
4923          }
4924  
4925          if ( ! current_user_can( 'edit_posts' ) ) {
4926              return new IXR_Error( 401, __( 'Sorry, you are not allowed to access user data on this site.' ) );
4927          }
4928  
4929          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4930          do_action( 'xmlrpc_call', 'blogger.getUserInfo', $args, $this );
4931  
4932          $struct = array(
4933              'nickname'  => $user->nickname,
4934              'userid'    => $user->ID,
4935              'url'       => $user->user_url,
4936              'lastname'  => $user->last_name,
4937              'firstname' => $user->first_name,
4938          );
4939  
4940          return $struct;
4941      }
4942  
4943      /**
4944       * Retrieves a post.
4945       *
4946       * @since 1.5.0
4947       *
4948       * @param array $args {
4949       *     Method arguments. Note: arguments must be ordered as documented.
4950       *
4951       *     @type int    $0 Blog ID (unused).
4952       *     @type int    $1 Post ID.
4953       *     @type string $2 Username.
4954       *     @type string $3 Password.
4955       * }
4956       * @return array|IXR_Error
4957       */
4958  	public function blogger_getPost( $args ) {
4959          $this->escape( $args );
4960  
4961          $post_id  = (int) $args[1];
4962          $username = $args[2];
4963          $password = $args[3];
4964  
4965          $user = $this->login( $username, $password );
4966          if ( ! $user ) {
4967              return $this->error;
4968          }
4969  
4970          $post_data = get_post( $post_id, ARRAY_A );
4971          if ( ! $post_data ) {
4972              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4973          }
4974  
4975          if ( ! current_user_can( 'edit_post', $post_id ) ) {
4976              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
4977          }
4978  
4979          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4980          do_action( 'xmlrpc_call', 'blogger.getPost', $args, $this );
4981  
4982          $categories = implode( ',', wp_get_post_categories( $post_id ) );
4983  
4984          $content  = '<title>' . wp_unslash( $post_data['post_title'] ) . '</title>';
4985          $content .= '<category>' . $categories . '</category>';
4986          $content .= wp_unslash( $post_data['post_content'] );
4987  
4988          $struct = array(
4989              'userid'      => $post_data['post_author'],
4990              'dateCreated' => $this->_convert_date( $post_data['post_date'] ),
4991              'content'     => $content,
4992              'postid'      => (string) $post_data['ID'],
4993          );
4994  
4995          return $struct;
4996      }
4997  
4998      /**
4999       * Retrieves the list of recent posts.
5000       *
5001       * @since 1.5.0
5002       *
5003       * @param array $args {
5004       *     Method arguments. Note: arguments must be ordered as documented.
5005       *
5006       *     @type string $0 App key (unused).
5007       *     @type int    $1 Blog ID (unused).
5008       *     @type string $2 Username.
5009       *     @type string $3 Password.
5010       *     @type int    $4 Optional. Number of posts.
5011       * }
5012       * @return array|IXR_Error
5013       */
5014  	public function blogger_getRecentPosts( $args ) {
5015  
5016          $this->escape( $args );
5017  
5018          // $args[0] = appkey - ignored.
5019          $username = $args[2];
5020          $password = $args[3];
5021          if ( isset( $args[4] ) ) {
5022