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