[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

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