[ 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 call_user_func_array( array( $this, $name ), $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 string|array $args Sanitize single string or array of strings.
 731       * @param int $count         Minimum number of arguments.
 732       * @return bool if `$args` contains at least $count arguments.
 733       */
 734  	protected function minimum_args( $args, $count ) {
 735          if ( 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                              $taxonomy,
1561                              array(
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          $this->escape( $post );
1712          $merged_content_struct = array_merge( $post, $content_struct );
1713  
1714          $retval = $this->_insert_post( $user, $merged_content_struct );
1715          if ( $retval instanceof IXR_Error ) {
1716              return $retval;
1717          }
1718  
1719          return true;
1720      }
1721  
1722      /**
1723       * Delete a post for any registered post type.
1724       *
1725       * @since 3.4.0
1726       *
1727       * @see wp_delete_post()
1728       *
1729       * @param array  $args {
1730       *     Method arguments. Note: arguments must be ordered as documented.
1731       *
1732       *     @type int    $blog_id  Blog ID (unused).
1733       *     @type string $username Username.
1734       *     @type string $password Password.
1735       *     @type int    $post_id  Post ID.
1736       * }
1737       * @return true|IXR_Error True on success, IXR_Error instance on failure.
1738       */
1739  	public function wp_deletePost( $args ) {
1740          if ( ! $this->minimum_args( $args, 4 ) ) {
1741              return $this->error;
1742          }
1743  
1744          $this->escape( $args );
1745  
1746          $username = $args[1];
1747          $password = $args[2];
1748          $post_id  = (int) $args[3];
1749  
1750          $user = $this->login( $username, $password );
1751          if ( ! $user ) {
1752              return $this->error;
1753          }
1754  
1755          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1756          do_action( 'xmlrpc_call', 'wp.deletePost' );
1757  
1758          $post = get_post( $post_id, ARRAY_A );
1759          if ( empty( $post['ID'] ) ) {
1760              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1761          }
1762  
1763          if ( ! current_user_can( 'delete_post', $post_id ) ) {
1764              return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this post.' ) );
1765          }
1766  
1767          $result = wp_delete_post( $post_id );
1768  
1769          if ( ! $result ) {
1770              return new IXR_Error( 500, __( 'The post cannot be deleted.' ) );
1771          }
1772  
1773          return true;
1774      }
1775  
1776      /**
1777       * Retrieve a post.
1778       *
1779       * @since 3.4.0
1780       *
1781       * The optional $fields parameter specifies what fields will be included
1782       * in the response array. This should be a list of field names. 'post_id' will
1783       * always be included in the response regardless of the value of $fields.
1784       *
1785       * Instead of, or in addition to, individual field names, conceptual group
1786       * names can be used to specify multiple fields. The available conceptual
1787       * groups are 'post' (all basic fields), 'taxonomies', 'custom_fields',
1788       * and 'enclosure'.
1789       *
1790       * @see get_post()
1791       *
1792       * @param array $args {
1793       *     Method arguments. Note: arguments must be ordered as documented.
1794       *
1795       *     @type int    $blog_id  Blog ID (unused).
1796       *     @type string $username Username.
1797       *     @type string $password Password.
1798       *     @type int    $post_id  Post ID.
1799       *     @type array  $fields   The subset of post type fields to return.
1800       * }
1801       * @return array|IXR_Error Array contains (based on $fields parameter):
1802       *  - 'post_id'
1803       *  - 'post_title'
1804       *  - 'post_date'
1805       *  - 'post_date_gmt'
1806       *  - 'post_modified'
1807       *  - 'post_modified_gmt'
1808       *  - 'post_status'
1809       *  - 'post_type'
1810       *  - 'post_name'
1811       *  - 'post_author'
1812       *  - 'post_password'
1813       *  - 'post_excerpt'
1814       *  - 'post_content'
1815       *  - 'link'
1816       *  - 'comment_status'
1817       *  - 'ping_status'
1818       *  - 'sticky'
1819       *  - 'custom_fields'
1820       *  - 'terms'
1821       *  - 'categories'
1822       *  - 'tags'
1823       *  - 'enclosure'
1824       */
1825  	public function wp_getPost( $args ) {
1826          if ( ! $this->minimum_args( $args, 4 ) ) {
1827              return $this->error;
1828          }
1829  
1830          $this->escape( $args );
1831  
1832          $username = $args[1];
1833          $password = $args[2];
1834          $post_id  = (int) $args[3];
1835  
1836          if ( isset( $args[4] ) ) {
1837              $fields = $args[4];
1838          } else {
1839              /**
1840               * Filters the list of post query fields used by the given XML-RPC method.
1841               *
1842               * @since 3.4.0
1843               *
1844               * @param array  $fields Array of post fields. Default array contains 'post', 'terms', and 'custom_fields'.
1845               * @param string $method Method name.
1846               */
1847              $fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPost' );
1848          }
1849  
1850          $user = $this->login( $username, $password );
1851          if ( ! $user ) {
1852              return $this->error;
1853          }
1854  
1855          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1856          do_action( 'xmlrpc_call', 'wp.getPost' );
1857  
1858          $post = get_post( $post_id, ARRAY_A );
1859  
1860          if ( empty( $post['ID'] ) ) {
1861              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1862          }
1863  
1864          if ( ! current_user_can( 'edit_post', $post_id ) ) {
1865              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
1866          }
1867  
1868          return $this->_prepare_post( $post, $fields );
1869      }
1870  
1871      /**
1872       * Retrieve posts.
1873       *
1874       * @since 3.4.0
1875       *
1876       * @see wp_get_recent_posts()
1877       * @see wp_getPost() for more on `$fields`
1878       * @see get_posts() for more on `$filter` values
1879       *
1880       * @param array $args {
1881       *     Method arguments. Note: arguments must be ordered as documented.
1882       *
1883       *     @type int    $blog_id  Blog ID (unused).
1884       *     @type string $username Username.
1885       *     @type string $password Password.
1886       *     @type array  $filter   Optional. Modifies the query used to retrieve posts. Accepts 'post_type',
1887       *                            'post_status', 'number', 'offset', 'orderby', 's', and 'order'.
1888       *                            Default empty array.
1889       *     @type array  $fields   Optional. The subset of post type fields to return in the response array.
1890       * }
1891       * @return array|IXR_Error Array contains a collection of posts.
1892       */
1893  	public function wp_getPosts( $args ) {
1894          if ( ! $this->minimum_args( $args, 3 ) ) {
1895              return $this->error;
1896          }
1897  
1898          $this->escape( $args );
1899  
1900          $username = $args[1];
1901          $password = $args[2];
1902          $filter   = isset( $args[3] ) ? $args[3] : array();
1903  
1904          if ( isset( $args[4] ) ) {
1905              $fields = $args[4];
1906          } else {
1907              /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1908              $fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPosts' );
1909          }
1910  
1911          $user = $this->login( $username, $password );
1912          if ( ! $user ) {
1913              return $this->error;
1914          }
1915  
1916          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1917          do_action( 'xmlrpc_call', 'wp.getPosts' );
1918  
1919          $query = array();
1920  
1921          if ( isset( $filter['post_type'] ) ) {
1922              $post_type = get_post_type_object( $filter['post_type'] );
1923              if ( ! ( (bool) $post_type ) ) {
1924                  return new IXR_Error( 403, __( 'Invalid post type.' ) );
1925              }
1926          } else {
1927              $post_type = get_post_type_object( 'post' );
1928          }
1929  
1930          if ( ! current_user_can( $post_type->cap->edit_posts ) ) {
1931              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type.' ) );
1932          }
1933  
1934          $query['post_type'] = $post_type->name;
1935  
1936          if ( isset( $filter['post_status'] ) ) {
1937              $query['post_status'] = $filter['post_status'];
1938          }
1939  
1940          if ( isset( $filter['number'] ) ) {
1941              $query['numberposts'] = absint( $filter['number'] );
1942          }
1943  
1944          if ( isset( $filter['offset'] ) ) {
1945              $query['offset'] = absint( $filter['offset'] );
1946          }
1947  
1948          if ( isset( $filter['orderby'] ) ) {
1949              $query['orderby'] = $filter['orderby'];
1950  
1951              if ( isset( $filter['order'] ) ) {
1952                  $query['order'] = $filter['order'];
1953              }
1954          }
1955  
1956          if ( isset( $filter['s'] ) ) {
1957              $query['s'] = $filter['s'];
1958          }
1959  
1960          $posts_list = wp_get_recent_posts( $query );
1961  
1962          if ( ! $posts_list ) {
1963              return array();
1964          }
1965  
1966          // Holds all the posts data.
1967          $struct = array();
1968  
1969          foreach ( $posts_list as $post ) {
1970              if ( ! current_user_can( 'edit_post', $post['ID'] ) ) {
1971                  continue;
1972              }
1973  
1974              $struct[] = $this->_prepare_post( $post, $fields );
1975          }
1976  
1977          return $struct;
1978      }
1979  
1980      /**
1981       * Create a new term.
1982       *
1983       * @since 3.4.0
1984       *
1985       * @see wp_insert_term()
1986       *
1987       * @param array $args {
1988       *     Method arguments. Note: arguments must be ordered as documented.
1989       *
1990       *     @type int    $blog_id        Blog ID (unused).
1991       *     @type string $username       Username.
1992       *     @type string $password       Password.
1993       *     @type array  $content_struct Content struct for adding a new term. The struct must contain
1994       *                                  the term 'name' and 'taxonomy'. Optional accepted values include
1995       *                                  'parent', 'description', and 'slug'.
1996       * }
1997       * @return int|IXR_Error The term ID on success, or an IXR_Error object on failure.
1998       */
1999  	public function wp_newTerm( $args ) {
2000          if ( ! $this->minimum_args( $args, 4 ) ) {
2001              return $this->error;
2002          }
2003  
2004          $this->escape( $args );
2005  
2006          $username       = $args[1];
2007          $password       = $args[2];
2008          $content_struct = $args[3];
2009  
2010          $user = $this->login( $username, $password );
2011          if ( ! $user ) {
2012              return $this->error;
2013          }
2014  
2015          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2016          do_action( 'xmlrpc_call', 'wp.newTerm' );
2017  
2018          if ( ! taxonomy_exists( $content_struct['taxonomy'] ) ) {
2019              return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2020          }
2021  
2022          $taxonomy = get_taxonomy( $content_struct['taxonomy'] );
2023  
2024          if ( ! current_user_can( $taxonomy->cap->edit_terms ) ) {
2025              return new IXR_Error( 401, __( 'Sorry, you are not allowed to create terms in this taxonomy.' ) );
2026          }
2027  
2028          $taxonomy = (array) $taxonomy;
2029  
2030          // hold the data of the term
2031          $term_data = array();
2032  
2033          $term_data['name'] = trim( $content_struct['name'] );
2034          if ( empty( $term_data['name'] ) ) {
2035              return new IXR_Error( 403, __( 'The term name cannot be empty.' ) );
2036          }
2037  
2038          if ( isset( $content_struct['parent'] ) ) {
2039              if ( ! $taxonomy['hierarchical'] ) {
2040                  return new IXR_Error( 403, __( 'This taxonomy is not hierarchical.' ) );
2041              }
2042  
2043              $parent_term_id = (int) $content_struct['parent'];
2044              $parent_term    = get_term( $parent_term_id, $taxonomy['name'] );
2045  
2046              if ( is_wp_error( $parent_term ) ) {
2047                  return new IXR_Error( 500, $parent_term->get_error_message() );
2048              }
2049  
2050              if ( ! $parent_term ) {
2051                  return new IXR_Error( 403, __( 'Parent term does not exist.' ) );
2052              }
2053  
2054              $term_data['parent'] = $content_struct['parent'];
2055          }
2056  
2057          if ( isset( $content_struct['description'] ) ) {
2058              $term_data['description'] = $content_struct['description'];
2059          }
2060  
2061          if ( isset( $content_struct['slug'] ) ) {
2062              $term_data['slug'] = $content_struct['slug'];
2063          }
2064  
2065          $term = wp_insert_term( $term_data['name'], $taxonomy['name'], $term_data );
2066  
2067          if ( is_wp_error( $term ) ) {
2068              return new IXR_Error( 500, $term->get_error_message() );
2069          }
2070  
2071          if ( ! $term ) {
2072              return new IXR_Error( 500, __( 'Sorry, your term could not be created.' ) );
2073          }
2074  
2075          // Add term meta.
2076          if ( isset( $content_struct['custom_fields'] ) ) {
2077              $this->set_term_custom_fields( $term['term_id'], $content_struct['custom_fields'] );
2078          }
2079  
2080          return strval( $term['term_id'] );
2081      }
2082  
2083      /**
2084       * Edit a term.
2085       *
2086       * @since 3.4.0
2087       *
2088       * @see wp_update_term()
2089       *
2090       * @param array $args {
2091       *     Method arguments. Note: arguments must be ordered as documented.
2092       *
2093       *     @type int    $blog_id        Blog ID (unused).
2094       *     @type string $username       Username.
2095       *     @type string $password       Password.
2096       *     @type int    $term_id        Term ID.
2097       *     @type array  $content_struct Content struct for editing a term. The struct must contain the
2098       *                                  term ''taxonomy'. Optional accepted values include 'name', 'parent',
2099       *                                  'description', and 'slug'.
2100       * }
2101       * @return true|IXR_Error True on success, IXR_Error instance on failure.
2102       */
2103  	public function wp_editTerm( $args ) {
2104          if ( ! $this->minimum_args( $args, 5 ) ) {
2105              return $this->error;
2106          }
2107  
2108          $this->escape( $args );
2109  
2110          $username       = $args[1];
2111          $password       = $args[2];
2112          $term_id        = (int) $args[3];
2113          $content_struct = $args[4];
2114  
2115          $user = $this->login( $username, $password );
2116          if ( ! $user ) {
2117              return $this->error;
2118          }
2119  
2120          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2121          do_action( 'xmlrpc_call', 'wp.editTerm' );
2122  
2123          if ( ! taxonomy_exists( $content_struct['taxonomy'] ) ) {
2124              return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2125          }
2126  
2127          $taxonomy = get_taxonomy( $content_struct['taxonomy'] );
2128  
2129          $taxonomy = (array) $taxonomy;
2130  
2131          // hold the data of the term
2132          $term_data = array();
2133  
2134          $term = get_term( $term_id, $content_struct['taxonomy'] );
2135  
2136          if ( is_wp_error( $term ) ) {
2137              return new IXR_Error( 500, $term->get_error_message() );
2138          }
2139  
2140          if ( ! $term ) {
2141              return new IXR_Error( 404, __( 'Invalid term ID.' ) );
2142          }
2143  
2144          if ( ! current_user_can( 'edit_term', $term_id ) ) {
2145              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this term.' ) );
2146          }
2147  
2148          if ( isset( $content_struct['name'] ) ) {
2149              $term_data['name'] = trim( $content_struct['name'] );
2150  
2151              if ( empty( $term_data['name'] ) ) {
2152                  return new IXR_Error( 403, __( 'The term name cannot be empty.' ) );
2153              }
2154          }
2155  
2156          if ( ! empty( $content_struct['parent'] ) ) {
2157              if ( ! $taxonomy['hierarchical'] ) {
2158                  return new IXR_Error( 403, __( 'Cannot set parent term, taxonomy is not hierarchical.' ) );
2159              }
2160  
2161              $parent_term_id = (int) $content_struct['parent'];
2162              $parent_term    = get_term( $parent_term_id, $taxonomy['name'] );
2163  
2164              if ( is_wp_error( $parent_term ) ) {
2165                  return new IXR_Error( 500, $parent_term->get_error_message() );
2166              }
2167  
2168              if ( ! $parent_term ) {
2169                  return new IXR_Error( 403, __( 'Parent term does not exist.' ) );
2170              }
2171  
2172              $term_data['parent'] = $content_struct['parent'];
2173          }
2174  
2175          if ( isset( $content_struct['description'] ) ) {
2176              $term_data['description'] = $content_struct['description'];
2177          }
2178  
2179          if ( isset( $content_struct['slug'] ) ) {
2180              $term_data['slug'] = $content_struct['slug'];
2181          }
2182  
2183          $term = wp_update_term( $term_id, $taxonomy['name'], $term_data );
2184  
2185          if ( is_wp_error( $term ) ) {
2186              return new IXR_Error( 500, $term->get_error_message() );
2187          }
2188  
2189          if ( ! $term ) {
2190              return new IXR_Error( 500, __( 'Sorry, editing the term failed.' ) );
2191          }
2192  
2193          // Update term meta.
2194          if ( isset( $content_struct['custom_fields'] ) ) {
2195              $this->set_term_custom_fields( $term_id, $content_struct['custom_fields'] );
2196          }
2197  
2198          return true;
2199      }
2200  
2201      /**
2202       * Delete a term.
2203       *
2204       * @since 3.4.0
2205       *
2206       * @see wp_delete_term()
2207       *
2208       * @param array  $args {
2209       *     Method arguments. Note: arguments must be ordered as documented.
2210       *
2211       *     @type int    $blog_id      Blog ID (unused).
2212       *     @type string $username     Username.
2213       *     @type string $password     Password.
2214       *     @type string $taxnomy_name Taxonomy name.
2215       *     @type int    $term_id      Term ID.
2216       * }
2217       * @return bool|IXR_Error True on success, IXR_Error instance on failure.
2218       */
2219  	public function wp_deleteTerm( $args ) {
2220          if ( ! $this->minimum_args( $args, 5 ) ) {
2221              return $this->error;
2222          }
2223  
2224          $this->escape( $args );
2225  
2226          $username = $args[1];
2227          $password = $args[2];
2228          $taxonomy = $args[3];
2229          $term_id  = (int) $args[4];
2230  
2231          $user = $this->login( $username, $password );
2232          if ( ! $user ) {
2233              return $this->error;
2234          }
2235  
2236          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2237          do_action( 'xmlrpc_call', 'wp.deleteTerm' );
2238  
2239          if ( ! taxonomy_exists( $taxonomy ) ) {
2240              return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2241          }
2242  
2243          $taxonomy = get_taxonomy( $taxonomy );
2244          $term     = get_term( $term_id, $taxonomy->name );
2245  
2246          if ( is_wp_error( $term ) ) {
2247              return new IXR_Error( 500, $term->get_error_message() );
2248          }
2249  
2250          if ( ! $term ) {
2251              return new IXR_Error( 404, __( 'Invalid term ID.' ) );
2252          }
2253  
2254          if ( ! current_user_can( 'delete_term', $term_id ) ) {
2255              return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this term.' ) );
2256          }
2257  
2258          $result = wp_delete_term( $term_id, $taxonomy->name );
2259  
2260          if ( is_wp_error( $result ) ) {
2261              return new IXR_Error( 500, $term->get_error_message() );
2262          }
2263  
2264          if ( ! $result ) {
2265              return new IXR_Error( 500, __( 'Sorry, deleting the term failed.' ) );
2266          }
2267  
2268          return $result;
2269      }
2270  
2271      /**
2272       * Retrieve a term.
2273       *
2274       * @since 3.4.0
2275       *
2276       * @see get_term()
2277       *
2278       * @param array  $args {
2279       *     Method arguments. Note: arguments must be ordered as documented.
2280       *
2281       *     @type int    $blog_id  Blog ID (unused).
2282       *     @type string $username Username.
2283       *     @type string $password Password.
2284       *     @type string $taxnomy  Taxonomy name.
2285       *     @type string $term_id  Term ID.
2286       * }
2287       * @return array|IXR_Error IXR_Error on failure, array on success, containing:
2288       *  - 'term_id'
2289       *  - 'name'
2290       *  - 'slug'
2291       *  - 'term_group'
2292       *  - 'term_taxonomy_id'
2293       *  - 'taxonomy'
2294       *  - 'description'
2295       *  - 'parent'
2296       *  - 'count'
2297       */
2298  	public function wp_getTerm( $args ) {
2299          if ( ! $this->minimum_args( $args, 5 ) ) {
2300              return $this->error;
2301          }
2302  
2303          $this->escape( $args );
2304  
2305          $username = $args[1];
2306          $password = $args[2];
2307          $taxonomy = $args[3];
2308          $term_id  = (int) $args[4];
2309  
2310          $user = $this->login( $username, $password );
2311          if ( ! $user ) {
2312              return $this->error;
2313          }
2314  
2315          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2316          do_action( 'xmlrpc_call', 'wp.getTerm' );
2317  
2318          if ( ! taxonomy_exists( $taxonomy ) ) {
2319              return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2320          }
2321  
2322          $taxonomy = get_taxonomy( $taxonomy );
2323  
2324          $term = get_term( $term_id, $taxonomy->name, ARRAY_A );
2325  
2326          if ( is_wp_error( $term ) ) {
2327              return new IXR_Error( 500, $term->get_error_message() );
2328          }
2329  
2330          if ( ! $term ) {
2331              return new IXR_Error( 404, __( 'Invalid term ID.' ) );
2332          }
2333  
2334          if ( ! current_user_can( 'assign_term', $term_id ) ) {
2335              return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign this term.' ) );
2336          }
2337  
2338          return $this->_prepare_term( $term );
2339      }
2340  
2341      /**
2342       * Retrieve all terms for a taxonomy.
2343       *
2344       * @since 3.4.0
2345       *
2346       * The optional $filter parameter modifies the query used to retrieve terms.
2347       * Accepted keys are 'number', 'offset', 'orderby', 'order', 'hide_empty', and 'search'.
2348       *
2349       * @see get_terms()
2350       *
2351       * @param array  $args {
2352       *     Method arguments. Note: arguments must be ordered as documented.
2353       *
2354       *     @type int    $blog_id  Blog ID (unused).
2355       *     @type string $username Username.
2356       *     @type string $password Password.
2357       *     @type string $taxnomy  Taxonomy name.
2358       *     @type array  $filter   Optional. Modifies the query used to retrieve posts. Accepts 'number',
2359       *                            'offset', 'orderby', 'order', 'hide_empty', and 'search'. Default empty array.
2360       * }
2361       * @return array|IXR_Error An associative array of terms data on success, IXR_Error instance otherwise.
2362       */
2363  	public function wp_getTerms( $args ) {
2364          if ( ! $this->minimum_args( $args, 4 ) ) {
2365              return $this->error;
2366          }
2367  
2368          $this->escape( $args );
2369  
2370          $username = $args[1];
2371          $password = $args[2];
2372          $taxonomy = $args[3];
2373          $filter   = isset( $args[4] ) ? $args[4] : array();
2374  
2375          $user = $this->login( $username, $password );
2376          if ( ! $user ) {
2377              return $this->error;
2378          }
2379  
2380          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2381          do_action( 'xmlrpc_call', 'wp.getTerms' );
2382  
2383          if ( ! taxonomy_exists( $taxonomy ) ) {
2384              return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2385          }
2386  
2387          $taxonomy = get_taxonomy( $taxonomy );
2388  
2389          if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) {
2390              return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) );
2391          }
2392  
2393          $query = array();
2394  
2395          if ( isset( $filter['number'] ) ) {
2396              $query['number'] = absint( $filter['number'] );
2397          }
2398  
2399          if ( isset( $filter['offset'] ) ) {
2400              $query['offset'] = absint( $filter['offset'] );
2401          }
2402  
2403          if ( isset( $filter['orderby'] ) ) {
2404              $query['orderby'] = $filter['orderby'];
2405  
2406              if ( isset( $filter['order'] ) ) {
2407                  $query['order'] = $filter['order'];
2408              }
2409          }
2410  
2411          if ( isset( $filter['hide_empty'] ) ) {
2412              $query['hide_empty'] = $filter['hide_empty'];
2413          } else {
2414              $query['get'] = 'all';
2415          }
2416  
2417          if ( isset( $filter['search'] ) ) {
2418              $query['search'] = $filter['search'];
2419          }
2420  
2421          $terms = get_terms( $taxonomy->name, $query );
2422  
2423          if ( is_wp_error( $terms ) ) {
2424              return new IXR_Error( 500, $terms->get_error_message() );
2425          }
2426  
2427          $struct = array();
2428  
2429          foreach ( $terms as $term ) {
2430              $struct[] = $this->_prepare_term( $term );
2431          }
2432  
2433          return $struct;
2434      }
2435  
2436      /**
2437       * Retrieve a taxonomy.
2438       *
2439       * @since 3.4.0
2440       *
2441       * @see get_taxonomy()
2442       *
2443       * @param array  $args {
2444       *     Method arguments. Note: arguments must be ordered as documented.
2445       *
2446       *     @type int    $blog_id  Blog ID (unused).
2447       *     @type string $username Username.
2448       *     @type string $password Password.
2449       *     @type string $taxnomy  Taxonomy name.
2450       *     @type array  $fields   Optional. Array of taxonomy fields to limit to in the return.
2451       *                            Accepts 'labels', 'cap', 'menu', and 'object_type'.
2452       *                            Default empty array.
2453       * }
2454       * @return array|IXR_Error An array of taxonomy data on success, IXR_Error instance otherwise.
2455       */
2456  	public function wp_getTaxonomy( $args ) {
2457          if ( ! $this->minimum_args( $args, 4 ) ) {
2458              return $this->error;
2459          }
2460  
2461          $this->escape( $args );
2462  
2463          $username = $args[1];
2464          $password = $args[2];
2465          $taxonomy = $args[3];
2466  
2467          if ( isset( $args[4] ) ) {
2468              $fields = $args[4];
2469          } else {
2470              /**
2471               * Filters the taxonomy query fields used by the given XML-RPC method.
2472               *
2473               * @since 3.4.0
2474               *
2475               * @param array  $fields An array of taxonomy fields to retrieve.
2476               * @param string $method The method name.
2477               */
2478              $fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomy' );
2479          }
2480  
2481          $user = $this->login( $username, $password );
2482          if ( ! $user ) {
2483              return $this->error;
2484          }
2485  
2486          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2487          do_action( 'xmlrpc_call', 'wp.getTaxonomy' );
2488  
2489          if ( ! taxonomy_exists( $taxonomy ) ) {
2490              return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2491          }
2492  
2493          $taxonomy = get_taxonomy( $taxonomy );
2494  
2495          if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) {
2496              return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) );
2497          }
2498  
2499          return $this->_prepare_taxonomy( $taxonomy, $fields );
2500      }
2501  
2502      /**
2503       * Retrieve all taxonomies.
2504       *
2505       * @since 3.4.0
2506       *
2507       * @see get_taxonomies()
2508       *
2509       * @param array  $args {
2510       *     Method arguments. Note: arguments must be ordered as documented.
2511       *
2512       *     @type int    $blog_id  Blog ID (unused).
2513       *     @type string $username Username.
2514       *     @type string $password Password.
2515       *     @type array  $filter   Optional. An array of arguments for retrieving taxonomies.
2516       *     @type array  $fields   Optional. The subset of taxonomy fields to return.
2517       * }
2518       * @return array|IXR_Error An associative array of taxonomy data with returned fields determined
2519       *                         by `$fields`, or an IXR_Error instance on failure.
2520       */
2521  	public function wp_getTaxonomies( $args ) {
2522          if ( ! $this->minimum_args( $args, 3 ) ) {
2523              return $this->error;
2524          }
2525  
2526          $this->escape( $args );
2527  
2528          $username = $args[1];
2529          $password = $args[2];
2530          $filter   = isset( $args[3] ) ? $args[3] : array( 'public' => true );
2531  
2532          if ( isset( $args[4] ) ) {
2533              $fields = $args[4];
2534          } else {
2535              /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2536              $fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomies' );
2537          }
2538  
2539          $user = $this->login( $username, $password );
2540          if ( ! $user ) {
2541              return $this->error;
2542          }
2543  
2544          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2545          do_action( 'xmlrpc_call', 'wp.getTaxonomies' );
2546  
2547          $taxonomies = get_taxonomies( $filter, 'objects' );
2548  
2549          // holds all the taxonomy data
2550          $struct = array();
2551  
2552          foreach ( $taxonomies as $taxonomy ) {
2553              // capability check for post_types
2554              if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) {
2555                  continue;
2556              }
2557  
2558              $struct[] = $this->_prepare_taxonomy( $taxonomy, $fields );
2559          }
2560  
2561          return $struct;
2562      }
2563  
2564      /**
2565       * Retrieve a user.
2566       *
2567       * The optional $fields parameter specifies what fields will be included
2568       * in the response array. This should be a list of field names. 'user_id' will
2569       * always be included in the response regardless of the value of $fields.
2570       *
2571       * Instead of, or in addition to, individual field names, conceptual group
2572       * names can be used to specify multiple fields. The available conceptual
2573       * groups are 'basic' and 'all'.
2574       *
2575       * @uses get_userdata()
2576       *
2577       * @param array  $args {
2578       *     Method arguments. Note: arguments must be ordered as documented.
2579       *
2580       *     @type int    $blog_id (unused)
2581       *     @type string $username
2582       *     @type string $password
2583       *     @type int    $user_id
2584       *     @type array  $fields (optional)
2585       * }
2586       * @return array|IXR_Error Array contains (based on $fields parameter):
2587       *  - 'user_id'
2588       *  - 'username'
2589       *  - 'first_name'
2590       *  - 'last_name'
2591       *  - 'registered'
2592       *  - 'bio'
2593       *  - 'email'
2594       *  - 'nickname'
2595       *  - 'nicename'
2596       *  - 'url'
2597       *  - 'display_name'
2598       *  - 'roles'
2599       */
2600  	public function wp_getUser( $args ) {
2601          if ( ! $this->minimum_args( $args, 4 ) ) {
2602              return $this->error;
2603          }
2604  
2605          $this->escape( $args );
2606  
2607          $username = $args[1];
2608          $password = $args[2];
2609          $user_id  = (int) $args[3];
2610  
2611          if ( isset( $args[4] ) ) {
2612              $fields = $args[4];
2613          } else {
2614              /**
2615               * Filters the default user query fields used by the given XML-RPC method.
2616               *
2617               * @since 3.5.0
2618               *
2619               * @param array  $fields User query fields for given method. Default 'all'.
2620               * @param string $method The method name.
2621               */
2622              $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUser' );
2623          }
2624  
2625          $user = $this->login( $username, $password );
2626          if ( ! $user ) {
2627              return $this->error;
2628          }
2629  
2630          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2631          do_action( 'xmlrpc_call', 'wp.getUser' );
2632  
2633          if ( ! current_user_can( 'edit_user', $user_id ) ) {
2634              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this user.' ) );
2635          }
2636  
2637          $user_data = get_userdata( $user_id );
2638  
2639          if ( ! $user_data ) {
2640              return new IXR_Error( 404, __( 'Invalid user ID.' ) );
2641          }
2642  
2643          return $this->_prepare_user( $user_data, $fields );
2644      }
2645  
2646      /**
2647       * Retrieve users.
2648       *
2649       * The optional $filter parameter modifies the query used to retrieve users.
2650       * Accepted keys are 'number' (default: 50), 'offset' (default: 0), 'role',
2651       * 'who', 'orderby', and 'order'.
2652       *
2653       * The optional $fields parameter specifies what fields will be included
2654       * in the response array.
2655       *
2656       * @uses get_users()
2657       * @see wp_getUser() for more on $fields and return values
2658       *
2659       * @param array  $args {
2660       *     Method arguments. Note: arguments must be ordered as documented.
2661       *
2662       *     @type int    $blog_id (unused)
2663       *     @type string $username
2664       *     @type string $password
2665       *     @type array  $filter (optional)
2666       *     @type array  $fields (optional)
2667       * }
2668       * @return array|IXR_Error users data
2669       */
2670  	public function wp_getUsers( $args ) {
2671          if ( ! $this->minimum_args( $args, 3 ) ) {
2672              return $this->error;
2673          }
2674  
2675          $this->escape( $args );
2676  
2677          $username = $args[1];
2678          $password = $args[2];
2679          $filter   = isset( $args[3] ) ? $args[3] : array();
2680  
2681          if ( isset( $args[4] ) ) {
2682              $fields = $args[4];
2683          } else {
2684              /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2685              $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUsers' );
2686          }
2687  
2688          $user = $this->login( $username, $password );
2689          if ( ! $user ) {
2690              return $this->error;
2691          }
2692  
2693          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2694          do_action( 'xmlrpc_call', 'wp.getUsers' );
2695  
2696          if ( ! current_user_can( 'list_users' ) ) {
2697              return new IXR_Error( 401, __( 'Sorry, you are not allowed to list users.' ) );
2698          }
2699  
2700          $query = array( 'fields' => 'all_with_meta' );
2701  
2702          $query['number'] = ( isset( $filter['number'] ) ) ? absint( $filter['number'] ) : 50;
2703          $query['offset'] = ( isset( $filter['offset'] ) ) ? absint( $filter['offset'] ) : 0;
2704  
2705          if ( isset( $filter['orderby'] ) ) {
2706              $query['orderby'] = $filter['orderby'];
2707  
2708              if ( isset( $filter['order'] ) ) {
2709                  $query['order'] = $filter['order'];
2710              }
2711          }
2712  
2713          if ( isset( $filter['role'] ) ) {
2714              if ( get_role( $filter['role'] ) === null ) {
2715                  return new IXR_Error( 403, __( 'Invalid role.' ) );
2716              }
2717  
2718              $query['role'] = $filter['role'];
2719          }
2720  
2721          if ( isset( $filter['who'] ) ) {
2722              $query['who'] = $filter['who'];
2723          }
2724  
2725          $users = get_users( $query );
2726  
2727          $_users = array();
2728          foreach ( $users as $user_data ) {
2729              if ( current_user_can( 'edit_user', $user_data->ID ) ) {
2730                  $_users[] = $this->_prepare_user( $user_data, $fields );
2731              }
2732          }
2733          return $_users;
2734      }
2735  
2736      /**
2737       * Retrieve information about the requesting user.
2738       *
2739       * @uses get_userdata()
2740       *
2741       * @param array  $args {
2742       *     Method arguments. Note: arguments must be ordered as documented.
2743       *
2744       *     @type int    $blog_id (unused)
2745       *     @type string $username
2746       *     @type string $password
2747       *     @type array  $fields (optional)
2748       * }
2749       * @return array|IXR_Error (@see wp_getUser)
2750       */
2751  	public function wp_getProfile( $args ) {
2752          if ( ! $this->minimum_args( $args, 3 ) ) {
2753              return $this->error;
2754          }
2755  
2756          $this->escape( $args );
2757  
2758          $username = $args[1];
2759          $password = $args[2];
2760  
2761          if ( isset( $args[3] ) ) {
2762              $fields = $args[3];
2763          } else {
2764              /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2765              $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getProfile' );
2766          }
2767  
2768          $user = $this->login( $username, $password );
2769          if ( ! $user ) {
2770              return $this->error;
2771          }
2772  
2773          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2774          do_action( 'xmlrpc_call', 'wp.getProfile' );
2775  
2776          if ( ! current_user_can( 'edit_user', $user->ID ) ) {
2777              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit your profile.' ) );
2778          }
2779  
2780          $user_data = get_userdata( $user->ID );
2781  
2782          return $this->_prepare_user( $user_data, $fields );
2783      }
2784  
2785      /**
2786       * Edit user's profile.
2787       *
2788       * @uses wp_update_user()
2789       *
2790       * @param array  $args {
2791       *     Method arguments. Note: arguments must be ordered as documented.
2792       *
2793       *     @type int    $blog_id (unused)
2794       *     @type string $username
2795       *     @type string $password
2796       *     @type array  $content_struct It can optionally contain:
2797       *      - 'first_name'
2798       *      - 'last_name'
2799       *      - 'website'
2800       *      - 'display_name'
2801       *      - 'nickname'
2802       *      - 'nicename'
2803       *      - 'bio'
2804       * }
2805       * @return true|IXR_Error True, on success.
2806       */
2807  	public function wp_editProfile( $args ) {
2808          if ( ! $this->minimum_args( $args, 4 ) ) {
2809              return $this->error;
2810          }
2811  
2812          $this->escape( $args );
2813  
2814          $username       = $args[1];
2815          $password       = $args[2];
2816          $content_struct = $args[3];
2817  
2818          $user = $this->login( $username, $password );
2819          if ( ! $user ) {
2820              return $this->error;
2821          }
2822  
2823          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2824          do_action( 'xmlrpc_call', 'wp.editProfile' );
2825  
2826          if ( ! current_user_can( 'edit_user', $user->ID ) ) {
2827              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit your profile.' ) );
2828          }
2829  
2830          // holds data of the user
2831          $user_data       = array();
2832          $user_data['ID'] = $user->ID;
2833  
2834          // only set the user details if it was given
2835          if ( isset( $content_struct['first_name'] ) ) {
2836              $user_data['first_name'] = $content_struct['first_name'];
2837          }
2838  
2839          if ( isset( $content_struct['last_name'] ) ) {
2840              $user_data['last_name'] = $content_struct['last_name'];
2841          }
2842  
2843          if ( isset( $content_struct['url'] ) ) {
2844              $user_data['user_url'] = $content_struct['url'];
2845          }
2846  
2847          if ( isset( $content_struct['display_name'] ) ) {
2848              $user_data['display_name'] = $content_struct['display_name'];
2849          }
2850  
2851          if ( isset( $content_struct['nickname'] ) ) {
2852              $user_data['nickname'] = $content_struct['nickname'];
2853          }
2854  
2855          if ( isset( $content_struct['nicename'] ) ) {
2856              $user_data['user_nicename'] = $content_struct['nicename'];
2857          }
2858  
2859          if ( isset( $content_struct['bio'] ) ) {
2860              $user_data['description'] = $content_struct['bio'];
2861          }
2862  
2863          $result = wp_update_user( $user_data );
2864  
2865          if ( is_wp_error( $result ) ) {
2866              return new IXR_Error( 500, $result->get_error_message() );
2867          }
2868  
2869          if ( ! $result ) {
2870              return new IXR_Error( 500, __( 'Sorry, the user cannot be updated.' ) );
2871          }
2872  
2873          return true;
2874      }
2875  
2876      /**
2877       * Retrieve page.
2878       *
2879       * @since 2.2.0
2880       *
2881       * @param array  $args {
2882       *     Method arguments. Note: arguments must be ordered as documented.
2883       *
2884       *     @type int    $blog_id (unused)
2885       *     @type int    $page_id
2886       *     @type string $username
2887       *     @type string $password
2888       * }
2889       * @return array|IXR_Error
2890       */
2891  	public function wp_getPage( $args ) {
2892          $this->escape( $args );
2893  
2894          $page_id  = (int) $args[1];
2895          $username = $args[2];
2896          $password = $args[3];
2897  
2898          $user = $this->login( $username, $password );
2899          if ( ! $user ) {
2900              return $this->error;
2901          }
2902  
2903          $page = get_post( $page_id );
2904          if ( ! $page ) {
2905              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
2906          }
2907  
2908          if ( ! current_user_can( 'edit_page', $page_id ) ) {
2909              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this page.' ) );
2910          }
2911  
2912          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2913          do_action( 'xmlrpc_call', 'wp.getPage' );
2914  
2915          // If we found the page then format the data.
2916          if ( $page->ID && ( $page->post_type == 'page' ) ) {
2917              return $this->_prepare_page( $page );
2918          } else {
2919              // If the page doesn't exist indicate that.
2920              return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
2921          }
2922      }
2923  
2924      /**
2925       * Retrieve Pages.
2926       *
2927       * @since 2.2.0
2928       *
2929       * @param array  $args {
2930       *     Method arguments. Note: arguments must be ordered as documented.
2931       *
2932       *     @type int    $blog_id (unused)
2933       *     @type string $username
2934       *     @type string $password
2935       *     @type int    $num_pages
2936       * }
2937       * @return array|IXR_Error
2938       */
2939  	public function wp_getPages( $args ) {
2940          $this->escape( $args );
2941  
2942          $username  = $args[1];
2943          $password  = $args[2];
2944          $num_pages = isset( $args[3] ) ? (int) $args[3] : 10;
2945  
2946          $user = $this->login( $username, $password );
2947          if ( ! $user ) {
2948              return $this->error;
2949          }
2950  
2951          if ( ! current_user_can( 'edit_pages' ) ) {
2952              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit pages.' ) );
2953          }
2954  
2955          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2956          do_action( 'xmlrpc_call', 'wp.getPages' );
2957  
2958          $pages     = get_posts(
2959              array(
2960                  'post_type'   => 'page',
2961                  'post_status' => 'any',
2962                  'numberposts' => $num_pages,
2963              )
2964          );
2965          $num_pages = count( $pages );
2966  
2967          // If we have pages, put together their info.
2968          if ( $num_pages >= 1 ) {
2969              $pages_struct = array();
2970  
2971              foreach ( $pages as $page ) {
2972                  if ( current_user_can( 'edit_page', $page->ID ) ) {
2973                      $pages_struct[] = $this->_prepare_page( $page );
2974                  }
2975              }
2976  
2977              return $pages_struct;
2978          }
2979  
2980          return array();
2981      }
2982  
2983      /**
2984       * Create new page.
2985       *
2986       * @since 2.2.0
2987       *
2988       * @see wp_xmlrpc_server::mw_newPost()
2989       *
2990       * @param array  $args {
2991       *     Method arguments. Note: arguments must be ordered as documented.
2992       *
2993       *     @type int    $blog_id (unused)
2994       *     @type string $username
2995       *     @type string $password
2996       *     @type array  $content_struct
2997       * }
2998       * @return int|IXR_Error
2999       */
3000  	public function wp_newPage( $args ) {
3001          // Items not escaped here will be escaped in newPost.
3002          $username = $this->escape( $args[1] );
3003          $password = $this->escape( $args[2] );
3004  
3005          $user = $this->login( $username, $password );
3006          if ( ! $user ) {
3007              return $this->error;
3008          }
3009  
3010          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3011          do_action( 'xmlrpc_call', 'wp.newPage' );
3012  
3013          // Mark this as content for a page.
3014          $args[3]['post_type'] = 'page';
3015  
3016          // Let mw_newPost do all of the heavy lifting.
3017          return $this->mw_newPost( $args );
3018      }
3019  
3020      /**
3021       * Delete page.
3022       *
3023       * @since 2.2.0
3024       *
3025       * @param array  $args {
3026       *     Method arguments. Note: arguments must be ordered as documented.
3027       *
3028       *     @type int    $blog_id (unused)
3029       *     @type string $username
3030       *     @type string $password
3031       *     @type int    $page_id
3032       * }
3033       * @return true|IXR_Error True, if success.
3034       */
3035  	public function wp_deletePage( $args ) {
3036          $this->escape( $args );
3037  
3038          $username = $args[1];
3039          $password = $args[2];
3040          $page_id  = (int) $args[3];
3041  
3042          $user = $this->login( $username, $password );
3043          if ( ! $user ) {
3044              return $this->error;
3045          }
3046  
3047          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3048          do_action( 'xmlrpc_call', 'wp.deletePage' );
3049  
3050          // Get the current page based on the page_id and
3051          // make sure it is a page and not a post.
3052          $actual_page = get_post( $page_id, ARRAY_A );
3053          if ( ! $actual_page || ( $actual_page['post_type'] != 'page' ) ) {
3054              return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
3055          }
3056  
3057          // Make sure the user can delete pages.
3058          if ( ! current_user_can( 'delete_page', $page_id ) ) {
3059              return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this page.' ) );
3060          }
3061  
3062          // Attempt to delete the page.
3063          $result = wp_delete_post( $page_id );
3064          if ( ! $result ) {
3065              return new IXR_Error( 500, __( 'Failed to delete the page.' ) );
3066          }
3067  
3068          /**
3069           * Fires after a page has been successfully deleted via XML-RPC.
3070           *
3071           * @since 3.4.0
3072           *
3073           * @param int   $page_id ID of the deleted page.
3074           * @param array $args    An array of arguments to delete the page.
3075           */
3076          do_action( 'xmlrpc_call_success_wp_deletePage', $page_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
3077  
3078          return true;
3079      }
3080  
3081      /**
3082       * Edit page.
3083       *
3084       * @since 2.2.0
3085       *
3086       * @param array  $args {
3087       *     Method arguments. Note: arguments must be ordered as documented.
3088       *
3089       *     @type int    $blog_id (unused)
3090       *     @type int    $page_id
3091       *     @type string $username
3092       *     @type string $password
3093       *     @type string $content
3094       *     @type string $publish
3095       * }
3096       * @return array|IXR_Error
3097       */
3098  	public function wp_editPage( $args ) {
3099          // Items will be escaped in mw_editPost.
3100          $page_id  = (int) $args[1];
3101          $username = $args[2];
3102          $password = $args[3];
3103          $content  = $args[4];
3104          $publish  = $args[5];
3105  
3106          $escaped_username = $this->escape( $username );
3107          $escaped_password = $this->escape( $password );
3108  
3109          $user = $this->login( $escaped_username, $escaped_password );
3110          if ( ! $user ) {
3111              return $this->error;
3112          }
3113  
3114          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3115          do_action( 'xmlrpc_call', 'wp.editPage' );
3116  
3117          // Get the page data and make sure it is a page.
3118          $actual_page = get_post( $page_id, ARRAY_A );
3119          if ( ! $actual_page || ( $actual_page['post_type'] != 'page' ) ) {
3120              return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
3121          }
3122  
3123          // Make sure the user is allowed to edit pages.
3124          if ( ! current_user_can( 'edit_page', $page_id ) ) {
3125              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this page.' ) );
3126          }
3127  
3128          // Mark this as content for a page.
3129          $content['post_type'] = 'page';
3130  
3131          // Arrange args in the way mw_editPost understands.
3132          $args = array(
3133              $page_id,
3134              $username,
3135              $password,
3136              $content,
3137              $publish,
3138          );
3139  
3140          // Let mw_editPost do all of the heavy lifting.
3141          return $this->mw_editPost( $args );
3142      }
3143  
3144      /**
3145       * Retrieve page list.
3146       *
3147       * @since 2.2.0
3148       *
3149       * @global wpdb $wpdb WordPress database abstraction object.
3150       *
3151       * @param array  $args {
3152       *     Method arguments. Note: arguments must be ordered as documented.
3153       *
3154       *     @type int    $blog_id (unused)
3155       *     @type string $username
3156       *     @type string $password
3157       * }
3158       * @return array|IXR_Error
3159       */
3160  	public function wp_getPageList( $args ) {
3161          global $wpdb;
3162  
3163          $this->escape( $args );
3164  
3165          $username = $args[1];
3166          $password = $args[2];
3167  
3168          $user = $this->login( $username, $password );
3169          if ( ! $user ) {
3170              return $this->error;
3171          }
3172  
3173          if ( ! current_user_can( 'edit_pages' ) ) {
3174              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit pages.' ) );
3175          }
3176  
3177          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3178          do_action( 'xmlrpc_call', 'wp.getPageList' );
3179  
3180          // Get list of pages ids and titles
3181          $page_list = $wpdb->get_results(
3182              "
3183              SELECT ID page_id,
3184                  post_title page_title,
3185                  post_parent page_parent_id,
3186                  post_date_gmt,
3187                  post_date,
3188                  post_status
3189              FROM {$wpdb->posts}
3190              WHERE post_type = 'page'
3191              ORDER BY ID
3192          "
3193          );
3194  
3195          // The date needs to be formatted properly.
3196          $num_pages = count( $page_list );
3197          for ( $i = 0; $i < $num_pages; $i++ ) {
3198              $page_list[ $i ]->dateCreated      = $this->_convert_date( $page_list[ $i ]->post_date );
3199              $page_list[ $i ]->date_created_gmt = $this->_convert_date_gmt( $page_list[ $i ]->post_date_gmt, $page_list[ $i ]->post_date );
3200  
3201              unset( $page_list[ $i ]->post_date_gmt );
3202              unset( $page_list[ $i ]->post_date );
3203              unset( $page_list[ $i ]->post_status );
3204          }
3205  
3206          return $page_list;
3207      }
3208  
3209      /**
3210       * Retrieve authors list.
3211       *
3212       * @since 2.2.0
3213       *
3214       * @param array  $args {
3215       *     Method arguments. Note: arguments must be ordered as documented.
3216       *
3217       *     @type int    $blog_id (unused)
3218       *     @type string $username
3219       *     @type string $password
3220       * }
3221       * @return array|IXR_Error
3222       */
3223  	public function wp_getAuthors( $args ) {
3224          $this->escape( $args );
3225  
3226          $username = $args[1];
3227          $password = $args[2];
3228  
3229          $user = $this->login( $username, $password );
3230          if ( ! $user ) {
3231              return $this->error;
3232          }
3233  
3234          if ( ! current_user_can( 'edit_posts' ) ) {
3235              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
3236          }
3237  
3238          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3239          do_action( 'xmlrpc_call', 'wp.getAuthors' );
3240  
3241          $authors = array();
3242          foreach ( get_users( array( 'fields' => array( 'ID', 'user_login', 'display_name' ) ) ) as $user ) {
3243              $authors[] = array(
3244                  'user_id'      => $user->ID,
3245                  'user_login'   => $user->user_login,
3246                  'display_name' => $user->display_name,
3247              );
3248          }
3249  
3250          return $authors;
3251      }
3252  
3253      /**
3254       * Get list of all tags
3255       *
3256       * @since 2.7.0
3257       *
3258       * @param array  $args {
3259       *     Method arguments. Note: arguments must be ordered as documented.
3260       *
3261       *     @type int    $blog_id (unused)
3262       *     @type string $username
3263       *     @type string $password
3264       * }
3265       * @return array|IXR_Error
3266       */
3267  	public function wp_getTags( $args ) {
3268          $this->escape( $args );
3269  
3270          $username = $args[1];
3271          $password = $args[2];
3272  
3273          $user = $this->login( $username, $password );
3274          if ( ! $user ) {
3275              return $this->error;
3276          }
3277  
3278          if ( ! current_user_can( 'edit_posts' ) ) {
3279              return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view tags.' ) );
3280          }
3281  
3282          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3283          do_action( 'xmlrpc_call', 'wp.getKeywords' );
3284  
3285          $tags = array();
3286  
3287          $all_tags = get_tags();
3288          if ( $all_tags ) {
3289              foreach ( (array) $all_tags as $tag ) {
3290                  $struct             = array();
3291                  $struct['tag_id']   = $tag->term_id;
3292                  $struct['name']     = $tag->name;
3293                  $struct['count']    = $tag->count;
3294                  $struct['slug']     = $tag->slug;
3295                  $struct['html_url'] = esc_html( get_tag_link( $tag->term_id ) );
3296                  $struct['rss_url']  = esc_html( get_tag_feed_link( $tag->term_id ) );
3297  
3298                  $tags[] = $struct;
3299              }
3300          }
3301  
3302          return $tags;
3303      }
3304  
3305      /**
3306       * Create new category.
3307       *
3308       * @since 2.2.0
3309       *
3310       * @param array  $args {
3311       *     Method arguments. Note: arguments must be ordered as documented.
3312       *
3313       *     @type int    $blog_id (unused)
3314       *     @type string $username
3315       *     @type string $password
3316       *     @type array  $category
3317       * }
3318       * @return int|IXR_Error Category ID.
3319       */
3320  	public function wp_newCategory( $args ) {
3321          $this->escape( $args );
3322  
3323          $username = $args[1];
3324          $password = $args[2];
3325          $category = $args[3];
3326  
3327          $user = $this->login( $username, $password );
3328          if ( ! $user ) {
3329              return $this->error;
3330          }
3331  
3332          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3333          do_action( 'xmlrpc_call', 'wp.newCategory' );
3334  
3335          // Make sure the user is allowed to add a category.
3336          if ( ! current_user_can( 'manage_categories' ) ) {
3337              return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a category.' ) );
3338          }
3339  
3340          // If no slug was provided make it empty so that
3341          // WordPress will generate one.
3342          if ( empty( $category['slug'] ) ) {
3343              $category['slug'] = '';
3344          }
3345  
3346          // If no parent_id was provided make it empty
3347          // so that it will be a top level page (no parent).
3348          if ( ! isset( $category['parent_id'] ) ) {
3349              $category['parent_id'] = '';
3350          }
3351  
3352          // If no description was provided make it empty.
3353          if ( empty( $category['description'] ) ) {
3354              $category['description'] = '';
3355          }
3356  
3357          $new_category = array(
3358              'cat_name'             => $category['name'],
3359              'category_nicename'    => $category['slug'],
3360              'category_parent'      => $category['parent_id'],
3361              'category_description' => $category['description'],
3362          );
3363  
3364          $cat_id = wp_insert_category( $new_category, true );
3365          if ( is_wp_error( $cat_id ) ) {
3366              if ( 'term_exists' == $cat_id->get_error_code() ) {
3367                  return (int) $cat_id->get_error_data();
3368              } else {
3369                  return new IXR_Error( 500, __( 'Sorry, the new category failed.' ) );
3370              }
3371          } elseif ( ! $cat_id ) {
3372              return new IXR_Error( 500, __( 'Sorry, the new category failed.' ) );
3373          }
3374  
3375          /**
3376           * Fires after a new category has been successfully created via XML-RPC.
3377           *
3378           * @since 3.4.0
3379           *
3380           * @param int   $cat_id ID of the new category.
3381           * @param array $args   An array of new category arguments.
3382           */
3383          do_action( 'xmlrpc_call_success_wp_newCategory', $cat_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
3384  
3385          return $cat_id;
3386      }
3387  
3388      /**
3389       * Remove category.
3390       *
3391       * @since 2.5.0
3392       *
3393       * @param array  $args {
3394       *     Method arguments. Note: arguments must be ordered as documented.
3395       *
3396       *     @type int    $blog_id (unused)
3397       *     @type string $username
3398       *     @type string $password
3399       *     @type int    $category_id
3400       * }
3401       * @return bool|IXR_Error See wp_delete_term() for return info.
3402       */
3403  	public function wp_deleteCategory( $args ) {
3404          $this->escape( $args );
3405  
3406          $username    = $args[1];
3407          $password    = $args[2];
3408          $category_id = (int) $args[3];
3409  
3410          $user = $this->login( $username, $password );
3411          if ( ! $user ) {
3412              return $this->error;
3413          }
3414  
3415          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3416          do_action( 'xmlrpc_call', 'wp.deleteCategory' );
3417  
3418          if ( ! current_user_can( 'delete_term', $category_id ) ) {
3419              return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this category.' ) );
3420          }
3421  
3422          $status = wp_delete_term( $category_id, 'category' );
3423  
3424          if ( true == $status ) {
3425              /**
3426               * Fires after a category has been successfully deleted via XML-RPC.
3427               *
3428               * @since 3.4.0
3429               *
3430               * @param int   $category_id ID of the deleted category.
3431               * @param array $args        An array of arguments to delete the category.
3432               */
3433              do_action( 'xmlrpc_call_success_wp_deleteCategory', $category_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
3434          }
3435  
3436          return $status;
3437      }
3438  
3439      /**
3440       * Retrieve category list.
3441       *
3442       * @since 2.2.0
3443       *
3444       * @param array  $args {
3445       *     Method arguments. Note: arguments must be ordered as documented.
3446       *
3447       *     @type int    $blog_id (unused)
3448       *     @type string $username
3449       *     @type string $password
3450       *     @type array  $category
3451       *     @type int    $max_results
3452       * }
3453       * @return array|IXR_Error
3454       */
3455  	public function wp_suggestCategories( $args ) {
3456          $this->escape( $args );
3457  
3458          $username    = $args[1];
3459          $password    = $args[2];
3460          $category    = $args[3];
3461          $max_results = (int) $args[4];
3462  
3463          $user = $this->login( $username, $password );
3464          if ( ! $user ) {
3465              return $this->error;
3466          }
3467  
3468          if ( ! current_user_can( 'edit_posts' ) ) {
3469              return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
3470          }
3471  
3472          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3473          do_action( 'xmlrpc_call', 'wp.suggestCategories' );
3474  
3475          $category_suggestions = array();
3476          $args                 = array(
3477              'get'        => 'all',
3478              'number'     => $max_results,
3479              'name__like' => $category,
3480          );
3481          foreach ( (array) get_categories( $args ) as $cat ) {
3482              $category_suggestions[] = array(
3483                  'category_id'   => $cat->term_id,
3484                  'category_name' => $cat->name,
3485              );
3486          }
3487  
3488          return $category_suggestions;
3489      }
3490  
3491      /**
3492       * Retrieve comment.
3493       *
3494       * @since 2.7.0
3495       *
3496       * @param array  $args {
3497       *     Method arguments. Note: arguments must be ordered as documented.
3498       *
3499       *     @type int    $blog_id (unused)
3500       *     @type string $username
3501       *     @type string $password
3502       *     @type int    $comment_id
3503       * }
3504       * @return array|IXR_Error
3505       */
3506  	public function wp_getComment( $args ) {
3507          $this->escape( $args );
3508  
3509          $username   = $args[1];
3510          $password   = $args[2];
3511          $comment_id = (int) $args[3];
3512  
3513          $user = $this->login( $username, $password );
3514          if ( ! $user ) {
3515              return $this->error;
3516          }
3517  
3518          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3519          do_action( 'xmlrpc_call', 'wp.getComment' );
3520  
3521          $comment = get_comment( $comment_id );
3522          if ( ! $comment ) {
3523              return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
3524          }
3525  
3526          if ( ! current_user_can( 'edit_comment', $comment_id ) ) {
3527              return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) );
3528          }
3529  
3530          return $this->_prepare_comment( $comment );
3531      }
3532  
3533      /**
3534       * Retrieve comments.
3535       *
3536       * Besides the common blog_id (unused), username, and password arguments, it takes a filter
3537       * array as last argument.
3538       *
3539       * Accepted 'filter' keys are 'status', 'post_id', 'offset', and 'number'.
3540       *
3541       * The defaults are as follows:
3542       * - 'status' - Default is ''. Filter by status (e.g., 'approve', 'hold')
3543       * - 'post_id' - Default is ''. The post where the comment is posted. Empty string shows all comments.
3544       * - 'number' - Default is 10. Total number of media items to retrieve.
3545       * - 'offset' - Default is 0. See WP_Query::query() for more.
3546       *
3547       * @since 2.7.0
3548       *
3549       * @param array  $args {
3550       *     Method arguments. Note: arguments must be ordered as documented.
3551       *
3552       *     @type int    $blog_id (unused)
3553       *     @type string $username
3554       *     @type string $password
3555       *     @type array  $struct
3556       * }
3557       * @return array|IXR_Error Contains a collection of comments. See wp_xmlrpc_server::wp_getComment() for a description of each item contents
3558       */
3559  	public function wp_getComments( $args ) {
3560          $this->escape( $args );
3561  
3562          $username = $args[1];
3563          $password = $args[2];
3564          $struct   = isset( $args[3] ) ? $args[3] : array();
3565  
3566          $user = $this->login( $username, $password );
3567          if ( ! $user ) {
3568              return $this->error;
3569          }
3570  
3571          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3572          do_action( 'xmlrpc_call', 'wp.getComments' );
3573  
3574          if ( isset( $struct['status'] ) ) {
3575              $status = $struct['status'];
3576          } else {
3577              $status = '';
3578          }
3579  
3580          if ( ! current_user_can( 'moderate_comments' ) && 'approve' !== $status ) {
3581              return new IXR_Error( 401, __( 'Invalid comment status.' ) );
3582          }
3583  
3584          $post_id = '';
3585          if ( isset( $struct['post_id'] ) ) {
3586              $post_id = absint( $struct['post_id'] );
3587          }
3588  
3589          $post_type = '';
3590          if ( isset( $struct['post_type'] ) ) {
3591              $post_type_object = get_post_type_object( $struct['post_type'] );
3592              if ( ! $post_type_object || ! post_type_supports( $post_type_object->name, 'comments' ) ) {
3593                  return new IXR_Error( 404, __( 'Invalid post type.' ) );
3594              }
3595              $post_type = $struct['post_type'];
3596          }
3597  
3598          $offset = 0;
3599          if ( isset( $struct['offset'] ) ) {
3600              $offset = absint( $struct['offset'] );
3601          }
3602  
3603          $number = 10;
3604          if ( isset( $struct['number'] ) ) {
3605              $number = absint( $struct['number'] );
3606          }
3607  
3608          $comments = get_comments(
3609              array(
3610                  'status'    => $status,
3611                  'post_id'   => $post_id,
3612                  'offset'    => $offset,
3613                  'number'    => $number,
3614                  'post_type' => $post_type,
3615              )
3616          );
3617  
3618          $comments_struct = array();
3619          if ( is_array( $comments ) ) {
3620              foreach ( $comments as $comment ) {
3621                  $comments_struct[] = $this->_prepare_comment( $comment );
3622              }
3623          }
3624  
3625          return $comments_struct;
3626      }
3627  
3628      /**
3629       * Delete a comment.
3630       *
3631       * By default, the comment will be moved to the trash instead of deleted.
3632       * See wp_delete_comment() for more information on this behavior.
3633       *
3634       * @since 2.7.0
3635       *
3636       * @param array  $args {
3637       *     Method arguments. Note: arguments must be ordered as documented.
3638       *
3639       *     @type int    $blog_id (unused)
3640       *     @type string $username
3641       *     @type string $password
3642       *     @type int    $comment_ID
3643       * }
3644       * @return bool|IXR_Error See wp_delete_comment().
3645       */
3646  	public function wp_deleteComment( $args ) {
3647          $this->escape( $args );
3648  
3649          $username   = $args[1];
3650          $password   = $args[2];
3651          $comment_ID = (int) $args[3];
3652  
3653          $user = $this->login( $username, $password );
3654          if ( ! $user ) {
3655              return $this->error;
3656          }
3657  
3658          if ( ! get_comment( $comment_ID ) ) {
3659              return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
3660          }
3661  
3662          if ( ! current_user_can( 'edit_comment', $comment_ID ) ) {
3663              return new IXR_Error( 403, __( 'Sorry, you are not allowed to delete this comment.' ) );
3664          }
3665  
3666          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3667          do_action( 'xmlrpc_call', 'wp.deleteComment' );
3668  
3669          $status = wp_delete_comment( $comment_ID );
3670  
3671          if ( $status ) {
3672              /**
3673               * Fires after a comment has been successfully deleted via XML-RPC.
3674               *
3675               * @since 3.4.0
3676               *
3677               * @param int   $comment_ID ID of the deleted comment.
3678               * @param array $args       An array of arguments to delete the comment.
3679               */
3680              do_action( 'xmlrpc_call_success_wp_deleteComment', $comment_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
3681          }
3682  
3683          return $status;
3684      }
3685  
3686      /**
3687       * Edit comment.
3688       *
3689       * Besides the common blog_id (unused), username, and password arguments, it takes a
3690       * comment_id integer and a content_struct array as last argument.
3691       *
3692       * The allowed keys in the content_struct array are:
3693       *  - 'author'
3694       *  - 'author_url'
3695       *  - 'author_email'
3696       *  - 'content'
3697       *  - 'date_created_gmt'
3698       *  - 'status'. Common statuses are 'approve', 'hold', 'spam'. See get_comment_statuses() for more details
3699       *
3700       * @since 2.7.0
3701       *
3702       * @param array  $args {
3703       *     Method arguments. Note: arguments must be ordered as documented.
3704       *
3705       *     @type int    $blog_id (unused)
3706       *     @type string $username
3707       *     @type string $password
3708       *     @type int    $comment_ID
3709       *     @type array  $content_struct
3710       * }
3711       * @return true|IXR_Error True, on success.
3712       */
3713  	public function wp_editComment( $args ) {
3714          $this->escape( $args );
3715  
3716          $username       = $args[1];
3717          $password       = $args[2];
3718          $comment_ID     = (int) $args[3];
3719          $content_struct = $args[4];
3720  
3721          $user = $this->login( $username, $password );
3722          if ( ! $user ) {
3723              return $this->error;
3724          }
3725  
3726          if ( ! get_comment( $comment_ID ) ) {
3727              return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
3728          }
3729  
3730          if ( ! current_user_can( 'edit_comment', $comment_ID ) ) {
3731              return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) );
3732          }
3733  
3734          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3735          do_action( 'xmlrpc_call', 'wp.editComment' );
3736          $comment = array(
3737              'comment_ID' => $comment_ID,
3738          );
3739  
3740          if ( isset( $content_struct['status'] ) ) {
3741              $statuses = get_comment_statuses();
3742              $statuses = array_keys( $statuses );
3743  
3744              if ( ! in_array( $content_struct['status'], $statuses ) ) {
3745                  return new IXR_Error( 401, __( 'Invalid comment status.' ) );
3746              }
3747  
3748              $comment['comment_approved'] = $content_struct['status'];
3749          }
3750  
3751          // Do some timestamp voodoo
3752          if ( ! empty( $content_struct['date_created_gmt'] ) ) {
3753              // We know this is supposed to be GMT, so we're going to slap that Z on there by force
3754              $dateCreated                 = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
3755              $comment['comment_date']     = get_date_from_gmt( iso8601_to_datetime( $dateCreated ) );
3756              $comment['comment_date_gmt'] = iso8601_to_datetime( $dateCreated, 'GMT' );
3757          }
3758  
3759          if ( isset( $content_struct['content'] ) ) {
3760              $comment['comment_content'] = $content_struct['content'];
3761          }
3762  
3763          if ( isset( $content_struct['author'] ) ) {
3764              $comment['comment_author'] = $content_struct['author'];
3765          }
3766  
3767          if ( isset( $content_struct['author_url'] ) ) {
3768              $comment['comment_author_url'] = $content_struct['author_url'];
3769          }
3770  
3771          if ( isset( $content_struct['author_email'] ) ) {
3772              $comment['comment_author_email'] = $content_struct['author_email'];
3773          }
3774  
3775          $result = wp_update_comment( $comment );
3776          if ( is_wp_error( $result ) ) {
3777              return new IXR_Error( 500, $result->get_error_message() );
3778          }
3779  
3780          if ( ! $result ) {
3781              return new IXR_Error( 500, __( 'Sorry, the comment could not be edited.' ) );
3782          }
3783  
3784          /**
3785           * Fires after a comment has been successfully updated via XML-RPC.
3786           *
3787           * @since 3.4.0
3788           *
3789           * @param int   $comment_ID ID of the updated comment.
3790           * @param array $args       An array of arguments to update the comment.
3791           */
3792          do_action( 'xmlrpc_call_success_wp_editComment', $comment_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
3793  
3794          return true;
3795      }
3796  
3797      /**
3798       * Create new comment.
3799       *
3800       * @since 2.7.0
3801       *
3802       * @param array  $args {
3803       *     Method arguments. Note: arguments must be ordered as documented.
3804       *
3805       *     @type int        $blog_id (unused)
3806       *     @type string     $username
3807       *     @type string     $password
3808       *     @type string|int $post
3809       *     @type array      $content_struct
3810       * }
3811       * @return int|IXR_Error See wp_new_comment().
3812       */
3813  	public function wp_newComment( $args ) {
3814          $this->escape( $args );
3815  
3816          $username       = $args[1];
3817          $password       = $args[2];
3818          $post           = $args[3];
3819          $content_struct = $args[4];
3820  
3821          /**
3822           * Filters whether to allow anonymous comments over XML-RPC.
3823           *
3824           * @since 2.7.0
3825           *
3826           * @param bool $allow Whether to allow anonymous commenting via XML-RPC.
3827           *                    Default false.
3828           */
3829          $allow_anon = apply_filters( 'xmlrpc_allow_anonymous_comments', false );
3830  
3831          $user = $this->login( $username, $password );
3832  
3833          if ( ! $user ) {
3834              $logged_in = false;
3835              if ( $allow_anon && get_option( 'comment_registration' ) ) {
3836                  return new IXR_Error( 403, __( 'You must be registered to comment.' ) );
3837              } elseif ( ! $allow_anon ) {
3838                  return $this->error;
3839              }
3840          } else {
3841              $logged_in = true;
3842          }
3843  
3844          if ( is_numeric( $post ) ) {
3845              $post_id = absint( $post );
3846          } else {
3847              $post_id = url_to_postid( $post );
3848          }
3849  
3850          if ( ! $post_id ) {
3851              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
3852          }
3853  
3854          if ( ! get_post( $post_id ) ) {
3855              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
3856          }
3857  
3858          if ( ! comments_open( $post_id ) ) {
3859              return new IXR_Error( 403, __( 'Sorry, comments are closed for this item.' ) );
3860          }
3861  
3862          if ( empty( $content_struct['content'] ) ) {
3863              return new IXR_Error( 403, __( 'Comment is required.' ) );
3864          }
3865  
3866          $comment = array(
3867              'comment_post_ID' => $post_id,
3868              'comment_content' => $content_struct['content'],
3869          );
3870  
3871          if ( $logged_in ) {
3872              $display_name = $user->display_name;
3873              $user_email   = $user->user_email;
3874              $user_url     = $user->user_url;
3875  
3876              $comment['comment_author']       = $this->escape( $display_name );
3877              $comment['comment_author_email'] = $this->escape( $user_email );
3878              $comment['comment_author_url']   = $this->escape( $user_url );
3879              $comment['user_ID']              = $user->ID;
3880          } else {
3881              $comment['comment_author'] = '';
3882              if ( isset( $content_struct['author'] ) ) {
3883                  $comment['comment_author'] = $content_struct['author'];
3884              }
3885  
3886              $comment['comment_author_email'] = '';
3887              if ( isset( $content_struct['author_email'] ) ) {
3888                  $comment['comment_author_email'] = $content_struct['author_email'];
3889              }
3890  
3891              $comment['comment_author_url'] = '';
3892              if ( isset( $content_struct['author_url'] ) ) {
3893                  $comment['comment_author_url'] = $content_struct['author_url'];
3894              }
3895  
3896              $comment['user_ID'] = 0;
3897  
3898              if ( get_option( 'require_name_email' ) ) {
3899                  if ( 6 > strlen( $comment['comment_author_email'] ) || '' == $comment['comment_author'] ) {
3900                      return new IXR_Error( 403, __( 'Comment author name and email are required.' ) );
3901                  } elseif ( ! is_email( $comment['comment_author_email'] ) ) {
3902                      return new IXR_Error( 403, __( 'A valid email address is required.' ) );
3903                  }
3904              }
3905          }
3906  
3907          $comment['comment_parent'] = isset( $content_struct['comment_parent'] ) ? absint( $content_struct['comment_parent'] ) : 0;
3908  
3909          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3910          do_action( 'xmlrpc_call', 'wp.newComment' );
3911  
3912          $comment_ID = wp_new_comment( $comment, true );
3913          if ( is_wp_error( $comment_ID ) ) {
3914              return new IXR_Error( 403, $comment_ID->get_error_message() );
3915          }
3916  
3917          if ( ! $comment_ID ) {
3918              return new IXR_Error( 403, __( 'Something went wrong.' ) );
3919          }
3920  
3921          /**
3922           * Fires after a new comment has been successfully created via XML-RPC.
3923           *
3924           * @since 3.4.0
3925           *
3926           * @param int   $comment_ID ID of the new comment.
3927           * @param array $args       An array of new comment arguments.
3928           */
3929          do_action( 'xmlrpc_call_success_wp_newComment', $comment_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
3930  
3931          return $comment_ID;
3932      }
3933  
3934      /**
3935       * Retrieve all of the comment status.
3936       *
3937       * @since 2.7.0
3938       *
3939       * @param array  $args {
3940       *     Method arguments. Note: arguments must be ordered as documented.
3941       *
3942       *     @type int    $blog_id (unused)
3943       *     @type string $username
3944       *     @type string $password
3945       * }
3946       * @return array|IXR_Error
3947       */
3948  	public function wp_getCommentStatusList( $args ) {
3949          $this->escape( $args );
3950  
3951          $username = $args[1];
3952          $password = $args[2];
3953  
3954          $user = $this->login( $username, $password );
3955          if ( ! $user ) {
3956              return $this->error;
3957          }
3958  
3959          if ( ! current_user_can( 'publish_posts' ) ) {
3960              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
3961          }
3962  
3963          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3964          do_action( 'xmlrpc_call', 'wp.getCommentStatusList' );
3965  
3966          return get_comment_statuses();
3967      }
3968  
3969      /**
3970       * Retrieve comment count.
3971       *
3972       * @since 2.5.0
3973       *
3974       * @param array  $args {
3975       *     Method arguments. Note: arguments must be ordered as documented.
3976       *
3977       *     @type int    $blog_id (unused)
3978       *     @type string $username
3979       *     @type string $password
3980       *     @type int    $post_id
3981       * }
3982       * @return array|IXR_Error
3983       */
3984  	public function wp_getCommentCount( $args ) {
3985          $this->escape( $args );
3986  
3987          $username = $args[1];
3988          $password = $args[2];
3989          $post_id  = (int) $args[3];
3990  
3991          $user = $this->login( $username, $password );
3992          if ( ! $user ) {
3993              return $this->error;
3994          }
3995  
3996          $post = get_post( $post_id, ARRAY_A );
3997          if ( empty( $post['ID'] ) ) {
3998              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
3999          }
4000  
4001          if ( ! current_user_can( 'edit_post', $post_id ) ) {
4002              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details of this post.' ) );
4003          }
4004  
4005          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4006          do_action( 'xmlrpc_call', 'wp.getCommentCount' );
4007  
4008          $count = wp_count_comments( $post_id );
4009  
4010          return array(
4011              'approved'            => $count->approved,
4012              'awaiting_moderation' => $count->moderated,
4013              'spam'                => $count->spam,
4014              'total_comments'      => $count->total_comments,
4015          );
4016      }
4017  
4018      /**
4019       * Retrieve post statuses.
4020       *
4021       * @since 2.5.0
4022       *
4023       * @param array  $args {
4024       *     Method arguments. Note: arguments must be ordered as documented.
4025       *
4026       *     @type int    $blog_id (unused)
4027       *     @type string $username
4028       *     @type string $password
4029       * }
4030       * @return array|IXR_Error
4031       */
4032  	public function wp_getPostStatusList( $args ) {
4033          $this->escape( $args );
4034  
4035          $username = $args[1];
4036          $password = $args[2];
4037  
4038          $user = $this->login( $username, $password );
4039          if ( ! $user ) {
4040              return $this->error;
4041          }
4042  
4043          if ( ! current_user_can( 'edit_posts' ) ) {
4044              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4045          }
4046  
4047          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4048          do_action( 'xmlrpc_call', 'wp.getPostStatusList' );
4049  
4050          return get_post_statuses();
4051      }
4052  
4053      /**
4054       * Retrieve page statuses.
4055       *
4056       * @since 2.5.0
4057       *
4058       * @param array  $args {
4059       *     Method arguments. Note: arguments must be ordered as documented.
4060       *
4061       *     @type int    $blog_id (unused)
4062       *     @type string $username
4063       *     @type string $password
4064       * }
4065       * @return array|IXR_Error
4066       */
4067  	public function wp_getPageStatusList( $args ) {
4068          $this->escape( $args );
4069  
4070          $username = $args[1];
4071          $password = $args[2];
4072  
4073          $user = $this->login( $username, $password );
4074          if ( ! $user ) {
4075              return $this->error;
4076          }
4077  
4078          if ( ! current_user_can( 'edit_pages' ) ) {
4079              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4080          }
4081  
4082          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4083          do_action( 'xmlrpc_call', 'wp.getPageStatusList' );
4084  
4085          return get_page_statuses();
4086      }
4087  
4088      /**
4089       * Retrieve page templates.
4090       *
4091       * @since 2.6.0
4092       *
4093       * @param array  $args {
4094       *     Method arguments. Note: arguments must be ordered as documented.
4095       *
4096       *     @type int    $blog_id (unused)
4097       *     @type string $username
4098       *     @type string $password
4099       * }
4100       * @return array|IXR_Error
4101       */
4102  	public function wp_getPageTemplates( $args ) {
4103          $this->escape( $args );
4104  
4105          $username = $args[1];
4106          $password = $args[2];
4107  
4108          $user = $this->login( $username, $password );
4109          if ( ! $user ) {
4110              return $this->error;
4111          }
4112  
4113          if ( ! current_user_can( 'edit_pages' ) ) {
4114              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4115          }
4116  
4117          $templates            = get_page_templates();
4118          $templates['Default'] = 'default';
4119  
4120          return $templates;
4121      }
4122  
4123      /**
4124       * Retrieve blog options.
4125       *
4126       * @since 2.6.0
4127       *
4128       * @param array  $args {
4129       *     Method arguments. Note: arguments must be ordered as documented.
4130       *
4131       *     @type int    $blog_id (unused)
4132       *     @type string $username
4133       *     @type string $password
4134       *     @type array  $options
4135       * }
4136       * @return array|IXR_Error
4137       */
4138  	public function wp_getOptions( $args ) {
4139          $this->escape( $args );
4140  
4141          $username = $args[1];
4142          $password = $args[2];
4143          $options  = isset( $args[3] ) ? (array) $args[3] : array();
4144  
4145          $user = $this->login( $username, $password );
4146          if ( ! $user ) {
4147              return $this->error;
4148          }
4149  
4150          // If no specific options where asked for, return all of them
4151          if ( count( $options ) == 0 ) {
4152              $options = array_keys( $this->blog_options );
4153          }
4154  
4155          return $this->_getOptions( $options );
4156      }
4157  
4158      /**
4159       * Retrieve blog options value from list.
4160       *
4161       * @since 2.6.0
4162       *
4163       * @param array $options Options to retrieve.
4164       * @return array
4165       */
4166  	public function _getOptions( $options ) {
4167          $data       = array();
4168          $can_manage = current_user_can( 'manage_options' );
4169          foreach ( $options as $option ) {
4170              if ( array_key_exists( $option, $this->blog_options ) ) {
4171                  $data[ $option ] = $this->blog_options[ $option ];
4172                  //Is the value static or dynamic?
4173                  if ( isset( $data[ $option ]['option'] ) ) {
4174                      $data[ $option ]['value'] = get_option( $data[ $option ]['option'] );
4175                      unset( $data[ $option ]['option'] );
4176                  }
4177  
4178                  if ( ! $can_manage ) {
4179                      $data[ $option ]['readonly'] = true;
4180                  }
4181              }
4182          }
4183  
4184          return $data;
4185      }
4186  
4187      /**
4188       * Update blog options.
4189       *
4190       * @since 2.6.0
4191       *
4192       * @param array  $args {
4193       *     Method arguments. Note: arguments must be ordered as documented.
4194       *
4195       *     @type int    $blog_id (unused)
4196       *     @type string $username
4197       *     @type string $password
4198       *     @type array  $options
4199       * }
4200       * @return array|IXR_Error
4201       */
4202  	public function wp_setOptions( $args ) {
4203          $this->escape( $args );
4204  
4205          $username = $args[1];
4206          $password = $args[2];
4207          $options  = (array) $args[3];
4208  
4209          $user = $this->login( $username, $password );
4210          if ( ! $user ) {
4211              return $this->error;
4212          }
4213  
4214          if ( ! current_user_can( 'manage_options' ) ) {
4215              return new IXR_Error( 403, __( 'Sorry, you are not allowed to update options.' ) );
4216          }
4217  
4218          $option_names = array();
4219          foreach ( $options as $o_name => $o_value ) {
4220              $option_names[] = $o_name;
4221              if ( ! array_key_exists( $o_name, $this->blog_options ) ) {
4222                  continue;
4223              }
4224  
4225              if ( $this->blog_options[ $o_name ]['readonly'] == true ) {
4226                  continue;
4227              }
4228  
4229              update_option( $this->blog_options[ $o_name ]['option'], wp_unslash( $o_value ) );
4230          }
4231  
4232          //Now return the updated values
4233          return $this->_getOptions( $option_names );
4234      }
4235  
4236      /**
4237       * Retrieve a media item by ID
4238       *
4239       * @since 3.1.0
4240       *
4241       * @param array  $args {
4242       *     Method arguments. Note: arguments must be ordered as documented.
4243       *
4244       *     @type int    $blog_id (unused)
4245       *     @type string $username
4246       *     @type string $password
4247       *     @type int    $attachment_id
4248       * }
4249       * @return array|IXR_Error Associative array contains:
4250       *  - 'date_created_gmt'
4251       *  - 'parent'
4252       *  - 'link'
4253       *  - 'thumbnail'
4254       *  - 'title'
4255       *  - 'caption'
4256       *  - 'description'
4257       *  - 'metadata'
4258       */
4259  	public function wp_getMediaItem( $args ) {
4260          $this->escape( $args );
4261  
4262          $username      = $args[1];
4263          $password      = $args[2];
4264          $attachment_id = (int) $args[3];
4265  
4266          $user = $this->login( $username, $password );
4267          if ( ! $user ) {
4268              return $this->error;
4269          }
4270  
4271          if ( ! current_user_can( 'upload_files' ) ) {
4272              return new IXR_Error( 403, __( 'Sorry, you are not allowed to upload files.' ) );
4273          }
4274  
4275          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4276          do_action( 'xmlrpc_call', 'wp.getMediaItem' );
4277  
4278          $attachment = get_post( $attachment_id );
4279          if ( ! $attachment ) {
4280              return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
4281          }
4282  
4283          return $this->_prepare_media_item( $attachment );
4284      }
4285  
4286      /**
4287       * Retrieves a collection of media library items (or attachments)
4288       *
4289       * Besides the common blog_id (unused), username, and password arguments, it takes a filter
4290       * array as last argument.
4291       *
4292       * Accepted 'filter' keys are 'parent_id', 'mime_type', 'offset', and 'number'.
4293       *
4294       * The defaults are as follows:
4295       * - 'number' - Default is 5. Total number of media items to retrieve.
4296       * - 'offset' - Default is 0. See WP_Query::query() for more.
4297       * - 'parent_id' - Default is ''. The post where the media item is attached. Empty string shows all media items. 0 shows unattached media items.
4298       * - 'mime_type' - Default is ''. Filter by mime type (e.g., 'image/jpeg', 'application/pdf')
4299       *
4300       * @since 3.1.0
4301       *
4302       * @param array  $args {
4303       *     Method arguments. Note: arguments must be ordered as documented.
4304       *
4305       *     @type int    $blog_id (unused)
4306       *     @type string $username
4307       *     @type string $password
4308       *     @type array  $struct
4309       * }
4310       * @return array|IXR_Error Contains a collection of media items. See wp_xmlrpc_server::wp_getMediaItem() for a description of each item contents
4311       */
4312  	public function wp_getMediaLibrary( $args ) {
4313          $this->escape( $args );
4314  
4315          $username = $args[1];
4316          $password = $args[2];
4317          $struct   = isset( $args[3] ) ? $args[3] : array();
4318  
4319          $user = $this->login( $username, $password );
4320          if ( ! $user ) {
4321              return $this->error;
4322          }
4323  
4324          if ( ! current_user_can( 'upload_files' ) ) {
4325              return new IXR_Error( 401, __( 'Sorry, you are not allowed to upload files.' ) );
4326          }
4327  
4328          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4329          do_action( 'xmlrpc_call', 'wp.getMediaLibrary' );
4330  
4331          $parent_id = ( isset( $struct['parent_id'] ) ) ? absint( $struct['parent_id'] ) : '';
4332          $mime_type = ( isset( $struct['mime_type'] ) ) ? $struct['mime_type'] : '';
4333          $offset    = ( isset( $struct['offset'] ) ) ? absint( $struct['offset'] ) : 0;
4334          $number    = ( isset( $struct['number'] ) ) ? absint( $struct['number'] ) : -1;
4335  
4336          $attachments = get_posts(
4337              array(
4338                  'post_type'      => 'attachment',
4339                  'post_parent'    => $parent_id,
4340                  'offset'         => $offset,
4341                  'numberposts'    => $number,
4342                  'post_mime_type' => $mime_type,
4343              )
4344          );
4345  
4346          $attachments_struct = array();
4347  
4348          foreach ( $attachments as $attachment ) {
4349              $attachments_struct[] = $this->_prepare_media_item( $attachment );
4350          }
4351  
4352          return $attachments_struct;
4353      }
4354  
4355      /**
4356       * Retrieves a list of post formats used by the site.
4357       *
4358       * @since 3.1.0
4359       *
4360       * @param array  $args {
4361       *     Method arguments. Note: arguments must be ordered as documented.
4362       *
4363       *     @type int    $blog_id (unused)
4364       *     @type string $username
4365       *     @type string $password
4366       * }
4367       * @return array|IXR_Error List of post formats, otherwise IXR_Error object.
4368       */
4369  	public function wp_getPostFormats( $args ) {
4370          $this->escape( $args );
4371  
4372          $username = $args[1];
4373          $password = $args[2];
4374  
4375          $user = $this->login( $username, $password );
4376          if ( ! $user ) {
4377              return $this->error;
4378          }
4379  
4380          if ( ! current_user_can( 'edit_posts' ) ) {
4381              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4382          }
4383  
4384          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4385          do_action( 'xmlrpc_call', 'wp.getPostFormats' );
4386  
4387          $formats = get_post_format_strings();
4388  
4389          // find out if they want a list of currently supports formats
4390          if ( isset( $args[3] ) && is_array( $args[3] ) ) {
4391              if ( $args[3]['show-supported'] ) {
4392                  if ( current_theme_supports( 'post-formats' ) ) {
4393                      $supported = get_theme_support( 'post-formats' );
4394  
4395                      $data              = array();
4396                      $data['all']       = $formats;
4397                      $data['supported'] = $supported[0];
4398  
4399                      $formats = $data;
4400                  }
4401              }
4402          }
4403  
4404          return $formats;
4405      }
4406  
4407      /**
4408       * Retrieves a post type
4409       *
4410       * @since 3.4.0
4411       *
4412       * @see get_post_type_object()
4413       *
4414       * @param array  $args {
4415       *     Method arguments. Note: arguments must be ordered as documented.
4416       *
4417       *     @type int    $blog_id (unused)
4418       *     @type string $username
4419       *     @type string $password
4420       *     @type string $post_type_name
4421       *     @type array  $fields (optional)
4422       * }
4423       * @return array|IXR_Error Array contains:
4424       *  - 'labels'
4425       *  - 'description'
4426       *  - 'capability_type'
4427       *  - 'cap'
4428       *  - 'map_meta_cap'
4429       *  - 'hierarchical'
4430       *  - 'menu_position'
4431       *  - 'taxonomies'
4432       *  - 'supports'
4433       */
4434  	public function wp_getPostType( $args ) {
4435          if ( ! $this->minimum_args( $args, 4 ) ) {
4436              return $this->error;
4437          }
4438  
4439          $this->escape( $args );
4440  
4441          $username       = $args[1];
4442          $password       = $args[2];
4443          $post_type_name = $args[3];
4444  
4445          if ( isset( $args[4] ) ) {
4446              $fields = $args[4];
4447          } else {
4448              /**
4449               * Filters the default query fields used by the given XML-RPC method.
4450               *
4451               * @since 3.4.0
4452               *
4453               * @param array  $fields An array of post type query fields for the given method.
4454               * @param string $method The method name.
4455               */
4456              $fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostType' );
4457          }
4458  
4459          $user = $this->login( $username, $password );
4460          if ( ! $user ) {
4461              return $this->error;
4462          }
4463  
4464          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4465          do_action( 'xmlrpc_call', 'wp.getPostType' );
4466  
4467          if ( ! post_type_exists( $post_type_name ) ) {
4468              return new IXR_Error( 403, __( 'Invalid post type.' ) );
4469          }
4470  
4471          $post_type = get_post_type_object( $post_type_name );
4472  
4473          if ( ! current_user_can( $post_type->cap->edit_posts ) ) {
4474              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type.' ) );
4475          }
4476  
4477          return $this->_prepare_post_type( $post_type, $fields );
4478      }
4479  
4480      /**
4481       * Retrieves a post types
4482       *
4483       * @since 3.4.0
4484       *
4485       * @see get_post_types()
4486       *
4487       * @param array  $args {
4488       *     Method arguments. Note: arguments must be ordered as documented.
4489       *
4490       *     @type int    $blog_id (unused)
4491       *     @type string $username
4492       *     @type string $password
4493       *     @type array  $filter (optional)
4494       *     @type array  $fields (optional)
4495       * }
4496       * @return array|IXR_Error
4497       */
4498  	public function wp_getPostTypes( $args ) {
4499          if ( ! $this->minimum_args( $args, 3 ) ) {
4500              return $this->error;
4501          }
4502  
4503          $this->escape( $args );
4504  
4505          $username = $args[1];
4506          $password = $args[2];
4507          $filter   = isset( $args[3] ) ? $args[3] : array( 'public' => true );
4508  
4509          if ( isset( $args[4] ) ) {
4510              $fields = $args[4];
4511          } else {
4512              /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4513              $fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostTypes' );
4514          }
4515  
4516          $user = $this->login( $username, $password );
4517          if ( ! $user ) {
4518              return $this->error;
4519          }
4520  
4521          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4522          do_action( 'xmlrpc_call', 'wp.getPostTypes' );
4523  
4524          $post_types = get_post_types( $filter, 'objects' );
4525  
4526          $struct = array();
4527  
4528          foreach ( $post_types as $post_type ) {
4529              if ( ! current_user_can( $post_type->cap->edit_posts ) ) {
4530                  continue;
4531              }
4532  
4533              $struct[ $post_type->name ] = $this->_prepare_post_type( $post_type, $fields );
4534          }
4535  
4536          return $struct;
4537      }
4538  
4539      /**
4540       * Retrieve revisions for a specific post.
4541       *
4542       * @since 3.5.0
4543       *
4544       * The optional $fields parameter specifies what fields will be included
4545       * in the response array.
4546       *
4547       * @uses wp_get_post_revisions()
4548       * @see wp_getPost() for more on $fields
4549       *
4550       * @param array  $args {
4551       *     Method arguments. Note: arguments must be ordered as documented.
4552       *
4553       *     @type int    $blog_id (unused)
4554       *     @type string $username
4555       *     @type string $password
4556       *     @type int    $post_id
4557       *     @type array  $fields (optional)
4558       * }
4559       * @return array|IXR_Error contains a collection of posts.
4560       */
4561  	public function wp_getRevisions( $args ) {
4562          if ( ! $this->minimum_args( $args, 4 ) ) {
4563              return $this->error;
4564          }
4565  
4566          $this->escape( $args );
4567  
4568          $username = $args[1];
4569          $password = $args[2];
4570          $post_id  = (int) $args[3];
4571  
4572          if ( isset( $args[4] ) ) {
4573              $fields = $args[4];
4574          } else {
4575              /**
4576               * Filters the default revision query fields used by the given XML-RPC method.
4577               *
4578               * @since 3.5.0
4579               *
4580               * @param array  $field  An array of revision query fields.
4581               * @param string $method The method name.
4582               */
4583              $fields = apply_filters( 'xmlrpc_default_revision_fields', array( 'post_date', 'post_date_gmt' ), 'wp.getRevisions' );
4584          }
4585  
4586          $user = $this->login( $username, $password );
4587          if ( ! $user ) {
4588              return $this->error;
4589          }
4590  
4591          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4592          do_action( 'xmlrpc_call', 'wp.getRevisions' );
4593  
4594          $post = get_post( $post_id );
4595          if ( ! $post ) {
4596              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4597          }
4598  
4599          if ( ! current_user_can( 'edit_post', $post_id ) ) {
4600              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
4601          }
4602  
4603          // Check if revisions are enabled.
4604          if ( ! wp_revisions_enabled( $post ) ) {
4605              return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) );
4606          }
4607  
4608          $revisions = wp_get_post_revisions( $post_id );
4609  
4610          if ( ! $revisions ) {
4611              return array();
4612          }
4613  
4614          $struct = array();
4615  
4616          foreach ( $revisions as $revision ) {
4617              if ( ! current_user_can( 'read_post', $revision->ID ) ) {
4618                  continue;
4619              }
4620  
4621              // Skip autosaves
4622              if ( wp_is_post_autosave( $revision ) ) {
4623                  continue;
4624              }
4625  
4626              $struct[] = $this->_prepare_post( get_object_vars( $revision ), $fields );
4627          }
4628  
4629          return $struct;
4630      }
4631  
4632      /**
4633       * Restore a post revision
4634       *
4635       * @since 3.5.0
4636       *
4637       * @uses wp_restore_post_revision()
4638       *
4639       * @param array  $args {
4640       *     Method arguments. Note: arguments must be ordered as documented.
4641       *
4642       *     @type int    $blog_id (unused)
4643       *     @type string $username
4644       *     @type string $password
4645       *     @type int    $revision_id
4646       * }
4647       * @return bool|IXR_Error false if there was an error restoring, true if success.
4648       */
4649  	public function wp_restoreRevision( $args ) {
4650          if ( ! $this->minimum_args( $args, 3 ) ) {
4651              return $this->error;
4652          }
4653  
4654          $this->escape( $args );
4655  
4656          $username    = $args[1];
4657          $password    = $args[2];
4658          $revision_id = (int) $args[3];
4659  
4660          $user = $this->login( $username, $password );
4661          if ( ! $user ) {
4662              return $this->error;
4663          }
4664  
4665          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4666          do_action( 'xmlrpc_call', 'wp.restoreRevision' );
4667  
4668          $revision = wp_get_post_revision( $revision_id );
4669          if ( ! $revision ) {
4670              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4671          }
4672  
4673          if ( wp_is_post_autosave( $revision ) ) {
4674              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4675          }
4676  
4677          $post = get_post( $revision->post_parent );
4678          if ( ! $post ) {
4679              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4680          }
4681  
4682          if ( ! current_user_can( 'edit_post', $revision->post_parent ) ) {
4683              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
4684          }
4685  
4686          // Check if revisions are disabled.
4687          if ( ! wp_revisions_enabled( $post ) ) {
4688              return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) );
4689          }
4690  
4691          $post = wp_restore_post_revision( $revision_id );
4692  
4693          return (bool) $post;
4694      }
4695  
4696      /* Blogger API functions.
4697       * specs on http://plant.blogger.com/api and https://groups.yahoo.com/group/bloggerDev/
4698       */
4699  
4700      /**
4701       * Retrieve blogs that user owns.
4702       *
4703       * Will make more sense once we support multiple blogs.
4704       *
4705       * @since 1.5.0
4706       *
4707       * @param array  $args {
4708       *     Method arguments. Note: arguments must be ordered as documented.
4709       *
4710       *     @type int    $blog_id (unused)
4711       *     @type string $username
4712       *     @type string $password
4713       * }
4714       * @return array|IXR_Error
4715       */
4716  	public function blogger_getUsersBlogs( $args ) {
4717          if ( ! $this->minimum_args( $args, 3 ) ) {
4718              return $this->error;
4719          }
4720  
4721          if ( is_multisite() ) {
4722              return $this->_multisite_getUsersBlogs( $args );
4723          }
4724  
4725          $this->escape( $args );
4726  
4727          $username = $args[1];
4728          $password = $args[2];
4729  
4730          $user = $this->login( $username, $password );
4731          if ( ! $user ) {
4732              return $this->error;
4733          }
4734  
4735          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4736          do_action( 'xmlrpc_call', 'blogger.getUsersBlogs' );
4737  
4738          $is_admin = current_user_can( 'manage_options' );
4739  
4740          $struct = array(
4741              'isAdmin'  => $is_admin,
4742              'url'      => get_option( 'home' ) . '/',
4743              'blogid'   => '1',
4744              'blogName' => get_option( 'blogname' ),
4745              'xmlrpc'   => site_url( 'xmlrpc.php', 'rpc' ),
4746          );
4747  
4748          return array( $struct );
4749      }
4750  
4751      /**
4752       * Private function for retrieving a users blogs for multisite setups
4753       *
4754       * @since 3.0.0
4755       *
4756       * @param array $args {
4757       *     Method arguments. Note: arguments must be ordered as documented.
4758       *
4759       *     @type string $username Username.
4760       *     @type string $password Password.
4761       * }
4762       * @return array|IXR_Error
4763       */
4764  	protected function _multisite_getUsersBlogs( $args ) {
4765          $current_blog = get_site();
4766  
4767          $domain = $current_blog->domain;
4768          $path   = $current_blog->path . 'xmlrpc.php';
4769  
4770          $rpc = new IXR_Client( set_url_scheme( "http://{$domain}{$path}" ) );
4771          $rpc->query( 'wp.getUsersBlogs', $args[1], $args[2] );
4772          $blogs = $rpc->getResponse();
4773  
4774          if ( isset( $blogs['faultCode'] ) ) {
4775              return new IXR_Error( $blogs['faultCode'], $blogs['faultString'] );
4776          }
4777  
4778          if ( $_SERVER['HTTP_HOST'] == $domain && $_SERVER['REQUEST_URI'] == $path ) {
4779              return $blogs;
4780          } else {
4781              foreach ( (array) $blogs as $blog ) {
4782                  if ( strpos( $blog['url'], $_SERVER['HTTP_HOST'] ) ) {
4783                      return array( $blog );
4784                  }
4785              }
4786              return array();
4787          }
4788      }
4789  
4790      /**
4791       * Retrieve user's data.
4792       *
4793       * Gives your client some info about you, so you don't have to.
4794       *
4795       * @since 1.5.0
4796       *
4797       * @param array  $args {
4798       *     Method arguments. Note: arguments must be ordered as documented.
4799       *
4800       *     @type int    $blog_id (unused)
4801       *     @type string $username
4802       *     @type string $password
4803       * }
4804       * @return array|IXR_Error
4805       */
4806  	public function blogger_getUserInfo( $args ) {
4807          $this->escape( $args );
4808  
4809          $username = $args[1];
4810          $password = $args[2];
4811  
4812          $user =