[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

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