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