[ Index ]

PHP Cross Reference of WordPress

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