[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

   1  <?php
   2  /**
   3   * XML-RPC protocol support for WordPress
   4   *
   5   * @package WordPress
   6   * @subpackage Publishing
   7   */
   8  
   9  /**
  10   * WordPress XMLRPC server implementation.
  11   *
  12   * Implements compatibility for Blogger API, MetaWeblog API, MovableType, and
  13   * pingback. Additional WordPress API for managing comments, pages, posts,
  14   * options, etc.
  15   *
  16   * As of WordPress 3.5.0, XML-RPC is enabled by default. It can be disabled
  17   * via the {@see 'xmlrpc_enabled'} filter found in wp_xmlrpc_server::set_is_enabled().
  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       * Flags that XML-RPC is enabled
  54       *
  55       * @var bool
  56       */
  57      private $is_enabled;
  58  
  59      /**
  60       * Registers all of the XMLRPC methods that XMLRPC server understands.
  61       *
  62       * Sets up server and method property. Passes XMLRPC
  63       * methods through the {@see 'xmlrpc_methods'} filter to allow plugins to extend
  64       * or replace XML-RPC methods.
  65       *
  66       * @since 1.5.0
  67       */
  68  	public function __construct() {
  69          $this->methods = array(
  70              // WordPress API.
  71              'wp.getUsersBlogs'                 => 'this:wp_getUsersBlogs',
  72              'wp.newPost'                       => 'this:wp_newPost',
  73              'wp.editPost'                      => 'this:wp_editPost',
  74              'wp.deletePost'                    => 'this:wp_deletePost',
  75              'wp.getPost'                       => 'this:wp_getPost',
  76              'wp.getPosts'                      => 'this:wp_getPosts',
  77              'wp.newTerm'                       => 'this:wp_newTerm',
  78              'wp.editTerm'                      => 'this:wp_editTerm',
  79              'wp.deleteTerm'                    => 'this:wp_deleteTerm',
  80              'wp.getTerm'                       => 'this:wp_getTerm',
  81              'wp.getTerms'                      => 'this:wp_getTerms',
  82              'wp.getTaxonomy'                   => 'this:wp_getTaxonomy',
  83              'wp.getTaxonomies'                 => 'this:wp_getTaxonomies',
  84              'wp.getUser'                       => 'this:wp_getUser',
  85              'wp.getUsers'                      => 'this:wp_getUsers',
  86              'wp.getProfile'                    => 'this:wp_getProfile',
  87              'wp.editProfile'                   => 'this:wp_editProfile',
  88              'wp.getPage'                       => 'this:wp_getPage',
  89              'wp.getPages'                      => 'this:wp_getPages',
  90              'wp.newPage'                       => 'this:wp_newPage',
  91              'wp.deletePage'                    => 'this:wp_deletePage',
  92              'wp.editPage'                      => 'this:wp_editPage',
  93              'wp.getPageList'                   => 'this:wp_getPageList',
  94              'wp.getAuthors'                    => 'this:wp_getAuthors',
  95              'wp.getCategories'                 => 'this:mw_getCategories',     // Alias.
  96              'wp.getTags'                       => 'this:wp_getTags',
  97              'wp.newCategory'                   => 'this:wp_newCategory',
  98              'wp.deleteCategory'                => 'this:wp_deleteCategory',
  99              'wp.suggestCategories'             => 'this:wp_suggestCategories',
 100              'wp.uploadFile'                    => 'this:mw_newMediaObject',    // Alias.
 101              'wp.deleteFile'                    => 'this:wp_deletePost',        // Alias.
 102              'wp.getCommentCount'               => 'this:wp_getCommentCount',
 103              'wp.getPostStatusList'             => 'this:wp_getPostStatusList',
 104              'wp.getPageStatusList'             => 'this:wp_getPageStatusList',
 105              'wp.getPageTemplates'              => 'this:wp_getPageTemplates',
 106              'wp.getOptions'                    => 'this:wp_getOptions',
 107              'wp.setOptions'                    => 'this:wp_setOptions',
 108              'wp.getComment'                    => 'this:wp_getComment',
 109              'wp.getComments'                   => 'this:wp_getComments',
 110              'wp.deleteComment'                 => 'this:wp_deleteComment',
 111              'wp.editComment'                   => 'this:wp_editComment',
 112              'wp.newComment'                    => 'this:wp_newComment',
 113              'wp.getCommentStatusList'          => 'this:wp_getCommentStatusList',
 114              'wp.getMediaItem'                  => 'this:wp_getMediaItem',
 115              'wp.getMediaLibrary'               => 'this:wp_getMediaLibrary',
 116              'wp.getPostFormats'                => 'this:wp_getPostFormats',
 117              'wp.getPostType'                   => 'this:wp_getPostType',
 118              'wp.getPostTypes'                  => 'this:wp_getPostTypes',
 119              'wp.getRevisions'                  => 'this:wp_getRevisions',
 120              'wp.restoreRevision'               => 'this:wp_restoreRevision',
 121  
 122              // Blogger API.
 123              'blogger.getUsersBlogs'            => 'this:blogger_getUsersBlogs',
 124              'blogger.getUserInfo'              => 'this:blogger_getUserInfo',
 125              'blogger.getPost'                  => 'this:blogger_getPost',
 126              'blogger.getRecentPosts'           => 'this:blogger_getRecentPosts',
 127              'blogger.newPost'                  => 'this:blogger_newPost',
 128              'blogger.editPost'                 => 'this:blogger_editPost',
 129              'blogger.deletePost'               => 'this:blogger_deletePost',
 130  
 131              // MetaWeblog API (with MT extensions to structs).
 132              'metaWeblog.newPost'               => 'this:mw_newPost',
 133              'metaWeblog.editPost'              => 'this:mw_editPost',
 134              'metaWeblog.getPost'               => 'this:mw_getPost',
 135              'metaWeblog.getRecentPosts'        => 'this:mw_getRecentPosts',
 136              'metaWeblog.getCategories'         => 'this:mw_getCategories',
 137              'metaWeblog.newMediaObject'        => 'this:mw_newMediaObject',
 138  
 139              // MetaWeblog API aliases for Blogger API.
 140              // See http://www.xmlrpc.com/stories/storyReader$2460
 141              'metaWeblog.deletePost'            => 'this:blogger_deletePost',
 142              'metaWeblog.getUsersBlogs'         => 'this:blogger_getUsersBlogs',
 143  
 144              // MovableType API.
 145              'mt.getCategoryList'               => 'this:mt_getCategoryList',
 146              'mt.getRecentPostTitles'           => 'this:mt_getRecentPostTitles',
 147              'mt.getPostCategories'             => 'this:mt_getPostCategories',
 148              'mt.setPostCategories'             => 'this:mt_setPostCategories',
 149              'mt.supportedMethods'              => 'this:mt_supportedMethods',
 150              'mt.supportedTextFilters'          => 'this:mt_supportedTextFilters',
 151              'mt.getTrackbackPings'             => 'this:mt_getTrackbackPings',
 152              'mt.publishPost'                   => 'this:mt_publishPost',
 153  
 154              // Pingback.
 155              'pingback.ping'                    => 'this:pingback_ping',
 156              'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks',
 157  
 158              'demo.sayHello'                    => 'this:sayHello',
 159              'demo.addTwoNumbers'               => 'this:addTwoNumbers',
 160          );
 161  
 162          $this->initialise_blog_option_info();
 163  
 164          /**
 165           * Filters the methods exposed by the XML-RPC server.
 166           *
 167           * This filter can be used to add new methods, and remove built-in methods.
 168           *
 169           * @since 1.5.0
 170           *
 171           * @param string[] $methods An array of XML-RPC methods, keyed by their methodName.
 172           */
 173          $this->methods = apply_filters( 'xmlrpc_methods', $this->methods );
 174  
 175          $this->set_is_enabled();
 176      }
 177  
 178      /**
 179       * Set wp_xmlrpc_server::$is_enabled property.
 180       *
 181       * Determine whether the xmlrpc server is enabled on this WordPress install
 182       * and set the is_enabled property accordingly.
 183       *
 184       * @since 5.7.3
 185       */
 186  	private function set_is_enabled() {
 187          /*
 188           * Respect old get_option() filters left for back-compat when the 'enable_xmlrpc'
 189           * option was deprecated in 3.5.0. Use the {@see 'xmlrpc_enabled'} hook instead.
 190           */
 191          $is_enabled = apply_filters( 'pre_option_enable_xmlrpc', false );
 192          if ( false === $is_enabled ) {
 193              $is_enabled = apply_filters( 'option_enable_xmlrpc', true );
 194          }
 195  
 196          /**
 197           * Filters whether XML-RPC methods requiring authentication are enabled.
 198           *
 199           * Contrary to the way it's named, this filter does not control whether XML-RPC is *fully*
 200           * enabled, rather, it only controls whether XML-RPC methods requiring authentication - such
 201           * as for publishing purposes - are enabled.
 202           *
 203           * Further, the filter does not control whether pingbacks or other custom endpoints that don't
 204           * require authentication are enabled. This behavior is expected, and due to how parity was matched
 205           * with the `enable_xmlrpc` UI option the filter replaced when it was introduced in 3.5.
 206           *
 207           * To disable XML-RPC methods that require authentication, use:
 208           *
 209           *     add_filter( 'xmlrpc_enabled', '__return_false' );
 210           *
 211           * For more granular control over all XML-RPC methods and requests, see the {@see 'xmlrpc_methods'}
 212           * and {@see 'xmlrpc_element_limit'} hooks.
 213           *
 214           * @since 3.5.0
 215           *
 216           * @param bool $is_enabled Whether XML-RPC is enabled. Default true.
 217           */
 218          $this->is_enabled = apply_filters( 'xmlrpc_enabled', $is_enabled );
 219      }
 220  
 221      /**
 222       * Make private/protected methods readable for backward compatibility.
 223       *
 224       * @since 4.0.0
 225       *
 226       * @param string $name      Method to call.
 227       * @param array  $arguments Arguments to pass when calling.
 228       * @return array|IXR_Error|false Return value of the callback, false otherwise.
 229       */
 230  	public function __call( $name, $arguments ) {
 231          if ( '_multisite_getUsersBlogs' === $name ) {
 232              return $this->_multisite_getUsersBlogs( ...$arguments );
 233          }
 234          return false;
 235      }
 236  
 237      /**
 238       * Serves the XML-RPC request.
 239       *
 240       * @since 2.9.0
 241       */
 242  	public function serve_request() {
 243          $this->IXR_Server( $this->methods );
 244      }
 245  
 246      /**
 247       * Test XMLRPC API by saying, "Hello!" to client.
 248       *
 249       * @since 1.5.0
 250       *
 251       * @return string Hello string response.
 252       */
 253  	public function sayHello() {
 254          return 'Hello!';
 255      }
 256  
 257      /**
 258       * Test XMLRPC API by adding two numbers for client.
 259       *
 260       * @since 1.5.0
 261       *
 262       * @param array $args {
 263       *     Method arguments. Note: arguments must be ordered as documented.
 264       *
 265       *     @type int $number1 A number to add.
 266       *     @type int $number2 A second number to add.
 267       * }
 268       * @return int Sum of the two given numbers.
 269       */
 270  	public function addTwoNumbers( $args ) {
 271          $number1 = $args[0];
 272          $number2 = $args[1];
 273          return $number1 + $number2;
 274      }
 275  
 276      /**
 277       * Log user in.
 278       *
 279       * @since 2.8.0
 280       *
 281       * @param string $username User's username.
 282       * @param string $password User's password.
 283       * @return WP_User|false WP_User object if authentication passed, false otherwise
 284       */
 285  	public function login( $username, $password ) {
 286          if ( ! $this->is_enabled ) {
 287              $this->error = new IXR_Error( 405, sprintf( __( 'XML-RPC services are disabled on this site.' ) ) );
 288              return false;
 289          }
 290  
 291          if ( $this->auth_failed ) {
 292              $user = new WP_Error( 'login_prevented' );
 293          } else {
 294              $user = wp_authenticate( $username, $password );
 295          }
 296  
 297          if ( is_wp_error( $user ) ) {
 298              $this->error = new IXR_Error( 403, __( 'Incorrect username or password.' ) );
 299  
 300              // Flag that authentication has failed once on this wp_xmlrpc_server instance.
 301              $this->auth_failed = true;
 302  
 303              /**
 304               * Filters the XML-RPC user login error message.
 305               *
 306               * @since 3.5.0
 307               *
 308               * @param IXR_Error $error The XML-RPC error message.
 309               * @param WP_Error  $user  WP_Error object.
 310               */
 311              $this->error = apply_filters( 'xmlrpc_login_error', $this->error, $user );
 312              return false;
 313          }
 314  
 315          wp_set_current_user( $user->ID );
 316          return $user;
 317      }
 318  
 319      /**
 320       * Check user's credentials. Deprecated.
 321       *
 322       * @since 1.5.0
 323       * @deprecated 2.8.0 Use wp_xmlrpc_server::login()
 324       * @see wp_xmlrpc_server::login()
 325       *
 326       * @param string $username User's username.
 327       * @param string $password User's password.
 328       * @return bool Whether authentication passed.
 329       */
 330  	public function login_pass_ok( $username, $password ) {
 331          return (bool) $this->login( $username, $password );
 332      }
 333  
 334      /**
 335       * Escape string or array of strings for database.
 336       *
 337       * @since 1.5.2
 338       *
 339       * @param string|array $data Escape single string or array of strings.
 340       * @return string|void Returns with string is passed, alters by-reference
 341       *                     when array is passed.
 342       */
 343  	public function escape( &$data ) {
 344          if ( ! is_array( $data ) ) {
 345              return wp_slash( $data );
 346          }
 347  
 348          foreach ( $data as &$v ) {
 349              if ( is_array( $v ) ) {
 350                  $this->escape( $v );
 351              } elseif ( ! is_object( $v ) ) {
 352                  $v = wp_slash( $v );
 353              }
 354          }
 355      }
 356  
 357      /**
 358       * Send error response to client.
 359       *
 360       * Send an XML error response to the client. If the endpoint is enabled
 361       * an HTTP 200 response is always sent per the XML-RPC specification.
 362       *
 363       * @since 5.7.3
 364       *
 365       * @param IXR_Error|string $error   Error code or an error object.
 366       * @param false            $message Error message. Optional.
 367       */
 368  	public function error( $error, $message = false ) {
 369          // Accepts either an error object or an error code and message
 370          if ( $message && ! is_object( $error ) ) {
 371              $error = new IXR_Error( $error, $message );
 372          }
 373  
 374          if ( ! $this->is_enabled ) {
 375              status_header( $error->code );
 376          }
 377  
 378          $this->output( $error->getXml() );
 379      }
 380  
 381      /**
 382       * Retrieve custom fields for post.
 383       *
 384       * @since 2.5.0
 385       *
 386       * @param int $post_id Post ID.
 387       * @return array Custom fields, if exist.
 388       */
 389  	public function get_custom_fields( $post_id ) {
 390          $post_id = (int) $post_id;
 391  
 392          $custom_fields = array();
 393  
 394          foreach ( (array) has_meta( $post_id ) as $meta ) {
 395              // Don't expose protected fields.
 396              if ( ! current_user_can( 'edit_post_meta', $post_id, $meta['meta_key'] ) ) {
 397                  continue;
 398              }
 399  
 400              $custom_fields[] = array(
 401                  'id'    => $meta['meta_id'],
 402                  'key'   => $meta['meta_key'],
 403                  'value' => $meta['meta_value'],
 404              );
 405          }
 406  
 407          return $custom_fields;
 408      }
 409  
 410      /**
 411       * Set custom fields for post.
 412       *
 413       * @since 2.5.0
 414       *
 415       * @param int   $post_id Post ID.
 416       * @param array $fields  Custom fields.
 417       */
 418  	public function set_custom_fields( $post_id, $fields ) {
 419          $post_id = (int) $post_id;
 420  
 421          foreach ( (array) $fields as $meta ) {
 422              if ( isset( $meta['id'] ) ) {
 423                  $meta['id'] = (int) $meta['id'];
 424                  $pmeta      = get_metadata_by_mid( 'post', $meta['id'] );
 425  
 426                  if ( ! $pmeta || $pmeta->post_id != $post_id ) {
 427                      continue;
 428                  }
 429  
 430                  if ( isset( $meta['key'] ) ) {
 431                      $meta['key'] = wp_unslash( $meta['key'] );
 432                      if ( $meta['key'] !== $pmeta->meta_key ) {
 433                          continue;
 434                      }
 435                      $meta['value'] = wp_unslash( $meta['value'] );
 436                      if ( current_user_can( 'edit_post_meta', $post_id, $meta['key'] ) ) {
 437                          update_metadata_by_mid( 'post', $meta['id'], $meta['value'] );
 438                      }
 439                  } elseif ( current_user_can( 'delete_post_meta', $post_id, $pmeta->meta_key ) ) {
 440                      delete_metadata_by_mid( 'post', $meta['id'] );
 441                  }
 442              } elseif ( current_user_can( 'add_post_meta', $post_id, wp_unslash( $meta['key'] ) ) ) {
 443                  add_post_meta( $post_id, $meta['key'], $meta['value'] );
 444              }
 445          }
 446      }
 447  
 448      /**
 449       * Retrieve custom fields for a term.
 450       *
 451       * @since 4.9.0
 452       *
 453       * @param int $term_id Term ID.
 454       * @return array Array of custom fields, if they exist.
 455       */
 456  	public function get_term_custom_fields( $term_id ) {
 457          $term_id = (int) $term_id;
 458  
 459          $custom_fields = array();
 460  
 461          foreach ( (array) has_term_meta( $term_id ) as $meta ) {
 462  
 463              if ( ! current_user_can( 'edit_term_meta', $term_id ) ) {
 464                  continue;
 465              }
 466  
 467              $custom_fields[] = array(
 468                  'id'    => $meta['meta_id'],
 469                  'key'   => $meta['meta_key'],
 470                  'value' => $meta['meta_value'],
 471              );
 472          }
 473  
 474          return $custom_fields;
 475      }
 476  
 477      /**
 478       * Set custom fields for a term.
 479       *
 480       * @since 4.9.0
 481       *
 482       * @param int   $term_id Term ID.
 483       * @param array $fields  Custom fields.
 484       */
 485  	public function set_term_custom_fields( $term_id, $fields ) {
 486          $term_id = (int) $term_id;
 487  
 488          foreach ( (array) $fields as $meta ) {
 489              if ( isset( $meta['id'] ) ) {
 490                  $meta['id'] = (int) $meta['id'];
 491                  $pmeta      = get_metadata_by_mid( 'term', $meta['id'] );
 492                  if ( isset( $meta['key'] ) ) {
 493                      $meta['key'] = wp_unslash( $meta['key'] );
 494                      if ( $meta['key'] !== $pmeta->meta_key ) {
 495                          continue;
 496                      }
 497                      $meta['value'] = wp_unslash( $meta['value'] );
 498                      if ( current_user_can( 'edit_term_meta', $term_id ) ) {
 499                          update_metadata_by_mid( 'term', $meta['id'], $meta['value'] );
 500                      }
 501                  } elseif ( current_user_can( 'delete_term_meta', $term_id ) ) {
 502                      delete_metadata_by_mid( 'term', $meta['id'] );
 503                  }
 504              } elseif ( current_user_can( 'add_term_meta', $term_id ) ) {
 505                  add_term_meta( $term_id, $meta['key'], $meta['value'] );
 506              }
 507          }
 508      }
 509  
 510      /**
 511       * Set up blog options property.
 512       *
 513       * Passes property through {@see 'xmlrpc_blog_options'} filter.
 514       *
 515       * @since 2.6.0
 516       */
 517  	public function initialise_blog_option_info() {
 518          $this->blog_options = array(
 519              // Read-only options.
 520              'software_name'           => array(
 521                  'desc'     => __( 'Software Name' ),
 522                  'readonly' => true,
 523                  'value'    => 'WordPress',
 524              ),
 525              'software_version'        => array(
 526                  'desc'     => __( 'Software Version' ),
 527                  'readonly' => true,
 528                  'value'    => get_bloginfo( 'version' ),
 529              ),
 530              'blog_url'                => array(
 531                  'desc'     => __( 'WordPress Address (URL)' ),
 532                  'readonly' => true,
 533                  'option'   => 'siteurl',
 534              ),
 535              'home_url'                => array(
 536                  'desc'     => __( 'Site Address (URL)' ),
 537                  'readonly' => true,
 538                  'option'   => 'home',
 539              ),
 540              'login_url'               => array(
 541                  'desc'     => __( 'Login Address (URL)' ),
 542                  'readonly' => true,
 543                  'value'    => wp_login_url(),
 544              ),
 545              'admin_url'               => array(
 546                  'desc'     => __( 'The URL to the admin area' ),
 547                  'readonly' => true,
 548                  'value'    => get_admin_url(),
 549              ),
 550              'image_default_link_type' => array(
 551                  'desc'     => __( 'Image default link type' ),
 552                  'readonly' => true,
 553                  'option'   => 'image_default_link_type',
 554              ),
 555              'image_default_size'      => array(
 556                  'desc'     => __( 'Image default size' ),
 557                  'readonly' => true,
 558                  'option'   => 'image_default_size',
 559              ),
 560              'image_default_align'     => array(
 561                  'desc'     => __( 'Image default align' ),
 562                  'readonly' => true,
 563                  'option'   => 'image_default_align',
 564              ),
 565              'template'                => array(
 566                  'desc'     => __( 'Template' ),
 567                  'readonly' => true,
 568                  'option'   => 'template',
 569              ),
 570              'stylesheet'              => array(
 571                  'desc'     => __( 'Stylesheet' ),
 572                  'readonly' => true,
 573                  'option'   => 'stylesheet',
 574              ),
 575              'post_thumbnail'          => array(
 576                  'desc'     => __( 'Post Thumbnail' ),
 577                  'readonly' => true,
 578                  'value'    => current_theme_supports( 'post-thumbnails' ),
 579              ),
 580  
 581              // Updatable options.
 582              'time_zone'               => array(
 583                  'desc'     => __( 'Time Zone' ),
 584                  'readonly' => false,
 585                  'option'   => 'gmt_offset',
 586              ),
 587              'blog_title'              => array(
 588                  'desc'     => __( 'Site Title' ),
 589                  'readonly' => false,
 590                  'option'   => 'blogname',
 591              ),
 592              'blog_tagline'            => array(
 593                  'desc'     => __( 'Site Tagline' ),
 594                  'readonly' => false,
 595                  'option'   => 'blogdescription',
 596              ),
 597              'date_format'             => array(
 598                  'desc'     => __( 'Date Format' ),
 599                  'readonly' => false,
 600                  'option'   => 'date_format',
 601              ),
 602              'time_format'             => array(
 603                  'desc'     => __( 'Time Format' ),
 604                  'readonly' => false,
 605                  'option'   => 'time_format',
 606              ),
 607              'users_can_register'      => array(
 608                  'desc'     => __( 'Allow new users to sign up' ),
 609                  'readonly' => false,
 610                  'option'   => 'users_can_register',
 611              ),
 612              'thumbnail_size_w'        => array(
 613                  'desc'     => __( 'Thumbnail Width' ),
 614                  'readonly' => false,
 615                  'option'   => 'thumbnail_size_w',
 616              ),
 617              'thumbnail_size_h'        => array(
 618                  'desc'     => __( 'Thumbnail Height' ),
 619                  'readonly' => false,
 620                  'option'   => 'thumbnail_size_h',
 621              ),
 622              'thumbnail_crop'          => array(
 623                  'desc'     => __( 'Crop thumbnail to exact dimensions' ),
 624                  'readonly' => false,
 625                  'option'   => 'thumbnail_crop',
 626              ),
 627              'medium_size_w'           => array(
 628                  'desc'     => __( 'Medium size image width' ),
 629                  'readonly' => false,
 630                  'option'   => 'medium_size_w',
 631              ),
 632              'medium_size_h'           => array(
 633                  'desc'     => __( 'Medium size image height' ),
 634                  'readonly' => false,
 635                  'option'   => 'medium_size_h',
 636              ),
 637              'medium_large_size_w'     => array(
 638                  'desc'     => __( 'Medium-Large size image width' ),
 639                  'readonly' => false,
 640                  'option'   => 'medium_large_size_w',
 641              ),
 642              'medium_large_size_h'     => array(
 643                  'desc'     => __( 'Medium-Large size image height' ),
 644                  'readonly' => false,
 645                  'option'   => 'medium_large_size_h',
 646              ),
 647              'large_size_w'            => array(
 648                  'desc'     => __( 'Large size image width' ),
 649                  'readonly' => false,
 650                  'option'   => 'large_size_w',
 651              ),
 652              'large_size_h'            => array(
 653                  'desc'     => __( 'Large size image height' ),
 654                  'readonly' => false,
 655                  'option'   => 'large_size_h',
 656              ),
 657              'default_comment_status'  => array(
 658                  'desc'     => __( 'Allow people to submit comments on new posts.' ),
 659                  'readonly' => false,
 660                  'option'   => 'default_comment_status',
 661              ),
 662              'default_ping_status'     => array(
 663                  'desc'     => __( 'Allow link notifications from other blogs (pingbacks and trackbacks) on new posts.' ),
 664                  'readonly' => false,
 665                  'option'   => 'default_ping_status',
 666              ),
 667          );
 668  
 669          /**
 670           * Filters the XML-RPC blog options property.
 671           *
 672           * @since 2.6.0
 673           *
 674           * @param array $blog_options An array of XML-RPC blog options.
 675           */
 676          $this->blog_options = apply_filters( 'xmlrpc_blog_options', $this->blog_options );
 677      }
 678  
 679      /**
 680       * Retrieve the blogs of the user.
 681       *
 682       * @since 2.6.0
 683       *
 684       * @param array $args {
 685       *     Method arguments. Note: arguments must be ordered as documented.
 686       *
 687       *     @type string $username Username.
 688       *     @type string $password Password.
 689       * }
 690       * @return array|IXR_Error Array contains:
 691       *  - 'isAdmin'
 692       *  - 'isPrimary' - whether the blog is the user's primary blog
 693       *  - 'url'
 694       *  - 'blogid'
 695       *  - 'blogName'
 696       *  - 'xmlrpc' - url of xmlrpc endpoint
 697       */
 698  	public function wp_getUsersBlogs( $args ) {
 699          if ( ! $this->minimum_args( $args, 2 ) ) {
 700              return $this->error;
 701          }
 702  
 703          // If this isn't on WPMU then just use blogger_getUsersBlogs().
 704          if ( ! is_multisite() ) {
 705              array_unshift( $args, 1 );
 706              return $this->blogger_getUsersBlogs( $args );
 707          }
 708  
 709          $this->escape( $args );
 710  
 711          $username = $args[0];
 712          $password = $args[1];
 713  
 714          $user = $this->login( $username, $password );
 715          if ( ! $user ) {
 716              return $this->error;
 717          }
 718  
 719          /**
 720           * Fires after the XML-RPC user has been authenticated but before the rest of
 721           * the method logic begins.
 722           *
 723           * All built-in XML-RPC methods use the action xmlrpc_call, with a parameter
 724           * equal to the method's name, e.g., wp.getUsersBlogs, wp.newPost, etc.
 725           *
 726           * @since 2.5.0
 727           * @since 5.7.0 Added the `$args` and `$server` parameters.
 728           *
 729           * @param string           $name   The method name.
 730           * @param array|string     $args   The escaped arguments passed to the method.
 731           * @param wp_xmlrpc_server $server The XML-RPC server instance.
 732           */
 733          do_action( 'xmlrpc_call', 'wp.getUsersBlogs', $args, $this );
 734  
 735          $blogs           = (array) get_blogs_of_user( $user->ID );
 736          $struct          = array();
 737          $primary_blog_id = 0;
 738          $active_blog     = get_active_blog_for_user( $user->ID );
 739          if ( $active_blog ) {
 740              $primary_blog_id = (int) $active_blog->blog_id;
 741          }
 742  
 743          foreach ( $blogs as $blog ) {
 744              // Don't include blogs that aren't hosted at this site.
 745              if ( get_current_network_id() != $blog->site_id ) {
 746                  continue;
 747              }
 748  
 749              $blog_id = $blog->userblog_id;
 750  
 751              switch_to_blog( $blog_id );
 752  
 753              $is_admin   = current_user_can( 'manage_options' );
 754              $is_primary = ( (int) $blog_id === $primary_blog_id );
 755  
 756              $struct[] = array(
 757                  'isAdmin'   => $is_admin,
 758                  'isPrimary' => $is_primary,
 759                  'url'       => home_url( '/' ),
 760                  'blogid'    => (string) $blog_id,
 761                  'blogName'  => get_option( 'blogname' ),
 762                  'xmlrpc'    => site_url( 'xmlrpc.php', 'rpc' ),
 763              );
 764  
 765              restore_current_blog();
 766          }
 767  
 768          return $struct;
 769      }
 770  
 771      /**
 772       * Checks if the method received at least the minimum number of arguments.
 773       *
 774       * @since 3.4.0
 775       *
 776       * @param array $args  An array of arguments to check.
 777       * @param int   $count Minimum number of arguments.
 778       * @return bool True if `$args` contains at least `$count` arguments, false otherwise.
 779       */
 780  	protected function minimum_args( $args, $count ) {
 781          if ( ! is_array( $args ) || count( $args ) < $count ) {
 782              $this->error = new IXR_Error( 400, __( 'Insufficient arguments passed to this XML-RPC method.' ) );
 783              return false;
 784          }
 785  
 786          return true;
 787      }
 788  
 789      /**
 790       * Prepares taxonomy data for return in an XML-RPC object.
 791       *
 792       * @param WP_Taxonomy $taxonomy The unprepared taxonomy data.
 793       * @param array       $fields   The subset of taxonomy fields to return.
 794       * @return array The prepared taxonomy data.
 795       */
 796  	protected function _prepare_taxonomy( $taxonomy, $fields ) {
 797          $_taxonomy = array(
 798              'name'         => $taxonomy->name,
 799              'label'        => $taxonomy->label,
 800              'hierarchical' => (bool) $taxonomy->hierarchical,
 801              'public'       => (bool) $taxonomy->public,
 802              'show_ui'      => (bool) $taxonomy->show_ui,
 803              '_builtin'     => (bool) $taxonomy->_builtin,
 804          );
 805  
 806          if ( in_array( 'labels', $fields, true ) ) {
 807              $_taxonomy['labels'] = (array) $taxonomy->labels;
 808          }
 809  
 810          if ( in_array( 'cap', $fields, true ) ) {
 811              $_taxonomy['cap'] = (array) $taxonomy->cap;
 812          }
 813  
 814          if ( in_array( 'menu', $fields, true ) ) {
 815              $_taxonomy['show_in_menu'] = (bool) $taxonomy->show_in_menu;
 816          }
 817  
 818          if ( in_array( 'object_type', $fields, true ) ) {
 819              $_taxonomy['object_type'] = array_unique( (array) $taxonomy->object_type );
 820          }
 821  
 822          /**
 823           * Filters XML-RPC-prepared data for the given taxonomy.
 824           *
 825           * @since 3.4.0
 826           *
 827           * @param array       $_taxonomy An array of taxonomy data.
 828           * @param WP_Taxonomy $taxonomy  Taxonomy object.
 829           * @param array       $fields    The subset of taxonomy fields to return.
 830           */
 831          return apply_filters( 'xmlrpc_prepare_taxonomy', $_taxonomy, $taxonomy, $fields );
 832      }
 833  
 834      /**
 835       * Prepares term data for return in an XML-RPC object.
 836       *
 837       * @param array|object $term The unprepared term data.
 838       * @return array The prepared term data.
 839       */
 840  	protected function _prepare_term( $term ) {
 841          $_term = $term;
 842          if ( ! is_array( $_term ) ) {
 843              $_term = get_object_vars( $_term );
 844          }
 845  
 846          // For integers which may be larger than XML-RPC supports ensure we return strings.
 847          $_term['term_id']          = (string) $_term['term_id'];
 848          $_term['term_group']       = (string) $_term['term_group'];
 849          $_term['term_taxonomy_id'] = (string) $_term['term_taxonomy_id'];
 850          $_term['parent']           = (string) $_term['parent'];
 851  
 852          // Count we are happy to return as an integer because people really shouldn't use terms that much.
 853          $_term['count'] = (int) $_term['count'];
 854  
 855          // Get term meta.
 856          $_term['custom_fields'] = $this->get_term_custom_fields( $_term['term_id'] );
 857  
 858          /**
 859           * Filters XML-RPC-prepared data for the given term.
 860           *
 861           * @since 3.4.0
 862           *
 863           * @param array        $_term An array of term data.
 864           * @param array|object $term  Term object or array.
 865           */
 866          return apply_filters( 'xmlrpc_prepare_term', $_term, $term );
 867      }
 868  
 869      /**
 870       * Convert a WordPress date string to an IXR_Date object.
 871       *
 872       * @param string $date Date string to convert.
 873       * @return IXR_Date IXR_Date object.
 874       */
 875  	protected function _convert_date( $date ) {
 876          if ( '0000-00-00 00:00:00' === $date ) {
 877              return new IXR_Date( '00000000T00:00:00Z' );
 878          }
 879          return new IXR_Date( mysql2date( 'Ymd\TH:i:s', $date, false ) );
 880      }
 881  
 882      /**
 883       * Convert a WordPress GMT date string to an IXR_Date object.
 884       *
 885       * @param string $date_gmt WordPress GMT date string.
 886       * @param string $date     Date string.
 887       * @return IXR_Date IXR_Date object.
 888       */
 889  	protected function _convert_date_gmt( $date_gmt, $date ) {
 890          if ( '0000-00-00 00:00:00' !== $date && '0000-00-00 00:00:00' === $date_gmt ) {
 891              return new IXR_Date( get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $date, false ), 'Ymd\TH:i:s' ) );
 892          }
 893          return $this->_convert_date( $date_gmt );
 894      }
 895  
 896      /**
 897       * Prepares post data for return in an XML-RPC object.
 898       *
 899       * @param array $post   The unprepared post data.
 900       * @param array $fields The subset of post type fields to return.
 901       * @return array The prepared post data.
 902       */
 903  	protected function _prepare_post( $post, $fields ) {
 904          // Holds the data for this post. built up based on $fields.
 905          $_post = array( 'post_id' => (string) $post['ID'] );
 906  
 907          // Prepare common post fields.
 908          $post_fields = array(
 909              'post_title'        => $post['post_title'],
 910              'post_date'         => $this->_convert_date( $post['post_date'] ),
 911              'post_date_gmt'     => $this->_convert_date_gmt( $post['post_date_gmt'], $post['post_date'] ),
 912              'post_modified'     => $this->_convert_date( $post['post_modified'] ),
 913              'post_modified_gmt' => $this->_convert_date_gmt( $post['post_modified_gmt'], $post['post_modified'] ),
 914              'post_status'       => $post['post_status'],
 915              'post_type'         => $post['post_type'],
 916              'post_name'         => $post['post_name'],
 917              'post_author'       => $post['post_author'],
 918              'post_password'     => $post['post_password'],
 919              'post_excerpt'      => $post['post_excerpt'],
 920              'post_content'      => $post['post_content'],
 921              'post_parent'       => (string) $post['post_parent'],
 922              'post_mime_type'    => $post['post_mime_type'],
 923              'link'              => get_permalink( $post['ID'] ),
 924              'guid'              => $post['guid'],
 925              'menu_order'        => (int) $post['menu_order'],
 926              'comment_status'    => $post['comment_status'],
 927              'ping_status'       => $post['ping_status'],
 928              'sticky'            => ( 'post' === $post['post_type'] && is_sticky( $post['ID'] ) ),
 929          );
 930  
 931          // Thumbnail.
 932          $post_fields['post_thumbnail'] = array();
 933          $thumbnail_id                  = get_post_thumbnail_id( $post['ID'] );
 934          if ( $thumbnail_id ) {
 935              $thumbnail_size                = current_theme_supports( 'post-thumbnail' ) ? 'post-thumbnail' : 'thumbnail';
 936              $post_fields['post_thumbnail'] = $this->_prepare_media_item( get_post( $thumbnail_id ), $thumbnail_size );
 937          }
 938  
 939          // Consider future posts as published.
 940          if ( 'future' === $post_fields['post_status'] ) {
 941              $post_fields['post_status'] = 'publish';
 942          }
 943  
 944          // Fill in blank post format.
 945          $post_fields['post_format'] = get_post_format( $post['ID'] );
 946          if ( empty( $post_fields['post_format'] ) ) {
 947              $post_fields['post_format'] = 'standard';
 948          }
 949  
 950          // Merge requested $post_fields fields into $_post.
 951          if ( in_array( 'post', $fields, true ) ) {
 952              $_post = array_merge( $_post, $post_fields );
 953          } else {
 954              $requested_fields = array_intersect_key( $post_fields, array_flip( $fields ) );
 955              $_post            = array_merge( $_post, $requested_fields );
 956          }
 957  
 958          $all_taxonomy_fields = in_array( 'taxonomies', $fields, true );
 959  
 960          if ( $all_taxonomy_fields || in_array( 'terms', $fields, true ) ) {
 961              $post_type_taxonomies = get_object_taxonomies( $post['post_type'], 'names' );
 962              $terms                = wp_get_object_terms( $post['ID'], $post_type_taxonomies );
 963              $_post['terms']       = array();
 964              foreach ( $terms as $term ) {
 965                  $_post['terms'][] = $this->_prepare_term( $term );
 966              }
 967          }
 968  
 969          if ( in_array( 'custom_fields', $fields, true ) ) {
 970              $_post['custom_fields'] = $this->get_custom_fields( $post['ID'] );
 971          }
 972  
 973          if ( in_array( 'enclosure', $fields, true ) ) {
 974              $_post['enclosure'] = array();
 975              $enclosures         = (array) get_post_meta( $post['ID'], 'enclosure' );
 976              if ( ! empty( $enclosures ) ) {
 977                  $encdata                      = explode( "\n", $enclosures[0] );
 978                  $_post['enclosure']['url']    = trim( htmlspecialchars( $encdata[0] ) );
 979                  $_post['enclosure']['length'] = (int) trim( $encdata[1] );
 980                  $_post['enclosure']['type']   = trim( $encdata[2] );
 981              }
 982          }
 983  
 984          /**
 985           * Filters XML-RPC-prepared date for the given post.
 986           *
 987           * @since 3.4.0
 988           *
 989           * @param array $_post  An array of modified post data.
 990           * @param array $post   An array of post data.
 991           * @param array $fields An array of post fields.
 992           */
 993          return apply_filters( 'xmlrpc_prepare_post', $_post, $post, $fields );
 994      }
 995  
 996      /**
 997       * Prepares post data for return in an XML-RPC object.
 998       *
 999       * @since 3.4.0
1000       * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
1001       *
1002       * @param WP_Post_Type $post_type Post type object.
1003       * @param array        $fields    The subset of post fields to return.
1004       * @return array The prepared post type data.
1005       */
1006  	protected function _prepare_post_type( $post_type, $fields ) {
1007          $_post_type = array(
1008              'name'         => $post_type->name,
1009              'label'        => $post_type->label,
1010              'hierarchical' => (bool) $post_type->hierarchical,
1011              'public'       => (bool) $post_type->public,
1012              'show_ui'      => (bool) $post_type->show_ui,
1013              '_builtin'     => (bool) $post_type->_builtin,
1014              'has_archive'  => (bool) $post_type->has_archive,
1015              'supports'     => get_all_post_type_supports( $post_type->name ),
1016          );
1017  
1018          if ( in_array( 'labels', $fields, true ) ) {
1019              $_post_type['labels'] = (array) $post_type->labels;
1020          }
1021  
1022          if ( in_array( 'cap', $fields, true ) ) {
1023              $_post_type['cap']          = (array) $post_type->cap;
1024              $_post_type['map_meta_cap'] = (bool) $post_type->map_meta_cap;
1025          }
1026  
1027          if ( in_array( 'menu', $fields, true ) ) {
1028              $_post_type['menu_position'] = (int) $post_type->menu_position;
1029              $_post_type['menu_icon']     = $post_type->menu_icon;
1030              $_post_type['show_in_menu']  = (bool) $post_type->show_in_menu;
1031          }
1032  
1033          if ( in_array( 'taxonomies', $fields, true ) ) {
1034              $_post_type['taxonomies'] = get_object_taxonomies( $post_type->name, 'names' );
1035          }
1036  
1037          /**
1038           * Filters XML-RPC-prepared date for the given post type.
1039           *
1040           * @since 3.4.0
1041           * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
1042           *
1043           * @param array        $_post_type An array of post type data.
1044           * @param WP_Post_Type $post_type  Post type object.
1045           */
1046          return apply_filters( 'xmlrpc_prepare_post_type', $_post_type, $post_type );
1047      }
1048  
1049      /**
1050       * Prepares media item data for return in an XML-RPC object.
1051       *
1052       * @param WP_Post $media_item     The unprepared media item data.
1053       * @param string  $thumbnail_size The image size to use for the thumbnail URL.
1054       * @return array The prepared media item data.
1055       */
1056  	protected function _prepare_media_item( $media_item, $thumbnail_size = 'thumbnail' ) {
1057          $_media_item = array(
1058              'attachment_id'    => (string) $media_item->ID,
1059              'date_created_gmt' => $this->_convert_date_gmt( $media_item->post_date_gmt, $media_item->post_date ),
1060              'parent'           => $media_item->post_parent,
1061              'link'             => wp_get_attachment_url( $media_item->ID ),
1062              'title'            => $media_item->post_title,
1063              'caption'          => $media_item->post_excerpt,
1064              'description'      => $media_item->post_content,
1065              'metadata'         => wp_get_attachment_metadata( $media_item->ID ),
1066              'type'             => $media_item->post_mime_type,
1067          );
1068  
1069          $thumbnail_src = image_downsize( $media_item->ID, $thumbnail_size );
1070          if ( $thumbnail_src ) {
1071              $_media_item['thumbnail'] = $thumbnail_src[0];
1072          } else {
1073              $_media_item['thumbnail'] = $_media_item['link'];
1074          }
1075  
1076          /**
1077           * Filters XML-RPC-prepared data for the given media item.
1078           *
1079           * @since 3.4.0
1080           *
1081           * @param array   $_media_item    An array of media item data.
1082           * @param WP_Post $media_item     Media item object.
1083           * @param string  $thumbnail_size Image size.
1084           */
1085          return apply_filters( 'xmlrpc_prepare_media_item', $_media_item, $media_item, $thumbnail_size );
1086      }
1087  
1088      /**
1089       * Prepares page data for return in an XML-RPC object.
1090       *
1091       * @param WP_Post $page The unprepared page data.
1092       * @return array The prepared page data.
1093       */
1094  	protected function _prepare_page( $page ) {
1095          // Get all of the page content and link.
1096          $full_page = get_extended( $page->post_content );
1097          $link      = get_permalink( $page->ID );
1098  
1099          // Get info the page parent if there is one.
1100          $parent_title = '';
1101          if ( ! empty( $page->post_parent ) ) {
1102              $parent       = get_post( $page->post_parent );
1103              $parent_title = $parent->post_title;
1104          }
1105  
1106          // Determine comment and ping settings.
1107          $allow_comments = comments_open( $page->ID ) ? 1 : 0;
1108          $allow_pings    = pings_open( $page->ID ) ? 1 : 0;
1109  
1110          // Format page date.
1111          $page_date     = $this->_convert_date( $page->post_date );
1112          $page_date_gmt = $this->_convert_date_gmt( $page->post_date_gmt, $page->post_date );
1113  
1114          // Pull the categories info together.
1115          $categories = array();
1116          if ( is_object_in_taxonomy( 'page', 'category' ) ) {
1117              foreach ( wp_get_post_categories( $page->ID ) as $cat_id ) {
1118                  $categories[] = get_cat_name( $cat_id );
1119              }
1120          }
1121  
1122          // Get the author info.
1123          $author = get_userdata( $page->post_author );
1124  
1125          $page_template = get_page_template_slug( $page->ID );
1126          if ( empty( $page_template ) ) {
1127              $page_template = 'default';
1128          }
1129  
1130          $_page = array(
1131              'dateCreated'            => $page_date,
1132              'userid'                 => $page->post_author,
1133              'page_id'                => $page->ID,
1134              'page_status'            => $page->post_status,
1135              'description'            => $full_page['main'],
1136              'title'                  => $page->post_title,
1137              'link'                   => $link,
1138              'permaLink'              => $link,
1139              'categories'             => $categories,
1140              'excerpt'                => $page->post_excerpt,
1141              'text_more'              => $full_page['extended'],
1142              'mt_allow_comments'      => $allow_comments,
1143              'mt_allow_pings'         => $allow_pings,
1144              'wp_slug'                => $page->post_name,
1145              'wp_password'            => $page->post_password,
1146              'wp_author'              => $author->display_name,
1147              'wp_page_parent_id'      => $page->post_parent,
1148              'wp_page_parent_title'   => $parent_title,
1149              'wp_page_order'          => $page->menu_order,
1150              'wp_author_id'           => (string) $author->ID,
1151              'wp_author_display_name' => $author->display_name,
1152              'date_created_gmt'       => $page_date_gmt,
1153              'custom_fields'          => $this->get_custom_fields( $page->ID ),
1154              'wp_page_template'       => $page_template,
1155          );
1156  
1157          /**
1158           * Filters XML-RPC-prepared data for the given page.
1159           *
1160           * @since 3.4.0
1161           *
1162           * @param array   $_page An array of page data.
1163           * @param WP_Post $page  Page object.
1164           */
1165          return apply_filters( 'xmlrpc_prepare_page', $_page, $page );
1166      }
1167  
1168      /**
1169       * Prepares comment data for return in an XML-RPC object.
1170       *
1171       * @param WP_Comment $comment The unprepared comment data.
1172       * @return array The prepared comment data.
1173       */
1174  	protected function _prepare_comment( $comment ) {
1175          // Format page date.
1176          $comment_date_gmt = $this->_convert_date_gmt( $comment->comment_date_gmt, $comment->comment_date );
1177  
1178          if ( '0' == $comment->comment_approved ) {
1179              $comment_status = 'hold';
1180          } elseif ( 'spam' === $comment->comment_approved ) {
1181              $comment_status = 'spam';
1182          } elseif ( '1' == $comment->comment_approved ) {
1183              $comment_status = 'approve';
1184          } else {
1185              $comment_status = $comment->comment_approved;
1186          }
1187          $_comment = array(
1188              'date_created_gmt' => $comment_date_gmt,
1189              'user_id'          => $comment->user_id,
1190              'comment_id'       => $comment->comment_ID,
1191              'parent'           => $comment->comment_parent,
1192              'status'           => $comment_status,
1193              'content'          => $comment->comment_content,
1194              'link'             => get_comment_link( $comment ),
1195              'post_id'          => $comment->comment_post_ID,
1196              'post_title'       => get_the_title( $comment->comment_post_ID ),
1197              'author'           => $comment->comment_author,
1198              'author_url'       => $comment->comment_author_url,
1199              'author_email'     => $comment->comment_author_email,
1200              'author_ip'        => $comment->comment_author_IP,
1201              'type'             => $comment->comment_type,
1202          );
1203  
1204          /**
1205           * Filters XML-RPC-prepared data for the given comment.
1206           *
1207           * @since 3.4.0
1208           *
1209           * @param array      $_comment An array of prepared comment data.
1210           * @param WP_Comment $comment  Comment object.
1211           */
1212          return apply_filters( 'xmlrpc_prepare_comment', $_comment, $comment );
1213      }
1214  
1215      /**
1216       * Prepares user data for return in an XML-RPC object.
1217       *
1218       * @param WP_User $user   The unprepared user object.
1219       * @param array   $fields The subset of user fields to return.
1220       * @return array The prepared user data.
1221       */
1222  	protected function _prepare_user( $user, $fields ) {
1223          $_user = array( 'user_id' => (string) $user->ID );
1224  
1225          $user_fields = array(
1226              'username'     => $user->user_login,
1227              'first_name'   => $user->user_firstname,
1228              'last_name'    => $user->user_lastname,
1229              'registered'   => $this->_convert_date( $user->user_registered ),
1230              'bio'          => $user->user_description,
1231              'email'        => $user->user_email,
1232              'nickname'     => $user->nickname,
1233              'nicename'     => $user->user_nicename,
1234              'url'          => $user->user_url,
1235              'display_name' => $user->display_name,
1236              'roles'        => $user->roles,
1237          );
1238  
1239          if ( in_array( 'all', $fields, true ) ) {
1240              $_user = array_merge( $_user, $user_fields );
1241          } else {
1242              if ( in_array( 'basic', $fields, true ) ) {
1243                  $basic_fields = array( 'username', 'email', 'registered', 'display_name', 'nicename' );
1244                  $fields       = array_merge( $fields, $basic_fields );
1245              }
1246              $requested_fields = array_intersect_key( $user_fields, array_flip( $fields ) );
1247              $_user            = array_merge( $_user, $requested_fields );
1248          }
1249  
1250          /**
1251           * Filters XML-RPC-prepared data for the given user.
1252           *
1253           * @since 3.5.0
1254           *
1255           * @param array   $_user  An array of user data.
1256           * @param WP_User $user   User object.
1257           * @param array   $fields An array of user fields.
1258           */
1259          return apply_filters( 'xmlrpc_prepare_user', $_user, $user, $fields );
1260      }
1261  
1262      /**
1263       * Create a new post for any registered post type.
1264       *
1265       * @since 3.4.0
1266       *
1267       * @link https://en.wikipedia.org/wiki/RSS_enclosure for information on RSS enclosures.
1268       *
1269       * @param array $args {
1270       *     Method arguments. Note: top-level arguments must be ordered as documented.
1271       *
1272       *     @type int    $blog_id        Blog ID (unused).
1273       *     @type string $username       Username.
1274       *     @type string $password       Password.
1275       *     @type array  $content_struct {
1276       *         Content struct for adding a new post. See wp_insert_post() for information on
1277       *         additional post fields
1278       *
1279       *         @type string $post_type      Post type. Default 'post'.
1280       *         @type string $post_status    Post status. Default 'draft'
1281       *         @type string $post_title     Post title.
1282       *         @type int    $post_author    Post author ID.
1283       *         @type string $post_excerpt   Post excerpt.
1284       *         @type string $post_content   Post content.
1285       *         @type string $post_date_gmt  Post date in GMT.
1286       *         @type string $post_date      Post date.
1287       *         @type string $post_password  Post password (20-character limit).
1288       *         @type string $comment_status Post comment enabled status. Accepts 'open' or 'closed'.
1289       *         @type string $ping_status    Post ping status. Accepts 'open' or 'closed'.
1290       *         @type bool   $sticky         Whether the post should be sticky. Automatically false if
1291       *                                      `$post_status` is 'private'.
1292       *         @type int    $post_thumbnail ID of an image to use as the post thumbnail/featured image.
1293       *         @type array  $custom_fields  Array of meta key/value pairs to add to the post.
1294       *         @type array  $terms          Associative array with taxonomy names as keys and arrays
1295       *                                      of term IDs as values.
1296       *         @type array  $terms_names    Associative array with taxonomy names as keys and arrays
1297       *                                      of term names as values.
1298       *         @type array  $enclosure      {
1299       *             Array of feed enclosure data to add to post meta.
1300       *
1301       *             @type string $url    URL for the feed enclosure.
1302       *             @type int    $length Size in bytes of the enclosure.
1303       *             @type string $type   Mime-type for the enclosure.
1304       *         }
1305       *     }
1306       * }
1307       * @return int|IXR_Error Post ID on success, IXR_Error instance otherwise.
1308       */
1309  	public function wp_newPost( $args ) {
1310          if ( ! $this->minimum_args( $args, 4 ) ) {
1311              return $this->error;
1312          }
1313  
1314          $this->escape( $args );
1315  
1316          $username       = $args[1];
1317          $password       = $args[2];
1318          $content_struct = $args[3];
1319  
1320          $user = $this->login( $username, $password );
1321          if ( ! $user ) {
1322              return $this->error;
1323          }
1324  
1325          // Convert the date field back to IXR form.
1326          if ( isset( $content_struct['post_date'] ) && ! ( $content_struct['post_date'] instanceof IXR_Date ) ) {
1327              $content_struct['post_date'] = $this->_convert_date( $content_struct['post_date'] );
1328          }
1329  
1330          /*
1331           * Ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct,
1332           * since _insert_post() will ignore the non-GMT date if the GMT date is set.
1333           */
1334          if ( isset( $content_struct['post_date_gmt'] ) && ! ( $content_struct['post_date_gmt'] instanceof IXR_Date ) ) {
1335              if ( '0000-00-00 00:00:00' === $content_struct['post_date_gmt'] || isset( $content_struct['post_date'] ) ) {
1336                  unset( $content_struct['post_date_gmt'] );
1337              } else {
1338                  $content_struct['post_date_gmt'] = $this->_convert_date( $content_struct['post_date_gmt'] );
1339              }
1340          }
1341  
1342          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1343          do_action( 'xmlrpc_call', 'wp.newPost', $args, $this );
1344  
1345          unset( $content_struct['ID'] );
1346  
1347          return $this->_insert_post( $user, $content_struct );
1348      }
1349  
1350      /**
1351       * Helper method for filtering out elements from an array.
1352       *
1353       * @since 3.4.0
1354       *
1355       * @param int $count Number to compare to one.
1356       * @return bool True if the number is greater than one, false otherwise.
1357       */
1358  	private function _is_greater_than_one( $count ) {
1359          return $count > 1;
1360      }
1361  
1362      /**
1363       * Encapsulate the logic for sticking a post
1364       * and determining if the user has permission to do so
1365       *
1366       * @since 4.3.0
1367       *
1368       * @param array $post_data
1369       * @param bool  $update
1370       * @return void|IXR_Error
1371       */
1372  	private function _toggle_sticky( $post_data, $update = false ) {
1373          $post_type = get_post_type_object( $post_data['post_type'] );
1374  
1375          // Private and password-protected posts cannot be stickied.
1376          if ( 'private' === $post_data['post_status'] || ! empty( $post_data['post_password'] ) ) {
1377              // Error if the client tried to stick the post, otherwise, silently unstick.
1378              if ( ! empty( $post_data['sticky'] ) ) {
1379                  return new IXR_Error( 401, __( 'Sorry, you cannot stick a private post.' ) );
1380              }
1381  
1382              if ( $update ) {
1383                  unstick_post( $post_data['ID'] );
1384              }
1385          } elseif ( isset( $post_data['sticky'] ) ) {
1386              if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
1387                  return new IXR_Error( 401, __( 'Sorry, you are not allowed to make posts sticky.' ) );
1388              }
1389  
1390              $sticky = wp_validate_boolean( $post_data['sticky'] );
1391              if ( $sticky ) {
1392                  stick_post( $post_data['ID'] );
1393              } else {
1394                  unstick_post( $post_data['ID'] );
1395              }
1396          }
1397      }
1398  
1399      /**
1400       * Helper method for wp_newPost() and wp_editPost(), containing shared logic.
1401       *
1402       * @since 3.4.0
1403       *
1404       * @see wp_insert_post()
1405       *
1406       * @param WP_User         $user           The post author if post_author isn't set in $content_struct.
1407       * @param array|IXR_Error $content_struct Post data to insert.
1408       * @return IXR_Error|string
1409       */
1410  	protected function _insert_post( $user, $content_struct ) {
1411          $defaults = array(
1412              'post_status'    => 'draft',
1413              'post_type'      => 'post',
1414              'post_author'    => null,
1415              'post_password'  => null,
1416              'post_excerpt'   => null,
1417              'post_content'   => null,
1418              'post_title'     => null,
1419              'post_date'      => null,
1420              'post_date_gmt'  => null,
1421              'post_format'    => null,
1422              'post_name'      => null,
1423              'post_thumbnail' => null,
1424              'post_parent'    => null,
1425              'ping_status'    => null,
1426              'comment_status' => null,
1427              'custom_fields'  => null,
1428              'terms_names'    => null,
1429              'terms'          => null,
1430              'sticky'         => null,
1431              'enclosure'      => null,
1432              'ID'             => null,
1433          );
1434  
1435          $post_data = wp_parse_args( array_intersect_key( $content_struct, $defaults ), $defaults );
1436  
1437          $post_type = get_post_type_object( $post_data['post_type'] );
1438          if ( ! $post_type ) {
1439              return new IXR_Error( 403, __( 'Invalid post type.' ) );
1440          }
1441  
1442          $update = ! empty( $post_data['ID'] );
1443  
1444          if ( $update ) {
1445              if ( ! get_post( $post_data['ID'] ) ) {
1446                  return new IXR_Error( 401, __( 'Invalid post ID.' ) );
1447              }
1448              if ( ! current_user_can( 'edit_post', $post_data['ID'] ) ) {
1449                  return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
1450              }
1451              if ( get_post_type( $post_data['ID'] ) !== $post_data['post_type'] ) {
1452                  return new IXR_Error( 401, __( 'The post type may not be changed.' ) );
1453              }
1454          } else {
1455              if ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( $post_type->cap->edit_posts ) ) {
1456                  return new IXR_Error( 401, __( 'Sorry, you are not allowed to post on this site.' ) );
1457              }
1458          }
1459  
1460          switch ( $post_data['post_status'] ) {
1461              case 'draft':
1462              case 'pending':
1463                  break;
1464              case 'private':
1465                  if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
1466                      return new IXR_Error( 401, __( 'Sorry, you are not allowed to create private posts in this post type.' ) );
1467                  }
1468                  break;
1469              case 'publish':
1470              case 'future':
1471                  if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
1472                      return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts in this post type.' ) );
1473                  }
1474                  break;
1475              default:
1476                  if ( ! get_post_status_object( $post_data['post_status'] ) ) {
1477                      $post_data['post_status'] = 'draft';
1478                  }
1479                  break;
1480          }
1481  
1482          if ( ! empty( $post_data['post_password'] ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
1483              return new IXR_Error( 401, __( 'Sorry, you are not allowed to create password protected posts in this post type.' ) );
1484          }
1485  
1486          $post_data['post_author'] = absint( $post_data['post_author'] );
1487          if ( ! empty( $post_data['post_author'] ) && $post_data['post_author'] != $user->ID ) {
1488              if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
1489                  return new IXR_Error( 401, __( 'Sorry, you are not allowed to create posts as this user.' ) );
1490              }
1491  
1492              $author = get_userdata( $post_data['post_author'] );
1493  
1494              if ( ! $author ) {
1495                  return new IXR_Error( 404, __( 'Invalid author ID.' ) );
1496              }
1497          } else {
1498              $post_data['post_author'] = $user->ID;
1499          }
1500  
1501          if ( isset( $post_data['comment_status'] ) && 'open' !== $post_data['comment_status'] && 'closed' !== $post_data['comment_status'] ) {
1502              unset( $post_data['comment_status'] );
1503          }
1504  
1505          if ( isset( $post_data['ping_status'] ) && 'open' !== $post_data['ping_status'] && 'closed' !== $post_data['ping_status'] ) {
1506              unset( $post_data['ping_status'] );
1507          }
1508  
1509          // Do some timestamp voodoo.
1510          if ( ! empty( $post_data['post_date_gmt'] ) ) {
1511              // We know this is supposed to be GMT, so we're going to slap that Z on there by force.
1512              $dateCreated = rtrim( $post_data['post_date_gmt']->getIso(), 'Z' ) . 'Z';
1513          } elseif ( ! empty( $post_data['post_date'] ) ) {
1514              $dateCreated = $post_data['post_date']->getIso();
1515          }
1516  
1517          // Default to not flagging the post date to be edited unless it's intentional.
1518          $post_data['edit_date'] = false;
1519  
1520          if ( ! empty( $dateCreated ) ) {
1521              $post_data['post_date']     = iso8601_to_datetime( $dateCreated );
1522              $post_data['post_date_gmt'] = iso8601_to_datetime( $dateCreated, 'gmt' );
1523  
1524              // Flag the post date to be edited.
1525              $post_data['edit_date'] = true;
1526          }
1527  
1528          if ( ! isset( $post_data['ID'] ) ) {
1529              $post_data['ID'] = get_default_post_to_edit( $post_data['post_type'], true )->ID;
1530          }
1531          $post_ID = $post_data['ID'];
1532  
1533          if ( 'post' === $post_data['post_type'] ) {
1534              $error = $this->_toggle_sticky( $post_data, $update );
1535              if ( $error ) {
1536                  return $error;
1537              }
1538          }
1539  
1540          if ( isset( $post_data['post_thumbnail'] ) ) {
1541              // Empty value deletes, non-empty value adds/updates.
1542              if ( ! $post_data['post_thumbnail'] ) {
1543                  delete_post_thumbnail( $post_ID );
1544              } elseif ( ! get_post( absint( $post_data['post_thumbnail'] ) ) ) {
1545                  return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
1546              }
1547              set_post_thumbnail( $post_ID, $post_data['post_thumbnail'] );
1548              unset( $content_struct['post_thumbnail'] );
1549          }
1550  
1551          if ( isset( $post_data['custom_fields'] ) ) {
1552              $this->set_custom_fields( $post_ID, $post_data['custom_fields'] );
1553          }
1554  
1555          if ( isset( $post_data['terms'] ) || isset( $post_data['terms_names'] ) ) {
1556              $post_type_taxonomies = get_object_taxonomies( $post_data['post_type'], 'objects' );
1557  
1558              // Accumulate term IDs from terms and terms_names.
1559              $terms = array();
1560  
1561              // First validate the terms specified by ID.
1562              if ( isset( $post_data['terms'] ) && is_array( $post_data['terms'] ) ) {
1563                  $taxonomies = array_keys( $post_data['terms'] );
1564  
1565                  // Validating term IDs.
1566                  foreach ( $taxonomies as $taxonomy ) {
1567                      if ( ! array_key_exists( $taxonomy, $post_type_taxonomies ) ) {
1568                          return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
1569                      }
1570  
1571                      if ( ! current_user_can( $post_type_taxonomies[ $taxonomy ]->cap->assign_terms ) ) {
1572                          return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
1573                      }
1574  
1575                      $term_ids           = $post_data['terms'][ $taxonomy ];
1576                      $terms[ $taxonomy ] = array();
1577                      foreach ( $term_ids as $term_id ) {
1578                          $term = get_term_by( 'id', $term_id, $taxonomy );
1579  
1580                          if ( ! $term ) {
1581                              return new IXR_Error( 403, __( 'Invalid term ID.' ) );
1582                          }
1583  
1584                          $terms[ $taxonomy ][] = (int) $term_id;
1585                      }
1586                  }
1587              }
1588  
1589              // Now validate terms specified by name.
1590              if ( isset( $post_data['terms_names'] ) && is_array( $post_data['terms_names'] ) ) {
1591                  $taxonomies = array_keys( $post_data['terms_names'] );
1592  
1593                  foreach ( $taxonomies as $taxonomy ) {
1594                      if ( ! array_key_exists( $taxonomy, $post_type_taxonomies ) ) {
1595                          return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
1596                      }
1597  
1598                      if ( ! current_user_can( $post_type_taxonomies[ $taxonomy ]->cap->assign_terms ) ) {
1599                          return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
1600                      }
1601  
1602                      /*
1603                       * For hierarchical taxonomies, we can't assign a term when multiple terms
1604                       * in the hierarchy share the same name.
1605                       */
1606                      $ambiguous_terms = array();
1607                      if ( is_taxonomy_hierarchical( $taxonomy ) ) {
1608                          $tax_term_names = get_terms(
1609                              array(
1610                                  'taxonomy'   => $taxonomy,
1611                                  'fields'     => 'names',
1612                                  'hide_empty' => false,
1613                              )
1614                          );
1615  
1616                          // Count the number of terms with the same name.
1617                          $tax_term_names_count = array_count_values( $tax_term_names );
1618  
1619                          // Filter out non-ambiguous term names.
1620                          $ambiguous_tax_term_counts = array_filter( $tax_term_names_count, array( $this, '_is_greater_than_one' ) );
1621  
1622                          $ambiguous_terms = array_keys( $ambiguous_tax_term_counts );
1623                      }
1624  
1625                      $term_names = $post_data['terms_names'][ $taxonomy ];
1626                      foreach ( $term_names as $term_name ) {
1627                          if ( in_array( $term_name, $ambiguous_terms, true ) ) {
1628                              return new IXR_Error( 401, __( 'Ambiguous term name used in a hierarchical taxonomy. Please use term ID instead.' ) );
1629                          }
1630  
1631                          $term = get_term_by( 'name', $term_name, $taxonomy );
1632  
1633                          if ( ! $term ) {
1634                              // Term doesn't exist, so check that the user is allowed to create new terms.
1635                              if ( ! current_user_can( $post_type_taxonomies[ $taxonomy ]->cap->edit_terms ) ) {
1636                                  return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a term to one of the given taxonomies.' ) );
1637                              }
1638  
1639                              // Create the new term.
1640                              $term_info = wp_insert_term( $term_name, $taxonomy );
1641                              if ( is_wp_error( $term_info ) ) {
1642                                  return new IXR_Error( 500, $term_info->get_error_message() );
1643                              }
1644  
1645                              $terms[ $taxonomy ][] = (int) $term_info['term_id'];
1646                          } else {
1647                              $terms[ $taxonomy ][] = (int) $term->term_id;
1648                          }
1649                      }
1650                  }
1651              }
1652  
1653              $post_data['tax_input'] = $terms;
1654              unset( $post_data['terms'], $post_data['terms_names'] );
1655          }
1656  
1657          if ( isset( $post_data['post_format'] ) ) {
1658              $format = set_post_format( $post_ID, $post_data['post_format'] );
1659  
1660              if ( is_wp_error( $format ) ) {
1661                  return new IXR_Error( 500, $format->get_error_message() );
1662              }
1663  
1664              unset( $post_data['post_format'] );
1665          }
1666  
1667          // Handle enclosures.
1668          $enclosure = isset( $post_data['enclosure'] ) ? $post_data['enclosure'] : null;
1669          $this->add_enclosure_if_new( $post_ID, $enclosure );
1670  
1671          $this->attach_uploads( $post_ID, $post_data['post_content'] );
1672  
1673          /**
1674           * Filters post data array to be inserted via XML-RPC.
1675           *
1676           * @since 3.4.0
1677           *
1678           * @param array $post_data      Parsed array of post data.
1679           * @param array $content_struct Post data array.
1680           */
1681          $post_data = apply_filters( 'xmlrpc_wp_insert_post_data', $post_data, $content_struct );
1682  
1683          $post_ID = $update ? wp_update_post( $post_data, true ) : wp_insert_post( $post_data, true );
1684          if ( is_wp_error( $post_ID ) ) {
1685              return new IXR_Error( 500, $post_ID->get_error_message() );
1686          }
1687  
1688          if ( ! $post_ID ) {
1689              if ( $update ) {
1690                  return new IXR_Error( 401, __( 'Sorry, the post could not be updated.' ) );
1691              } else {
1692                  return new IXR_Error( 401, __( 'Sorry, the post could not be created.' ) );
1693              }
1694          }
1695  
1696          return (string) $post_ID;
1697      }
1698  
1699      /**
1700       * Edit a post for any registered post type.
1701       *
1702       * The $content_struct parameter only needs to contain fields that
1703       * should be changed. All other fields will retain their existing values.
1704       *
1705       * @since 3.4.0
1706       *
1707       * @param array $args {
1708       *     Method arguments. Note: arguments must be ordered as documented.
1709       *
1710       *     @type int    $blog_id        Blog ID (unused).
1711       *     @type string $username       Username.
1712       *     @type string $password       Password.
1713       *     @type int    $post_id        Post ID.
1714       *     @type array  $content_struct Extra content arguments.
1715       * }
1716       * @return true|IXR_Error True on success, IXR_Error on failure.
1717       */
1718  	public function wp_editPost( $args ) {
1719          if ( ! $this->minimum_args( $args, 5 ) ) {
1720              return $this->error;
1721          }
1722  
1723          $this->escape( $args );
1724  
1725          $username       = $args[1];
1726          $password       = $args[2];
1727          $post_id        = (int) $args[3];
1728          $content_struct = $args[4];
1729  
1730          $user = $this->login( $username, $password );
1731          if ( ! $user ) {
1732              return $this->error;
1733          }
1734  
1735          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1736          do_action( 'xmlrpc_call', 'wp.editPost', $args, $this );
1737  
1738          $post = get_post( $post_id, ARRAY_A );
1739  
1740          if ( empty( $post['ID'] ) ) {
1741              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1742          }
1743  
1744          if ( isset( $content_struct['if_not_modified_since'] ) ) {
1745              // If the post has been modified since the date provided, return an error.
1746              if ( mysql2date( 'U', $post['post_modified_gmt'] ) > $content_struct['if_not_modified_since']->getTimestamp() ) {
1747                  return new IXR_Error( 409, __( 'There is a revision of this post that is more recent.' ) );
1748              }
1749          }
1750  
1751          // Convert the date field back to IXR form.
1752          $post['post_date'] = $this->_convert_date( $post['post_date'] );
1753  
1754          /*
1755           * Ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct,
1756           * since _insert_post() will ignore the non-GMT date if the GMT date is set.
1757           */
1758          if ( '0000-00-00 00:00:00' === $post['post_date_gmt'] || isset( $content_struct['post_date'] ) ) {
1759              unset( $post['post_date_gmt'] );
1760          } else {
1761              $post['post_date_gmt'] = $this->_convert_date( $post['post_date_gmt'] );
1762          }
1763  
1764          /*
1765           * If the API client did not provide 'post_date', then we must not perpetuate the value that
1766           * was stored in the database, or it will appear to be an intentional edit. Conveying it here
1767           * as if it was coming from the API client will cause an otherwise zeroed out 'post_date_gmt'
1768           * to get set with the value that was originally stored in the database when the draft was created.
1769           */
1770          if ( ! isset( $content_struct['post_date'] ) ) {
1771              unset( $post['post_date'] );
1772          }
1773  
1774          $this->escape( $post );
1775          $merged_content_struct = array_merge( $post, $content_struct );
1776  
1777          $retval = $this->_insert_post( $user, $merged_content_struct );
1778          if ( $retval instanceof IXR_Error ) {
1779              return $retval;
1780          }
1781  
1782          return true;
1783      }
1784  
1785      /**
1786       * Delete a post for any registered post type.
1787       *
1788       * @since 3.4.0
1789       *
1790       * @see wp_delete_post()
1791       *
1792       * @param array $args {
1793       *     Method arguments. Note: arguments must be ordered as documented.
1794       *
1795       *     @type int    $blog_id  Blog ID (unused).
1796       *     @type string $username Username.
1797       *     @type string $password Password.
1798       *     @type int    $post_id  Post ID.
1799       * }
1800       * @return true|IXR_Error True on success, IXR_Error instance on failure.
1801       */
1802  	public function wp_deletePost( $args ) {
1803          if ( ! $this->minimum_args( $args, 4 ) ) {
1804              return $this->error;
1805          }
1806  
1807          $this->escape( $args );
1808  
1809          $username = $args[1];
1810          $password = $args[2];
1811          $post_id  = (int) $args[3];
1812  
1813          $user = $this->login( $username, $password );
1814          if ( ! $user ) {
1815              return $this->error;
1816          }
1817  
1818          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1819          do_action( 'xmlrpc_call', 'wp.deletePost', $args, $this );
1820  
1821          $post = get_post( $post_id, ARRAY_A );
1822          if ( empty( $post['ID'] ) ) {
1823              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1824          }
1825  
1826          if ( ! current_user_can( 'delete_post', $post_id ) ) {
1827              return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this post.' ) );
1828          }
1829  
1830          $result = wp_delete_post( $post_id );
1831  
1832          if ( ! $result ) {
1833              return new IXR_Error( 500, __( 'Sorry, the post could not be deleted.' ) );
1834          }
1835  
1836          return true;
1837      }
1838  
1839      /**
1840       * Retrieve a post.
1841       *
1842       * @since 3.4.0
1843       *
1844       * The optional $fields parameter specifies what fields will be included
1845       * in the response array. This should be a list of field names. 'post_id' will
1846       * always be included in the response regardless of the value of $fields.
1847       *
1848       * Instead of, or in addition to, individual field names, conceptual group
1849       * names can be used to specify multiple fields. The available conceptual
1850       * groups are 'post' (all basic fields), 'taxonomies', 'custom_fields',
1851       * and 'enclosure'.
1852       *
1853       * @see get_post()
1854       *
1855       * @param array $args {
1856       *     Method arguments. Note: arguments must be ordered as documented.
1857       *
1858       *     @type int    $blog_id  Blog ID (unused).
1859       *     @type string $username Username.
1860       *     @type string $password Password.
1861       *     @type int    $post_id  Post ID.
1862       *     @type array  $fields   The subset of post type fields to return.
1863       * }
1864       * @return array|IXR_Error Array contains (based on $fields parameter):
1865       *  - 'post_id'
1866       *  - 'post_title'
1867       *  - 'post_date'
1868       *  - 'post_date_gmt'
1869       *  - 'post_modified'
1870       *  - 'post_modified_gmt'
1871       *  - 'post_status'
1872       *  - 'post_type'
1873       *  - 'post_name'
1874       *  - 'post_author'
1875       *  - 'post_password'
1876       *  - 'post_excerpt'
1877       *  - 'post_content'
1878       *  - 'link'
1879       *  - 'comment_status'
1880       *  - 'ping_status'
1881       *  - 'sticky'
1882       *  - 'custom_fields'
1883       *  - 'terms'
1884       *  - 'categories'
1885       *  - 'tags'
1886       *  - 'enclosure'
1887       */
1888  	public function wp_getPost( $args ) {
1889          if ( ! $this->minimum_args( $args, 4 ) ) {
1890              return $this->error;
1891          }
1892  
1893          $this->escape( $args );
1894  
1895          $username = $args[1];
1896          $password = $args[2];
1897          $post_id  = (int) $args[3];
1898  
1899          if ( isset( $args[4] ) ) {
1900              $fields = $args[4];
1901          } else {
1902              /**
1903               * Filters the list of post query fields used by the given XML-RPC method.
1904               *
1905               * @since 3.4.0
1906               *
1907               * @param array  $fields Array of post fields. Default array contains 'post', 'terms', and 'custom_fields'.
1908               * @param string $method Method name.
1909               */
1910              $fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPost' );
1911          }
1912  
1913          $user = $this->login( $username, $password );
1914          if ( ! $user ) {
1915              return $this->error;
1916          }
1917  
1918          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1919          do_action( 'xmlrpc_call', 'wp.getPost', $args, $this );
1920  
1921          $post = get_post( $post_id, ARRAY_A );
1922  
1923          if ( empty( $post['ID'] ) ) {
1924              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1925          }
1926  
1927          if ( ! current_user_can( 'edit_post', $post_id ) ) {
1928              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
1929          }
1930  
1931          return $this->_prepare_post( $post, $fields );
1932      }
1933  
1934      /**
1935       * Retrieve posts.
1936       *
1937       * @since 3.4.0
1938       *
1939       * @see wp_get_recent_posts()
1940       * @see wp_getPost() for more on `$fields`
1941       * @see get_posts() for more on `$filter` values
1942       *
1943       * @param array $args {
1944       *     Method arguments. Note: arguments must be ordered as documented.
1945       *
1946       *     @type int    $blog_id  Blog ID (unused).
1947       *     @type string $username Username.
1948       *     @type string $password Password.
1949       *     @type array  $filter   Optional. Modifies the query used to retrieve posts. Accepts 'post_type',
1950       *                            'post_status', 'number', 'offset', 'orderby', 's', and 'order'.
1951       *                            Default empty array.
1952       *     @type array  $fields   Optional. The subset of post type fields to return in the response array.
1953       * }
1954       * @return array|IXR_Error Array contains a collection of posts.
1955       */
1956  	public function wp_getPosts( $args ) {
1957          if ( ! $this->minimum_args( $args, 3 ) ) {
1958              return $this->error;
1959          }
1960  
1961          $this->escape( $args );
1962  
1963          $username = $args[1];
1964          $password = $args[2];
1965          $filter   = isset( $args[3] ) ? $args[3] : array();
1966  
1967          if ( isset( $args[4] ) ) {
1968              $fields = $args[4];
1969          } else {
1970              /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1971              $fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPosts' );
1972          }
1973  
1974          $user = $this->login( $username, $password );
1975          if ( ! $user ) {
1976              return $this->error;
1977          }
1978  
1979          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1980          do_action( 'xmlrpc_call', 'wp.getPosts', $args, $this );
1981  
1982          $query = array();
1983  
1984          if ( isset( $filter['post_type'] ) ) {
1985              $post_type = get_post_type_object( $filter['post_type'] );
1986              if ( ! ( (bool) $post_type ) ) {
1987                  return new IXR_Error( 403, __( 'Invalid post type.' ) );
1988              }
1989          } else {
1990              $post_type = get_post_type_object( 'post' );
1991          }
1992  
1993          if ( ! current_user_can( $post_type->cap->edit_posts ) ) {
1994              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type.' ) );
1995          }
1996  
1997          $query['post_type'] = $post_type->name;
1998  
1999          if ( isset( $filter['post_status'] ) ) {
2000              $query['post_status'] = $filter['post_status'];
2001          }
2002  
2003          if ( isset( $filter['number'] ) ) {
2004              $query['numberposts'] = absint( $filter['number'] );
2005          }
2006  
2007          if ( isset( $filter['offset'] ) ) {
2008              $query['offset'] = absint( $filter['offset'] );
2009          }
2010  
2011          if ( isset( $filter['orderby'] ) ) {
2012              $query['orderby'] = $filter['orderby'];
2013  
2014              if ( isset( $filter['order'] ) ) {
2015                  $query['order'] = $filter['order'];
2016              }
2017          }
2018  
2019          if ( isset( $filter['s'] ) ) {
2020              $query['s'] = $filter['s'];
2021          }
2022  
2023          $posts_list = wp_get_recent_posts( $query );
2024  
2025          if ( ! $posts_list ) {
2026              return array();
2027          }
2028  
2029          // Holds all the posts data.
2030          $struct = array();
2031  
2032          foreach ( $posts_list as $post ) {
2033              if ( ! current_user_can( 'edit_post', $post['ID'] ) ) {
2034                  continue;
2035              }
2036  
2037              $struct[] = $this->_prepare_post( $post, $fields );
2038          }
2039  
2040          return $struct;
2041      }
2042  
2043      /**
2044       * Create a new term.
2045       *
2046       * @since 3.4.0
2047       *
2048       * @see wp_insert_term()
2049       *
2050       * @param array $args {
2051       *     Method arguments. Note: arguments must be ordered as documented.
2052       *
2053       *     @type int    $blog_id        Blog ID (unused).
2054       *     @type string $username       Username.
2055       *     @type string $password       Password.
2056       *     @type array  $content_struct Content struct for adding a new term. The struct must contain
2057       *                                  the term 'name' and 'taxonomy'. Optional accepted values include
2058       *                                  'parent', 'description', and 'slug'.
2059       * }
2060       * @return int|IXR_Error The term ID on success, or an IXR_Error object on failure.
2061       */
2062  	public function wp_newTerm( $args ) {
2063          if ( ! $this->minimum_args( $args, 4 ) ) {
2064              return $this->error;
2065          }
2066  
2067          $this->escape( $args );
2068  
2069          $username       = $args[1];
2070          $password       = $args[2];
2071          $content_struct = $args[3];
2072  
2073          $user = $this->login( $username, $password );
2074          if ( ! $user ) {
2075              return $this->error;
2076          }
2077  
2078          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2079          do_action( 'xmlrpc_call', 'wp.newTerm', $args, $this );
2080  
2081          if ( ! taxonomy_exists( $content_struct['taxonomy'] ) ) {
2082              return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2083          }
2084  
2085          $taxonomy = get_taxonomy( $content_struct['taxonomy'] );
2086  
2087          if ( ! current_user_can( $taxonomy->cap->edit_terms ) ) {
2088              return new IXR_Error( 401, __( 'Sorry, you are not allowed to create terms in this taxonomy.' ) );
2089          }
2090  
2091          $taxonomy = (array) $taxonomy;
2092  
2093          // Hold the data of the term.
2094          $term_data = array();
2095  
2096          $term_data['name'] = trim( $content_struct['name'] );
2097          if ( empty( $term_data['name'] ) ) {
2098              return new IXR_Error( 403, __( 'The term name cannot be empty.' ) );
2099          }
2100  
2101          if ( isset( $content_struct['parent'] ) ) {
2102              if ( ! $taxonomy['hierarchical'] ) {
2103                  return new IXR_Error( 403, __( 'This taxonomy is not hierarchical.' ) );
2104              }
2105  
2106              $parent_term_id = (int) $content_struct['parent'];
2107              $parent_term    = get_term( $parent_term_id, $taxonomy['name'] );
2108  
2109              if ( is_wp_error( $parent_term ) ) {
2110                  return new IXR_Error( 500, $parent_term->get_error_message() );
2111              }
2112  
2113              if ( ! $parent_term ) {
2114                  return new IXR_Error( 403, __( 'Parent term does not exist.' ) );
2115              }
2116  
2117              $term_data['parent'] = $content_struct['parent'];
2118          }
2119  
2120          if ( isset( $content_struct['description'] ) ) {
2121              $term_data['description'] = $content_struct['description'];
2122          }
2123  
2124          if ( isset( $content_struct['slug'] ) ) {
2125              $term_data['slug'] = $content_struct['slug'];
2126          }
2127  
2128          $term = wp_insert_term( $term_data['name'], $taxonomy['name'], $term_data );
2129  
2130          if ( is_wp_error( $term ) ) {
2131              return new IXR_Error( 500, $term->get_error_message() );
2132          }
2133  
2134          if ( ! $term ) {
2135              return new IXR_Error( 500, __( 'Sorry, the term could not be created.' ) );
2136          }
2137  
2138          // Add term meta.
2139          if ( isset( $content_struct['custom_fields'] ) ) {
2140              $this->set_term_custom_fields( $term['term_id'], $content_struct['custom_fields'] );
2141          }
2142  
2143          return (string) $term['term_id'];
2144      }
2145  
2146      /**
2147       * Edit a term.
2148       *
2149       * @since 3.4.0
2150       *
2151       * @see wp_update_term()
2152       *
2153       * @param array $args {
2154       *     Method arguments. Note: arguments must be ordered as documented.
2155       *
2156       *     @type int    $blog_id        Blog ID (unused).
2157       *     @type string $username       Username.
2158       *     @type string $password       Password.
2159       *     @type int    $term_id        Term ID.
2160       *     @type array  $content_struct Content struct for editing a term. The struct must contain the
2161       *                                  term ''taxonomy'. Optional accepted values include 'name', 'parent',
2162       *                                  'description', and 'slug'.
2163       * }
2164       * @return true|IXR_Error True on success, IXR_Error instance on failure.
2165       */
2166  	public function wp_editTerm( $args ) {
2167          if ( ! $this->minimum_args( $args, 5 ) ) {
2168              return $this->error;
2169          }
2170  
2171          $this->escape( $args );
2172  
2173          $username       = $args[1];
2174          $password       = $args[2];
2175          $term_id        = (int) $args[3];
2176          $content_struct = $args[4];
2177  
2178          $user = $this->login( $username, $password );
2179          if ( ! $user ) {
2180              return $this->error;
2181          }
2182  
2183          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2184          do_action( 'xmlrpc_call', 'wp.editTerm', $args, $this );
2185  
2186          if ( ! taxonomy_exists( $content_struct['taxonomy'] ) ) {
2187              return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2188          }
2189  
2190          $taxonomy = get_taxonomy( $content_struct['taxonomy'] );
2191  
2192          $taxonomy = (array) $taxonomy;
2193  
2194          // Hold the data of the term.
2195          $term_data = array();
2196  
2197          $term = get_term( $term_id, $content_struct['taxonomy'] );
2198  
2199          if ( is_wp_error( $term ) ) {
2200              return new IXR_Error( 500, $term->get_error_message() );
2201          }
2202  
2203          if ( ! $term ) {
2204              return new IXR_Error( 404, __( 'Invalid term ID.' ) );
2205          }
2206  
2207          if ( ! current_user_can( 'edit_term', $term_id ) ) {
2208              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this term.' ) );
2209          }
2210  
2211          if ( isset( $content_struct['name'] ) ) {
2212              $term_data['name'] = trim( $content_struct['name'] );
2213  
2214              if ( empty( $term_data['name'] ) ) {
2215                  return new IXR_Error( 403, __( 'The term name cannot be empty.' ) );
2216              }
2217          }
2218  
2219          if ( ! empty( $content_struct['parent'] ) ) {
2220              if ( ! $taxonomy['hierarchical'] ) {
2221                  return new IXR_Error( 403, __( 'Cannot set parent term, taxonomy is not hierarchical.' ) );
2222              }
2223  
2224              $parent_term_id = (int) $content_struct['parent'];
2225              $parent_term    = get_term( $parent_term_id, $taxonomy['name'] );
2226  
2227              if ( is_wp_error( $parent_term ) ) {
2228                  return new IXR_Error( 500, $parent_term->get_error_message() );
2229              }
2230  
2231              if ( ! $parent_term ) {
2232                  return new IXR_Error( 403, __( 'Parent term does not exist.' ) );
2233              }
2234  
2235              $term_data['parent'] = $content_struct['parent'];
2236          }
2237  
2238          if ( isset( $content_struct['description'] ) ) {
2239              $term_data['description'] = $content_struct['description'];
2240          }
2241  
2242          if ( isset( $content_struct['slug'] ) ) {
2243              $term_data['slug'] = $content_struct['slug'];
2244          }
2245  
2246          $term = wp_update_term( $term_id, $taxonomy['name'], $term_data );
2247  
2248          if ( is_wp_error( $term ) ) {
2249              return new IXR_Error( 500, $term->get_error_message() );
2250          }
2251  
2252          if ( ! $term ) {
2253              return new IXR_Error( 500, __( 'Sorry, editing the term failed.' ) );
2254          }
2255  
2256          // Update term meta.
2257          if ( isset( $content_struct['custom_fields'] ) ) {
2258              $this->set_term_custom_fields( $term_id, $content_struct['custom_fields'] );
2259          }
2260  
2261          return true;
2262      }
2263  
2264      /**
2265       * Delete a term.
2266       *
2267       * @since 3.4.0
2268       *
2269       * @see wp_delete_term()
2270       *
2271       * @param array $args {
2272       *     Method arguments. Note: arguments must be ordered as documented.
2273       *
2274       *     @type int    $blog_id       Blog ID (unused).
2275       *     @type string $username      Username.
2276       *     @type string $password      Password.
2277       *     @type string $taxonomy_name Taxonomy name.
2278       *     @type int    $term_id       Term ID.
2279       * }
2280       * @return true|IXR_Error True on success, IXR_Error instance on failure.
2281       */
2282  	public function wp_deleteTerm( $args ) {
2283          if ( ! $this->minimum_args( $args, 5 ) ) {
2284              return $this->error;
2285          }
2286  
2287          $this->escape( $args );
2288  
2289          $username = $args[1];
2290          $password = $args[2];
2291          $taxonomy = $args[3];
2292          $term_id  = (int) $args[4];
2293  
2294          $user = $this->login( $username, $password );
2295          if ( ! $user ) {
2296              return $this->error;
2297          }
2298  
2299          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2300          do_action( 'xmlrpc_call', 'wp.deleteTerm', $args, $this );
2301  
2302          if ( ! taxonomy_exists( $taxonomy ) ) {
2303              return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2304          }
2305  
2306          $taxonomy = get_taxonomy( $taxonomy );
2307          $term     = get_term( $term_id, $taxonomy->name );
2308  
2309          if ( is_wp_error( $term ) ) {
2310              return new IXR_Error( 500, $term->get_error_message() );
2311          }
2312  
2313          if ( ! $term ) {
2314              return new IXR_Error( 404, __( 'Invalid term ID.' ) );
2315          }
2316  
2317          if ( ! current_user_can( 'delete_term', $term_id ) ) {
2318              return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this term.' ) );
2319          }
2320  
2321          $result = wp_delete_term( $term_id, $taxonomy->name );
2322  
2323          if ( is_wp_error( $result ) ) {
2324              return new IXR_Error( 500, $term->get_error_message() );
2325          }
2326  
2327          if ( ! $result ) {
2328              return new IXR_Error( 500, __( 'Sorry, deleting the term failed.' ) );
2329          }
2330  
2331          return $result;
2332      }
2333  
2334      /**
2335       * Retrieve a term.
2336       *
2337       * @since 3.4.0
2338       *
2339       * @see get_term()
2340       *
2341       * @param array $args {
2342       *     Method arguments. Note: arguments must be ordered as documented.
2343       *
2344       *     @type int    $blog_id  Blog ID (unused).
2345       *     @type string $username Username.
2346       *     @type string $password Password.
2347       *     @type string $taxonomy Taxonomy name.
2348       *     @type string $term_id  Term ID.
2349       * }
2350       * @return array|IXR_Error IXR_Error on failure, array on success, containing:
2351       *  - 'term_id'
2352       *  - 'name'
2353       *  - 'slug'
2354       *  - 'term_group'
2355       *  - 'term_taxonomy_id'
2356       *  - 'taxonomy'
2357       *  - 'description'
2358       *  - 'parent'
2359       *  - 'count'
2360       */
2361  	public function wp_getTerm( $args ) {
2362          if ( ! $this->minimum_args( $args, 5 ) ) {
2363              return $this->error;
2364          }
2365  
2366          $this->escape( $args );
2367  
2368          $username = $args[1];
2369          $password = $args[2];
2370          $taxonomy = $args[3];
2371          $term_id  = (int) $args[4];
2372  
2373          $user = $this->login( $username, $password );
2374          if ( ! $user ) {
2375              return $this->error;
2376          }
2377  
2378          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2379          do_action( 'xmlrpc_call', 'wp.getTerm', $args, $this );
2380  
2381          if ( ! taxonomy_exists( $taxonomy ) ) {
2382              return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2383          }
2384  
2385          $taxonomy = get_taxonomy( $taxonomy );
2386  
2387          $term = get_term( $term_id, $taxonomy->name, ARRAY_A );
2388  
2389          if ( is_wp_error( $term ) ) {
2390              return new IXR_Error( 500, $term->get_error_message() );
2391          }
2392  
2393          if ( ! $term ) {
2394              return new IXR_Error( 404, __( 'Invalid term ID.' ) );
2395          }
2396  
2397          if ( ! current_user_can( 'assign_term', $term_id ) ) {
2398              return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign this term.' ) );
2399          }
2400  
2401          return $this->_prepare_term( $term );
2402      }
2403  
2404      /**
2405       * Retrieve all terms for a taxonomy.
2406       *
2407       * @since 3.4.0
2408       *
2409       * The optional $filter parameter modifies the query used to retrieve terms.
2410       * Accepted keys are 'number', 'offset', 'orderby', 'order', 'hide_empty', and 'search'.
2411       *
2412       * @see get_terms()
2413       *
2414       * @param array $args {
2415       *     Method arguments. Note: arguments must be ordered as documented.
2416       *
2417       *     @type int    $blog_id  Blog ID (unused).
2418       *     @type string $username Username.
2419       *     @type string $password Password.
2420       *     @type string $taxonomy Taxonomy name.
2421       *     @type array  $filter   Optional. Modifies the query used to retrieve posts. Accepts 'number',
2422       *                            'offset', 'orderby', 'order', 'hide_empty', and 'search'. Default empty array.
2423       * }
2424       * @return array|IXR_Error An associative array of terms data on success, IXR_Error instance otherwise.
2425       */
2426  	public function wp_getTerms( $args ) {
2427          if ( ! $this->minimum_args( $args, 4 ) ) {
2428              return $this->error;
2429          }
2430  
2431          $this->escape( $args );
2432  
2433          $username = $args[1];
2434          $password = $args[2];
2435          $taxonomy = $args[3];
2436          $filter   = isset( $args[4] ) ? $args[4] : array();
2437  
2438          $user = $this->login( $username, $password );
2439          if ( ! $user ) {
2440              return $this->error;
2441          }
2442  
2443          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2444          do_action( 'xmlrpc_call', 'wp.getTerms', $args, $this );
2445  
2446          if ( ! taxonomy_exists( $taxonomy ) ) {
2447              return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2448          }
2449  
2450          $taxonomy = get_taxonomy( $taxonomy );
2451  
2452          if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) {
2453              return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) );
2454          }
2455  
2456          $query = array( 'taxonomy' => $taxonomy->name );
2457  
2458          if ( isset( $filter['number'] ) ) {
2459              $query['number'] = absint( $filter['number'] );
2460          }
2461  
2462          if ( isset( $filter['offset'] ) ) {
2463              $query['offset'] = absint( $filter['offset'] );
2464          }
2465  
2466          if ( isset( $filter['orderby'] ) ) {
2467              $query['orderby'] = $filter['orderby'];
2468  
2469              if ( isset( $filter['order'] ) ) {
2470                  $query['order'] = $filter['order'];
2471              }
2472          }
2473  
2474          if ( isset( $filter['hide_empty'] ) ) {
2475              $query['hide_empty'] = $filter['hide_empty'];
2476          } else {
2477              $query['get'] = 'all';
2478          }
2479  
2480          if ( isset( $filter['search'] ) ) {
2481              $query['search'] = $filter['search'];
2482          }
2483  
2484          $terms = get_terms( $query );
2485  
2486          if ( is_wp_error( $terms ) ) {
2487              return new IXR_Error( 500, $terms->get_error_message() );
2488          }
2489  
2490          $struct = array();
2491  
2492          foreach ( $terms as $term ) {
2493              $struct[] = $this->_prepare_term( $term );
2494          }
2495  
2496          return $struct;
2497      }
2498  
2499      /**
2500       * Retrieve a taxonomy.
2501       *
2502       * @since 3.4.0
2503       *
2504       * @see get_taxonomy()
2505       *
2506       * @param array $args {
2507       *     Method arguments. Note: arguments must be ordered as documented.
2508       *
2509       *     @type int    $blog_id  Blog ID (unused).
2510       *     @type string $username Username.
2511       *     @type string $password Password.
2512       *     @type string $taxonomy Taxonomy name.
2513       *     @type array  $fields   Optional. Array of taxonomy fields to limit to in the return.
2514       *                            Accepts 'labels', 'cap', 'menu', and 'object_type'.
2515       *                            Default empty array.
2516       * }
2517       * @return array|IXR_Error An array of taxonomy data on success, IXR_Error instance otherwise.
2518       */
2519  	public function wp_getTaxonomy( $args ) {
2520          if ( ! $this->minimum_args( $args, 4 ) ) {
2521              return $this->error;
2522          }
2523  
2524          $this->escape( $args );
2525  
2526          $username = $args[1];
2527          $password = $args[2];
2528          $taxonomy = $args[3];
2529  
2530          if ( isset( $args[4] ) ) {
2531              $fields = $args[4];
2532          } else {
2533              /**
2534               * Filters the taxonomy query fields used by the given XML-RPC method.
2535               *
2536               * @since 3.4.0
2537               *
2538               * @param array  $fields An array of taxonomy fields to retrieve.
2539               * @param string $method The method name.
2540               */
2541              $fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomy' );
2542          }
2543  
2544          $user = $this->login( $username, $password );
2545          if ( ! $user ) {
2546              return $this->error;
2547          }
2548  
2549          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2550          do_action( 'xmlrpc_call', 'wp.getTaxonomy', $args, $this );
2551  
2552          if ( ! taxonomy_exists( $taxonomy ) ) {
2553              return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2554          }
2555  
2556          $taxonomy = get_taxonomy( $taxonomy );
2557  
2558          if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) {
2559              return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) );
2560          }
2561  
2562          return $this->_prepare_taxonomy( $taxonomy, $fields );
2563      }
2564  
2565      /**
2566       * Retrieve all taxonomies.
2567       *
2568       * @since 3.4.0
2569       *
2570       * @see get_taxonomies()
2571       *
2572       * @param array $args {
2573       *     Method arguments. Note: arguments must be ordered as documented.
2574       *
2575       *     @type int    $blog_id  Blog ID (unused).
2576       *     @type string $username Username.
2577       *     @type string $password Password.
2578       *     @type array  $filter   Optional. An array of arguments for retrieving taxonomies.
2579       *     @type array  $fields   Optional. The subset of taxonomy fields to return.
2580       * }
2581       * @return array|IXR_Error An associative array of taxonomy data with returned fields determined
2582       *                         by `$fields`, or an IXR_Error instance on failure.
2583       */
2584  	public function wp_getTaxonomies( $args ) {
2585          if ( ! $this->minimum_args( $args, 3 ) ) {
2586              return $this->error;
2587          }
2588  
2589          $this->escape( $args );
2590  
2591          $username = $args[1];
2592          $password = $args[2];
2593          $filter   = isset( $args[3] ) ? $args[3] : array( 'public' => true );
2594  
2595          if ( isset( $args[4] ) ) {
2596              $fields = $args[4];
2597          } else {
2598              /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2599              $fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomies' );
2600          }
2601  
2602          $user = $this->login( $username, $password );
2603          if ( ! $user ) {
2604              return $this->error;
2605          }
2606  
2607          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2608          do_action( 'xmlrpc_call', 'wp.getTaxonomies', $args, $this );
2609  
2610          $taxonomies = get_taxonomies( $filter, 'objects' );
2611  
2612          // Holds all the taxonomy data.
2613          $struct = array();
2614  
2615          foreach ( $taxonomies as $taxonomy ) {
2616              // Capability check for post types.
2617              if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) {
2618                  continue;
2619              }
2620  
2621              $struct[] = $this->_prepare_taxonomy( $taxonomy, $fields );
2622          }
2623  
2624          return $struct;
2625      }
2626  
2627      /**
2628       * Retrieve a user.
2629       *
2630       * The optional $fields parameter specifies what fields will be included
2631       * in the response array. This should be a list of field names. 'user_id' will
2632       * always be included in the response regardless of the value of $fields.
2633       *
2634       * Instead of, or in addition to, individual field names, conceptual group
2635       * names can be used to specify multiple fields. The available conceptual
2636       * groups are 'basic' and 'all'.
2637       *
2638       * @uses get_userdata()
2639       *
2640       * @param array $args {
2641       *     Method arguments. Note: arguments must be ordered as documented.
2642       *
2643       *     @type int    $blog_id (unused)
2644       *     @type string $username
2645       *     @type string $password
2646       *     @type int    $user_id
2647       *     @type array  $fields (optional)
2648       * }
2649       * @return array|IXR_Error Array contains (based on $fields parameter):
2650       *  - 'user_id'
2651       *  - 'username'
2652       *  - 'first_name'
2653       *  - 'last_name'
2654       *  - 'registered'
2655       *  - 'bio'
2656       *  - 'email'
2657       *  - 'nickname'
2658       *  - 'nicename'
2659       *  - 'url'
2660       *  - 'display_name'
2661       *  - 'roles'
2662       */
2663  	public function wp_getUser( $args ) {
2664          if ( ! $this->minimum_args( $args, 4 ) ) {
2665              return $this->error;
2666          }
2667  
2668          $this->escape( $args );
2669  
2670          $username = $args[1];
2671          $password = $args[2];
2672          $user_id  = (int) $args[3];
2673  
2674          if ( isset( $args[4] ) ) {
2675              $fields = $args[4];
2676          } else {
2677              /**
2678               * Filters the default user query fields used by the given XML-RPC method.
2679               *
2680               * @since 3.5.0
2681               *
2682               * @param array  $fields User query fields for given method. Default 'all'.
2683               * @param string $method The method name.
2684               */
2685              $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUser' );
2686          }
2687  
2688          $user = $this->login( $username, $password );
2689          if ( ! $user ) {
2690              return $this->error;
2691          }
2692  
2693          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2694          do_action( 'xmlrpc_call', 'wp.getUser', $args, $this );
2695  
2696          if ( ! current_user_can( 'edit_user', $user_id ) ) {
2697              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this user.' ) );
2698          }
2699  
2700          $user_data = get_userdata( $user_id );
2701  
2702          if ( ! $user_data ) {
2703              return new IXR_Error( 404, __( 'Invalid user ID.' ) );
2704          }
2705  
2706          return $this->_prepare_user( $user_data, $fields );
2707      }
2708  
2709      /**
2710       * Retrieve users.
2711       *
2712       * The optional $filter parameter modifies the query used to retrieve users.
2713       * Accepted keys are 'number' (default: 50), 'offset' (default: 0), 'role',
2714       * 'who', 'orderby', and 'order'.
2715       *
2716       * The optional $fields parameter specifies what fields will be included
2717       * in the response array.
2718       *
2719       * @uses get_users()
2720       * @see wp_getUser() for more on $fields and return values
2721       *
2722       * @param array $args {
2723       *     Method arguments. Note: arguments must be ordered as documented.
2724       *
2725       *     @type int    $blog_id (unused)
2726       *     @type string $username
2727       *     @type string $password
2728       *     @type array  $filter (optional)
2729       *     @type array  $fields (optional)
2730       * }
2731       * @return array|IXR_Error users data
2732       */
2733  	public function wp_getUsers( $args ) {
2734          if ( ! $this->minimum_args( $args, 3 ) ) {
2735              return $this->error;
2736          }
2737  
2738          $this->escape( $args );
2739  
2740          $username = $args[1];
2741          $password = $args[2];
2742          $filter   = isset( $args[3] ) ? $args[3] : array();
2743  
2744          if ( isset( $args[4] ) ) {
2745              $fields = $args[4];
2746          } else {
2747              /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2748              $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUsers' );
2749          }
2750  
2751          $user = $this->login( $username, $password );
2752          if ( ! $user ) {
2753              return $this->error;
2754          }
2755  
2756          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2757          do_action( 'xmlrpc_call', 'wp.getUsers', $args, $this );
2758  
2759          if ( ! current_user_can( 'list_users' ) ) {
2760              return new IXR_Error( 401, __( 'Sorry, you are not allowed to list users.' ) );
2761          }
2762  
2763          $query = array( 'fields' => 'all_with_meta' );
2764  
2765          $query['number'] = ( isset( $filter['number'] ) ) ? absint( $filter['number'] ) : 50;
2766          $query['offset'] = ( isset( $filter['offset'] ) ) ? absint( $filter['offset'] ) : 0;
2767  
2768          if ( isset( $filter['orderby'] ) ) {
2769              $query['orderby'] = $filter['orderby'];
2770  
2771              if ( isset( $filter['order'] ) ) {
2772                  $query['order'] = $filter['order'];
2773              }
2774          }
2775  
2776          if ( isset( $filter['role'] ) ) {
2777              if ( get_role( $filter['role'] ) === null ) {
2778                  return new IXR_Error( 403, __( 'Invalid role.' ) );
2779              }
2780  
2781              $query['role'] = $filter['role'];
2782          }
2783  
2784          if ( isset( $filter['who'] ) ) {
2785              $query['who'] = $filter['who'];
2786          }
2787  
2788          $users = get_users( $query );
2789  
2790          $_users = array();
2791          foreach ( $users as $user_data ) {
2792              if ( current_user_can( 'edit_user', $user_data->ID ) ) {
2793                  $_users[] = $this->_prepare_user( $user_data, $fields );
2794              }
2795          }
2796          return $_users;
2797      }
2798  
2799      /**
2800       * Retrieve information about the requesting user.
2801       *
2802       * @uses get_userdata()
2803       *
2804       * @param array $args {
2805       *     Method arguments. Note: arguments must be ordered as documented.
2806       *
2807       *     @type int    $blog_id (unused)
2808       *     @type string $username
2809       *     @type string $password
2810       *     @type array  $fields (optional)
2811       * }
2812       * @return array|IXR_Error (@see wp_getUser)
2813       */
2814  	public function wp_getProfile( $args ) {
2815          if ( ! $this->minimum_args( $args, 3 ) ) {
2816              return $this->error;
2817          }
2818  
2819          $this->escape( $args );
2820  
2821          $username = $args[1];
2822          $password = $args[2];
2823  
2824          if ( isset( $args[3] ) ) {
2825              $fields = $args[3];
2826          } else {
2827              /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2828              $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getProfile' );
2829          }
2830  
2831          $user = $this->login( $username, $password );
2832          if ( ! $user ) {
2833              return $this->error;
2834          }
2835  
2836          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2837          do_action( 'xmlrpc_call', 'wp.getProfile', $args, $this );
2838  
2839          if ( ! current_user_can( 'edit_user', $user->ID ) ) {
2840              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit your profile.' ) );
2841          }
2842  
2843          $user_data = get_userdata( $user->ID );
2844  
2845          return $this->_prepare_user( $user_data, $fields );
2846      }
2847  
2848      /**
2849       * Edit user's profile.
2850       *
2851       * @uses wp_update_user()
2852       *
2853       * @param array $args {
2854       *     Method arguments. Note: arguments must be ordered as documented.
2855       *
2856       *     @type int    $blog_id (unused)
2857       *     @type string $username
2858       *     @type string $password
2859       *     @type array  $content_struct It can optionally contain:
2860       *      - 'first_name'
2861       *      - 'last_name'
2862       *      - 'website'
2863       *      - 'display_name'
2864       *      - 'nickname'
2865       *      - 'nicename'
2866       *      - 'bio'
2867       * }
2868       * @return true|IXR_Error True, on success.
2869       */
2870  	public function wp_editProfile( $args ) {
2871          if ( ! $this->minimum_args( $args, 4 ) ) {
2872              return $this->error;
2873          }
2874  
2875          $this->escape( $args );
2876  
2877          $username       = $args[1];
2878          $password       = $args[2];
2879          $content_struct = $args[3];
2880  
2881          $user = $this->login( $username, $password );
2882          if ( ! $user ) {
2883              return $this->error;
2884          }
2885  
2886          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2887          do_action( 'xmlrpc_call', 'wp.editProfile', $args, $this );
2888  
2889          if ( ! current_user_can( 'edit_user', $user->ID ) ) {
2890              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit your profile.' ) );
2891          }
2892  
2893          // Holds data of the user.
2894          $user_data       = array();
2895          $user_data['ID'] = $user->ID;
2896  
2897          // Only set the user details if they were given.
2898          if ( isset( $content_struct['first_name'] ) ) {
2899              $user_data['first_name'] = $content_struct['first_name'];
2900          }
2901  
2902          if ( isset( $content_struct['last_name'] ) ) {
2903              $user_data['last_name'] = $content_struct['last_name'];
2904          }
2905  
2906          if ( isset( $content_struct['url'] ) ) {
2907              $user_data['user_url'] = $content_struct['url'];
2908          }
2909  
2910          if ( isset( $content_struct['display_name'] ) ) {
2911              $user_data['display_name'] = $content_struct['display_name'];
2912          }
2913  
2914          if ( isset( $content_struct['nickname'] ) ) {
2915              $user_data['nickname'] = $content_struct['nickname'];
2916          }
2917  
2918          if ( isset( $content_struct['nicename'] ) ) {
2919              $user_data['user_nicename'] = $content_struct['nicename'];
2920          }
2921  
2922          if ( isset( $content_struct['bio'] ) ) {
2923              $user_data['description'] = $content_struct['bio'];
2924          }
2925  
2926          $result = wp_update_user( $user_data );
2927  
2928          if ( is_wp_error( $result ) ) {
2929              return new IXR_Error( 500, $result->get_error_message() );
2930          }
2931  
2932          if ( ! $result ) {
2933              return new IXR_Error( 500, __( 'Sorry, the user could not be updated.' ) );
2934          }
2935  
2936          return true;
2937      }
2938  
2939      /**
2940       * Retrieve page.
2941       *
2942       * @since 2.2.0
2943       *
2944       * @param array $args {
2945       *     Method arguments. Note: arguments must be ordered as documented.
2946       *
2947       *     @type int    $blog_id (unused)
2948       *     @type int    $page_id
2949       *     @type string $username
2950       *     @type string $password
2951       * }
2952       * @return array|IXR_Error
2953       */
2954  	public function wp_getPage( $args ) {
2955          $this->escape( $args );
2956  
2957          $page_id  = (int) $args[1];
2958          $username = $args[2];
2959          $password = $args[3];
2960  
2961          $user = $this->login( $username, $password );
2962          if ( ! $user ) {
2963              return $this->error;
2964          }
2965  
2966          $page = get_post( $page_id );
2967          if ( ! $page ) {
2968              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
2969          }
2970  
2971          if ( ! current_user_can( 'edit_page', $page_id ) ) {
2972              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this page.' ) );
2973          }
2974  
2975          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2976          do_action( 'xmlrpc_call', 'wp.getPage', $args, $this );
2977  
2978          // If we found the page then format the data.
2979          if ( $page->ID && ( 'page' === $page->post_type ) ) {
2980              return $this->_prepare_page( $page );
2981          } else {
2982              // If the page doesn't exist, indicate that.
2983              return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
2984          }
2985      }
2986  
2987      /**
2988       * Retrieve Pages.
2989       *
2990       * @since 2.2.0
2991       *
2992       * @param array $args {
2993       *     Method arguments. Note: arguments must be ordered as documented.
2994       *
2995       *     @type int    $blog_id (unused)
2996       *     @type string $username
2997       *     @type string $password
2998       *     @type int    $num_pages
2999       * }
3000       * @return array|IXR_Error
3001       */
3002  	public function wp_getPages( $args ) {
3003          $this->escape( $args );
3004  
3005          $username  = $args[1];
3006          $password  = $args[2];
3007          $num_pages = isset( $args[3] ) ? (int) $args[3] : 10;
3008  
3009          $user = $this->login( $username, $password );
3010          if ( ! $user ) {
3011              return $this->error;
3012          }
3013  
3014          if ( ! current_user_can( 'edit_pages' ) ) {
3015              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit pages.' ) );
3016          }
3017  
3018          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3019          do_action( 'xmlrpc_call', 'wp.getPages', $args, $this );
3020  
3021          $pages     = get_posts(
3022              array(
3023                  'post_type'   => 'page',
3024                  'post_status' => 'any',
3025                  'numberposts' => $num_pages,
3026              )
3027          );
3028          $num_pages = count( $pages );
3029  
3030          // If we have pages, put together their info.
3031          if ( $num_pages >= 1 ) {
3032              $pages_struct = array();
3033  
3034              foreach ( $pages as $page ) {
3035                  if ( current_user_can( 'edit_page', $page->ID ) ) {
3036                      $pages_struct[] = $this->_prepare_page( $page );
3037                  }
3038              }
3039  
3040              return $pages_struct;
3041          }
3042  
3043          return array();
3044      }
3045  
3046      /**
3047       * Create new page.
3048       *
3049       * @since 2.2.0
3050       *
3051       * @see wp_xmlrpc_server::mw_newPost()
3052       *
3053       * @param array $args {
3054       *     Method arguments. Note: arguments must be ordered as documented.
3055       *
3056       *     @type int    $blog_id (unused)
3057       *     @type string $username
3058       *     @type string $password
3059       *     @type array  $content_struct
3060       * }
3061       * @return int|IXR_Error
3062       */
3063  	public function wp_newPage( $args ) {
3064          // Items not escaped here will be escaped in wp_newPost().
3065          $username = $this->escape( $args[1] );
3066          $password = $this->escape( $args[2] );
3067  
3068          $user = $this->login( $username, $password );
3069          if ( ! $user ) {
3070              return $this->error;
3071          }
3072  
3073          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3074          do_action( 'xmlrpc_call', 'wp.newPage', $args, $this );
3075  
3076          // Mark this as content for a page.
3077          $args[3]['post_type'] = 'page';
3078  
3079          // Let mw_newPost() do all of the heavy lifting.
3080          return $this->mw_newPost( $args );
3081      }
3082  
3083      /**
3084       * Delete page.
3085       *
3086       * @since 2.2.0
3087       *
3088       * @param array $args {
3089       *     Method arguments. Note: arguments must be ordered as documented.
3090       *
3091       *     @type int    $blog_id (unused)
3092       *     @type string $username
3093       *     @type string $password
3094       *     @type int    $page_id
3095       * }
3096       * @return true|IXR_Error True, if success.
3097       */
3098  	public function wp_deletePage( $args ) {
3099          $this->escape( $args );
3100  
3101          $username = $args[1];
3102          $password = $args[2];
3103          $page_id  = (int) $args[3];
3104  
3105          $user = $this->login( $username, $password );
3106          if ( ! $user ) {
3107              return $this->error;
3108          }
3109  
3110          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3111          do_action( 'xmlrpc_call', 'wp.deletePage', $args, $this );
3112  
3113          // Get the current page based on the 'page_id' and
3114          // make sure it is a page and not a post.
3115          $actual_page = get_post( $page_id, ARRAY_A );
3116          if ( ! $actual_page || ( 'page' !== $actual_page['post_type'] ) ) {
3117              return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
3118          }
3119  
3120          // Make sure the user can delete pages.
3121          if ( ! current_user_can( 'delete_page', $page_id ) ) {
3122              return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this page.' ) );
3123          }
3124  
3125          // Attempt to delete the page.
3126          $result = wp_delete_post( $page_id );
3127          if ( ! $result ) {
3128              return new IXR_Error( 500, __( 'Failed to delete the page.' ) );
3129          }
3130  
3131          /**
3132           * Fires after a page has been successfully deleted via XML-RPC.
3133           *
3134           * @since 3.4.0
3135           *
3136           * @param int   $page_id ID of the deleted page.
3137           * @param array $args    An array of arguments to delete the page.
3138           */
3139          do_action( 'xmlrpc_call_success_wp_deletePage', $page_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
3140  
3141          return true;
3142      }
3143  
3144      /**
3145       * Edit page.
3146       *
3147       * @since 2.2.0
3148       *
3149       * @param array $args {
3150       *     Method arguments. Note: arguments must be ordered as documented.
3151       *
3152       *     @type int    $blog_id (unused)
3153       *     @type int    $page_id
3154       *     @type string $username
3155       *     @type string $password
3156       *     @type string $content
3157       *     @type string $publish
3158       * }
3159       * @return array|IXR_Error
3160       */
3161  	public function wp_editPage( $args ) {
3162          // Items will be escaped in mw_editPost().
3163          $page_id  = (int) $args[1];
3164          $username = $args[2];
3165          $password = $args[3];
3166          $content  = $args[4];
3167          $publish  = $args[5];
3168  
3169          $escaped_username = $this->escape( $username );
3170          $escaped_password = $this->escape( $password );
3171  
3172          $user = $this->login( $escaped_username, $escaped_password );
3173          if ( ! $user ) {
3174              return $this->error;
3175          }
3176  
3177          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3178          do_action( 'xmlrpc_call', 'wp.editPage', $args, $this );
3179  
3180          // Get the page data and make sure it is a page.
3181          $actual_page = get_post( $page_id, ARRAY_A );
3182          if ( ! $actual_page || ( 'page' !== $actual_page['post_type'] ) ) {
3183              return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
3184          }
3185  
3186          // Make sure the user is allowed to edit pages.
3187          if ( ! current_user_can( 'edit_page', $page_id ) ) {
3188              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this page.' ) );
3189          }
3190  
3191          // Mark this as content for a page.
3192          $content['post_type'] = 'page';
3193  
3194          // Arrange args in the way mw_editPost() understands.
3195          $args = array(
3196              $page_id,
3197              $username,
3198              $password,
3199              $content,
3200              $publish,
3201          );
3202  
3203          // Let mw_editPost() do all of the heavy lifting.
3204          return $this->mw_editPost( $args );
3205      }
3206  
3207      /**
3208       * Retrieve page list.
3209       *
3210       * @since 2.2.0
3211       *
3212       * @global wpdb $wpdb WordPress database abstraction object.
3213       *
3214       * @param array $args {
3215       *     Method arguments. Note: arguments must be ordered as documented.
3216       *
3217       *     @type int    $blog_id (unused)
3218       *     @type string $username
3219       *     @type string $password
3220       * }
3221       * @return array|IXR_Error
3222       */
3223  	public function wp_getPageList( $args ) {
3224          global $wpdb;
3225  
3226          $this->escape( $args );
3227  
3228          $username = $args[1];
3229          $password = $args[2];
3230  
3231          $user = $this->login( $username, $password );
3232          if ( ! $user ) {
3233              return $this->error;
3234          }
3235  
3236          if ( ! current_user_can( 'edit_pages' ) ) {
3237              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit pages.' ) );
3238          }
3239  
3240          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3241          do_action( 'xmlrpc_call', 'wp.getPageList', $args, $this );
3242  
3243          // Get list of page IDs and titles.
3244          $page_list = $wpdb->get_results(
3245              "
3246              SELECT ID page_id,
3247                  post_title page_title,
3248                  post_parent page_parent_id,
3249                  post_date_gmt,
3250                  post_date,
3251                  post_status
3252              FROM {$wpdb->posts}
3253              WHERE post_type = 'page'
3254              ORDER BY ID
3255          "
3256          );
3257  
3258          // The date needs to be formatted properly.
3259          $num_pages = count( $page_list );
3260          for ( $i = 0; $i < $num_pages; $i++ ) {
3261              $page_list[ $i ]->dateCreated      = $this->_convert_date( $page_list[ $i ]->post_date );
3262              $page_list[ $i ]->date_created_gmt = $this->_convert_date_gmt( $page_list[ $i ]->post_date_gmt, $page_list[ $i ]->post_date );
3263  
3264              unset( $page_list[ $i ]->post_date_gmt );
3265              unset( $page_list[ $i ]->post_date );
3266              unset( $page_list[ $i ]->post_status );
3267          }
3268  
3269          return $page_list;
3270      }
3271  
3272      /**
3273       * Retrieve authors list.
3274       *
3275       * @since 2.2.0
3276       *
3277       * @param array $args {
3278       *     Method arguments. Note: arguments must be ordered as documented.
3279       *
3280       *     @type int    $blog_id (unused)
3281       *     @type string $username
3282       *     @type string $password
3283       * }
3284       * @return array|IXR_Error
3285       */
3286  	public function wp_getAuthors( $args ) {
3287          $this->escape( $args );
3288  
3289          $username = $args[1];
3290          $password = $args[2];
3291  
3292          $user = $this->login( $username, $password );
3293          if ( ! $user ) {
3294              return $this->error;
3295          }
3296  
3297          if ( ! current_user_can( 'edit_posts' ) ) {
3298              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
3299          }
3300  
3301          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3302          do_action( 'xmlrpc_call', 'wp.getAuthors', $args, $this );
3303  
3304          $authors = array();
3305          foreach ( get_users( array( 'fields' => array( 'ID', 'user_login', 'display_name' ) ) ) as $user ) {
3306              $authors[] = array(
3307                  'user_id'      => $user->ID,
3308                  'user_login'   => $user->user_login,
3309                  'display_name' => $user->display_name,
3310              );
3311          }
3312  
3313          return $authors;
3314      }
3315  
3316      /**
3317       * Get list of all tags
3318       *
3319       * @since 2.7.0
3320       *
3321       * @param array $args {
3322       *     Method arguments. Note: arguments must be ordered as documented.
3323       *
3324       *     @type int    $blog_id (unused)
3325       *     @type string $username
3326       *     @type string $password
3327       * }
3328       * @return array|IXR_Error
3329       */
3330  	public function wp_getTags( $args ) {
3331          $this->escape( $args );
3332  
3333          $username = $args[1];
3334          $password = $args[2];
3335  
3336          $user = $this->login( $username, $password );
3337          if ( ! $user ) {
3338              return $this->error;
3339          }
3340  
3341          if ( ! current_user_can( 'edit_posts' ) ) {
3342              return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view tags.' ) );
3343          }
3344  
3345          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3346          do_action( 'xmlrpc_call', 'wp.getKeywords', $args, $this );
3347  
3348          $tags = array();
3349  
3350          $all_tags = get_tags();
3351          if ( $all_tags ) {
3352              foreach ( (array) $all_tags as $tag ) {
3353                  $struct             = array();
3354                  $struct['tag_id']   = $tag->term_id;
3355                  $struct['name']     = $tag->name;
3356                  $struct['count']    = $tag->count;
3357                  $struct['slug']     = $tag->slug;
3358                  $struct['html_url'] = esc_html( get_tag_link( $tag->term_id ) );
3359                  $struct['rss_url']  = esc_html( get_tag_feed_link( $tag->term_id ) );
3360  
3361                  $tags[] = $struct;
3362              }
3363          }
3364  
3365          return $tags;
3366      }
3367  
3368      /**
3369       * Create new category.
3370       *
3371       * @since 2.2.0
3372       *
3373       * @param array $args {
3374       *     Method arguments. Note: arguments must be ordered as documented.
3375       *
3376       *     @type int    $blog_id (unused)
3377       *     @type string $username
3378       *     @type string $password
3379       *     @type array  $category
3380       * }
3381       * @return int|IXR_Error Category ID.
3382       */
3383  	public function wp_newCategory( $args ) {
3384          $this->escape( $args );
3385  
3386          $username = $args[1];
3387          $password = $args[2];
3388          $category = $args[3];
3389  
3390          $user = $this->login( $username, $password );
3391          if ( ! $user ) {
3392              return $this->error;
3393          }
3394  
3395          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3396          do_action( 'xmlrpc_call', 'wp.newCategory', $args, $this );
3397  
3398          // Make sure the user is allowed to add a category.
3399          if ( ! current_user_can( 'manage_categories' ) ) {
3400              return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a category.' ) );
3401          }
3402  
3403          // If no slug was provided, make it empty
3404          // so that WordPress will generate one.
3405          if ( empty( $category['slug'] ) ) {
3406              $category['slug'] = '';
3407          }
3408  
3409          // If no parent_id was provided, make it empty
3410          // so that it will be a top-level page (no parent).
3411          if ( ! isset( $category['parent_id'] ) ) {
3412              $category['parent_id'] = '';
3413          }
3414  
3415          // If no description was provided, make it empty.
3416          if ( empty( $category['description'] ) ) {
3417              $category['description'] = '';
3418          }
3419  
3420          $new_category = array(
3421              'cat_name'             => $category['name'],
3422              'category_nicename'    => $category['slug'],
3423              'category_parent'      => $category['parent_id'],
3424              'category_description' => $category['description'],
3425          );
3426  
3427          $cat_id = wp_insert_category( $new_category, true );
3428          if ( is_wp_error( $cat_id ) ) {
3429              if ( 'term_exists' === $cat_id->get_error_code() ) {
3430                  return (int) $cat_id->get_error_data();
3431              } else {
3432                  return new IXR_Error( 500, __( 'Sorry, the category could not be created.' ) );
3433              }
3434          } elseif ( ! $cat_id ) {
3435              return new IXR_Error( 500, __( 'Sorry, the category could not be created.' ) );
3436          }
3437  
3438          /**
3439           * Fires after a new category has been successfully created via XML-RPC.
3440           *
3441           * @since 3.4.0
3442           *
3443           * @param int   $cat_id ID of the new category.
3444           * @param array $args   An array of new category arguments.
3445           */
3446          do_action( 'xmlrpc_call_success_wp_newCategory', $cat_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
3447  
3448          return $cat_id;
3449      }
3450  
3451      /**
3452       * Remove category.
3453       *
3454       * @since 2.5.0
3455       *
3456       * @param array $args {
3457       *     Method arguments. Note: arguments must be ordered as documented.
3458       *
3459       *     @type int    $blog_id (unused)
3460       *     @type string $username
3461       *     @type string $password
3462       *     @type int    $category_id
3463       * }
3464       * @return bool|IXR_Error See wp_delete_term() for return info.
3465       */
3466  	public function wp_deleteCategory( $args ) {
3467          $this->escape( $args );
3468  
3469          $username    = $args[1];
3470          $password    = $args[2];
3471          $category_id = (int) $args[3];
3472  
3473          $user = $this->login( $username, $password );
3474          if ( ! $user ) {
3475              return $this->error;
3476          }
3477  
3478          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3479          do_action( 'xmlrpc_call', 'wp.deleteCategory', $args, $this );
3480  
3481          if ( ! current_user_can( 'delete_term', $category_id ) ) {
3482              return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this category.' ) );
3483          }
3484  
3485          $status = wp_delete_term( $category_id, 'category' );
3486  
3487          if ( true == $status ) {
3488              /**
3489               * Fires after a category has been successfully deleted via XML-RPC.
3490               *
3491               * @since 3.4.0
3492               *
3493               * @param int   $category_id ID of the deleted category.
3494               * @param array $args        An array of arguments to delete the category.
3495               */
3496              do_action( 'xmlrpc_call_success_wp_deleteCategory', $category_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
3497          }
3498  
3499          return $status;
3500      }
3501  
3502      /**
3503       * Retrieve category list.
3504       *
3505       * @since 2.2.0
3506       *
3507       * @param array $args {
3508       *     Method arguments. Note: arguments must be ordered as documented.
3509       *
3510       *     @type int    $blog_id (unused)
3511       *     @type string $username
3512       *     @type string $password
3513       *     @type array  $category
3514       *     @type int    $max_results
3515       * }
3516       * @return array|IXR_Error
3517       */
3518  	public function wp_suggestCategories( $args ) {
3519          $this->escape( $args );
3520  
3521          $username    = $args[1];
3522          $password    = $args[2];
3523          $category    = $args[3];
3524          $max_results = (int) $args[4];
3525  
3526          $user = $this->login( $username, $password );
3527          if ( ! $user ) {
3528              return $this->error;
3529          }
3530  
3531          if ( ! current_user_can( 'edit_posts' ) ) {
3532              return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
3533          }
3534  
3535          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3536          do_action( 'xmlrpc_call', 'wp.suggestCategories', $args, $this );
3537  
3538          $category_suggestions = array();
3539          $args                 = array(
3540              'get'        => 'all',
3541              'number'     => $max_results,
3542              'name__like' => $category,
3543          );
3544          foreach ( (array) get_categories( $args ) as $cat ) {
3545              $category_suggestions[] = array(
3546                  'category_id'   => $cat->term_id,
3547                  'category_name' => $cat->name,
3548              );
3549          }
3550  
3551          return $category_suggestions;
3552      }
3553  
3554      /**
3555       * Retrieve comment.
3556       *
3557       * @since 2.7.0
3558       *
3559       * @param array $args {
3560       *     Method arguments. Note: arguments must be ordered as documented.
3561       *
3562       *     @type int    $blog_id (unused)
3563       *     @type string $username
3564       *     @type string $password
3565       *     @type int    $comment_id
3566       * }
3567       * @return array|IXR_Error
3568       */
3569  	public function wp_getComment( $args ) {
3570          $this->escape( $args );
3571  
3572          $username   = $args[1];
3573          $password   = $args[2];
3574          $comment_id = (int) $args[3];
3575  
3576          $user = $this->login( $username, $password );
3577          if ( ! $user ) {
3578              return $this->error;
3579          }
3580  
3581          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3582          do_action( 'xmlrpc_call', 'wp.getComment', $args, $this );
3583  
3584          $comment = get_comment( $comment_id );
3585          if ( ! $comment ) {
3586              return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
3587          }
3588  
3589          if ( ! current_user_can( 'edit_comment', $comment_id ) ) {
3590              return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) );
3591          }
3592  
3593          return $this->_prepare_comment( $comment );
3594      }
3595  
3596      /**
3597       * Retrieve comments.
3598       *
3599       * Besides the common blog_id (unused), username, and password arguments, it takes a filter
3600       * array as last argument.
3601       *
3602       * Accepted 'filter' keys are 'status', 'post_id', 'offset', and 'number'.
3603       *
3604       * The defaults are as follows:
3605       * - 'status' - Default is ''. Filter by status (e.g., 'approve', 'hold')
3606       * - 'post_id' - Default is ''. The post where the comment is posted. Empty string shows all comments.
3607       * - 'number' - Default is 10. Total number of media items to retrieve.
3608       * - 'offset' - Default is 0. See WP_Query::query() for more.
3609       *
3610       * @since 2.7.0
3611       *
3612       * @param array $args {
3613       *     Method arguments. Note: arguments must be ordered as documented.
3614       *
3615       *     @type int    $blog_id (unused)
3616       *     @type string $username
3617       *     @type string $password
3618       *     @type array  $struct
3619       * }
3620       * @return array|IXR_Error Contains a collection of comments. See wp_xmlrpc_server::wp_getComment() for a description of each item contents
3621       */
3622  	public function wp_getComments( $args ) {
3623          $this->escape( $args );
3624  
3625          $username = $args[1];
3626          $password = $args[2];
3627          $struct   = isset( $args[3] ) ? $args[3] : array();
3628  
3629          $user = $this->login( $username, $password );
3630          if ( ! $user ) {
3631              return $this->error;
3632          }
3633  
3634          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3635          do_action( 'xmlrpc_call', 'wp.getComments', $args, $this );
3636  
3637          if ( isset( $struct['status'] ) ) {
3638              $status = $struct['status'];
3639          } else {
3640              $status = '';
3641          }
3642  
3643          if ( ! current_user_can( 'moderate_comments' ) && 'approve' !== $status ) {
3644              return new IXR_Error( 401, __( 'Invalid comment status.' ) );
3645          }
3646  
3647          $post_id = '';
3648          if ( isset( $struct['post_id'] ) ) {
3649              $post_id = absint( $struct['post_id'] );
3650          }
3651  
3652          $post_type = '';
3653          if ( isset( $struct['post_type'] ) ) {
3654              $post_type_object = get_post_type_object( $struct['post_type'] );
3655              if ( ! $post_type_object || ! post_type_supports( $post_type_object->name, 'comments' ) ) {
3656                  return new IXR_Error( 404, __( 'Invalid post type.' ) );
3657              }
3658              $post_type = $struct['post_type'];
3659          }
3660  
3661          $offset = 0;
3662          if ( isset( $struct['offset'] ) ) {
3663              $offset = absint( $struct['offset'] );
3664          }
3665  
3666          $number = 10;
3667          if ( isset( $struct['number'] ) ) {
3668              $number = absint( $struct['number'] );
3669          }
3670  
3671          $comments = get_comments(
3672              array(
3673                  'status'    => $status,
3674                  'post_id'   => $post_id,
3675                  'offset'    => $offset,
3676                  'number'    => $number,
3677                  'post_type' => $post_type,
3678              )
3679          );
3680  
3681          $comments_struct = array();
3682          if ( is_array( $comments ) ) {
3683              foreach ( $comments as $comment ) {
3684                  $comments_struct[] = $this->_prepare_comment( $comment );
3685              }
3686          }
3687  
3688          return $comments_struct;
3689      }
3690  
3691      /**
3692       * Delete a comment.
3693       *
3694       * By default, the comment will be moved to the Trash instead of deleted.
3695       * See wp_delete_comment() for more information on this behavior.
3696       *
3697       * @since 2.7.0
3698       *
3699       * @param array $args {
3700       *     Method arguments. Note: arguments must be ordered as documented.
3701       *
3702       *     @type int    $blog_id (unused)
3703       *     @type string $username
3704       *     @type string $password
3705       *     @type int    $comment_ID
3706       * }
3707       * @return bool|IXR_Error See wp_delete_comment().
3708       */
3709  	public function wp_deleteComment( $args ) {
3710          $this->escape( $args );
3711  
3712          $username   = $args[1];
3713          $password   = $args[2];
3714          $comment_ID = (int) $args[3];
3715  
3716          $user = $this->login( $username, $password );
3717          if ( ! $user ) {
3718              return $this->error;
3719          }
3720  
3721          if ( ! get_comment( $comment_ID ) ) {
3722              return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
3723          }
3724  
3725          if ( ! current_user_can( 'edit_comment', $comment_ID ) ) {
3726              return new IXR_Error( 403, __( 'Sorry, you are not allowed to delete this comment.' ) );
3727          }
3728  
3729          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3730          do_action( 'xmlrpc_call', 'wp.deleteComment', $args, $this );
3731  
3732          $status = wp_delete_comment( $comment_ID );
3733  
3734          if ( $status ) {
3735              /**
3736               * Fires after a comment has been successfully deleted via XML-RPC.
3737               *
3738               * @since 3.4.0
3739               *
3740               * @param int   $comment_ID ID of the deleted comment.
3741               * @param array $args       An array of arguments to delete the comment.
3742               */
3743              do_action( 'xmlrpc_call_success_wp_deleteComment', $comment_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
3744          }
3745  
3746          return $status;
3747      }
3748  
3749      /**
3750       * Edit comment.
3751       *
3752       * Besides the common blog_id (unused), username, and password arguments, it takes a
3753       * comment_id integer and a content_struct array as last argument.
3754       *
3755       * The allowed keys in the content_struct array are:
3756       *  - 'author'
3757       *  - 'author_url'
3758       *  - 'author_email'
3759       *  - 'content'
3760       *  - 'date_created_gmt'
3761       *  - 'status'. Common statuses are 'approve', 'hold', 'spam'. See get_comment_statuses() for more details
3762       *
3763       * @since 2.7.0
3764       *
3765       * @param array $args {
3766       *     Method arguments. Note: arguments must be ordered as documented.
3767       *
3768       *     @type int    $blog_id (unused)
3769       *     @type string $username
3770       *     @type string $password
3771       *     @type int    $comment_ID
3772       *     @type array  $content_struct
3773       * }
3774       * @return true|IXR_Error True, on success.
3775       */
3776  	public function wp_editComment( $args ) {
3777          $this->escape( $args );
3778  
3779          $username       = $args[1];
3780          $password       = $args[2];
3781          $comment_ID     = (int) $args[3];
3782          $content_struct = $args[4];
3783  
3784          $user = $this->login( $username, $password );
3785          if ( ! $user ) {
3786              return $this->error;
3787          }
3788  
3789          if ( ! get_comment( $comment_ID ) ) {
3790              return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
3791          }
3792  
3793          if ( ! current_user_can( 'edit_comment', $comment_ID ) ) {
3794              return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) );
3795          }
3796  
3797          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3798          do_action( 'xmlrpc_call', 'wp.editComment', $args, $this );
3799          $comment = array(
3800              'comment_ID' => $comment_ID,
3801          );
3802  
3803          if ( isset( $content_struct['status'] ) ) {
3804              $statuses = get_comment_statuses();
3805              $statuses = array_keys( $statuses );
3806  
3807              if ( ! in_array( $content_struct['status'], $statuses, true ) ) {
3808                  return new IXR_Error( 401, __( 'Invalid comment status.' ) );
3809              }
3810  
3811              $comment['comment_approved'] = $content_struct['status'];
3812          }
3813  
3814          // Do some timestamp voodoo.
3815          if ( ! empty( $content_struct['date_created_gmt'] ) ) {
3816              // We know this is supposed to be GMT, so we're going to slap that Z on there by force.
3817              $dateCreated                 = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
3818              $comment['comment_date']     = get_date_from_gmt( $dateCreated );
3819              $comment['comment_date_gmt'] = iso8601_to_datetime( $dateCreated, 'gmt' );
3820          }
3821  
3822          if ( isset( $content_struct['content'] ) ) {
3823              $comment['comment_content'] = $content_struct['content'];
3824          }
3825  
3826          if ( isset( $content_struct['author'] ) ) {
3827              $comment['comment_author'] = $content_struct['author'];
3828          }
3829  
3830          if ( isset( $content_struct['author_url'] ) ) {
3831              $comment['comment_author_url'] = $content_struct['author_url'];
3832          }
3833  
3834          if ( isset( $content_struct['author_email'] ) ) {
3835              $comment['comment_author_email'] = $content_struct['author_email'];
3836          }
3837  
3838          $result = wp_update_comment( $comment, true );
3839          if ( is_wp_error( $result ) ) {
3840              return new IXR_Error( 500, $result->get_error_message() );
3841          }
3842  
3843          if ( ! $result ) {
3844              return new IXR_Error( 500, __( 'Sorry, the comment could not be updated.' ) );
3845          }
3846  
3847          /**
3848           * Fires after a comment has been successfully updated via XML-RPC.
3849           *
3850           * @since 3.4.0
3851           *
3852           * @param int   $comment_ID ID of the updated comment.
3853           * @param array $args       An array of arguments to update the comment.
3854           */
3855          do_action( 'xmlrpc_call_success_wp_editComment', $comment_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
3856  
3857          return true;
3858      }
3859  
3860      /**
3861       * Create new comment.
3862       *
3863       * @since 2.7.0
3864       *
3865       * @param array $args {
3866       *     Method arguments. Note: arguments must be ordered as documented.
3867       *
3868       *     @type int        $blog_id (unused)
3869       *     @type string     $username
3870       *     @type string     $password
3871       *     @type string|int $post
3872       *     @type array      $content_struct
3873       * }
3874       * @return int|IXR_Error See wp_new_comment().
3875       */
3876  	public function wp_newComment( $args ) {
3877          $this->escape( $args );
3878  
3879          $username       = $args[1];
3880          $password       = $args[2];
3881          $post           = $args[3];
3882          $content_struct = $args[4];
3883  
3884          /**
3885           * Filters whether to allow anonymous comments over XML-RPC.
3886           *
3887           * @since 2.7.0
3888           *
3889           * @param bool $allow Whether to allow anonymous commenting via XML-RPC.
3890           *                    Default false.
3891           */
3892          $allow_anon = apply_filters( 'xmlrpc_allow_anonymous_comments', false );
3893  
3894          $user = $this->login( $username, $password );
3895  
3896          if ( ! $user ) {
3897              $logged_in = false;
3898              if ( $allow_anon && get_option( 'comment_registration' ) ) {
3899                  return new IXR_Error( 403, __( 'Sorry, you must be logged in to comment.' ) );
3900              } elseif ( ! $allow_anon ) {
3901                  return $this->error;
3902              }
3903          } else {
3904              $logged_in = true;
3905          }
3906  
3907          if ( is_numeric( $post ) ) {
3908              $post_id = absint( $post );
3909          } else {
3910              $post_id = url_to_postid( $post );
3911          }
3912  
3913          if ( ! $post_id ) {
3914              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
3915          }
3916  
3917          if ( ! get_post( $post_id ) ) {
3918              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
3919          }
3920  
3921          if ( ! comments_open( $post_id ) ) {
3922              return new IXR_Error( 403, __( 'Sorry, comments are closed for this item.' ) );
3923          }
3924  
3925          if (
3926              'publish' === get_post_status( $post_id ) &&
3927              ! current_user_can( 'edit_post', $post_id ) &&
3928              post_password_required( $post_id )
3929          ) {
3930              return new IXR_Error( 403, __( 'Sorry, you are not allowed to comment on this post.' ) );
3931          }
3932  
3933          if (
3934              'private' === get_post_status( $post_id ) &&
3935              ! current_user_can( 'read_post', $post_id )
3936          ) {
3937              return new IXR_Error( 403, __( 'Sorry, you are not allowed to comment on this post.' ) );
3938          }
3939  
3940          $comment = array(
3941              'comment_post_ID' => $post_id,
3942              'comment_content' => trim( $content_struct['content'] ),
3943          );
3944  
3945          if ( $logged_in ) {
3946              $display_name = $user->display_name;
3947              $user_email   = $user->user_email;
3948              $user_url     = $user->user_url;
3949  
3950              $comment['comment_author']       = $this->escape( $display_name );
3951              $comment['comment_author_email'] = $this->escape( $user_email );
3952              $comment['comment_author_url']   = $this->escape( $user_url );
3953              $comment['user_ID']              = $user->ID;
3954          } else {
3955              $comment['comment_author'] = '';
3956              if ( isset( $content_struct['author'] ) ) {
3957                  $comment['comment_author'] = $content_struct['author'];
3958              }
3959  
3960              $comment['comment_author_email'] = '';
3961              if ( isset( $content_struct['author_email'] ) ) {
3962                  $comment['comment_author_email'] = $content_struct['author_email'];
3963              }
3964  
3965              $comment['comment_author_url'] = '';
3966              if ( isset( $content_struct['author_url'] ) ) {
3967                  $comment['comment_author_url'] = $content_struct['author_url'];
3968              }
3969  
3970              $comment['user_ID'] = 0;
3971  
3972              if ( get_option( 'require_name_email' ) ) {
3973                  if ( strlen( $comment['comment_author_email'] ) < 6 || '' === $comment['comment_author'] ) {
3974                      return new IXR_Error( 403, __( 'Comment author name and email are required.' ) );
3975                  } elseif ( ! is_email( $comment['comment_author_email'] ) ) {
3976                      return new IXR_Error( 403, __( 'A valid email address is required.' ) );
3977                  }
3978              }
3979          }
3980  
3981          $comment['comment_parent'] = isset( $content_struct['comment_parent'] ) ? absint( $content_struct['comment_parent'] ) : 0;
3982  
3983          /** This filter is documented in wp-includes/comment.php */
3984          $allow_empty = apply_filters( 'allow_empty_comment', false, $comment );
3985  
3986          if ( ! $allow_empty && '' === $comment['comment_content'] ) {
3987              return new IXR_Error( 403, __( 'Comment is required.' ) );
3988          }
3989  
3990          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3991          do_action( 'xmlrpc_call', 'wp.newComment', $args, $this );
3992  
3993          $comment_ID = wp_new_comment( $comment, true );
3994          if ( is_wp_error( $comment_ID ) ) {
3995              return new IXR_Error( 403, $comment_ID->get_error_message() );
3996          }
3997  
3998          if ( ! $comment_ID ) {
3999              return new IXR_Error( 403, __( 'Something went wrong.' ) );
4000          }
4001  
4002          /**
4003           * Fires after a new comment has been successfully created via XML-RPC.
4004           *
4005           * @since 3.4.0
4006           *
4007           * @param int   $comment_ID ID of the new comment.
4008           * @param array $args       An array of new comment arguments.
4009           */
4010          do_action( 'xmlrpc_call_success_wp_newComment', $comment_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
4011  
4012          return $comment_ID;
4013      }
4014  
4015      /**
4016       * Retrieve all of the comment status.
4017       *
4018       * @since 2.7.0
4019       *
4020       * @param array $args {
4021       *     Method arguments. Note: arguments must be ordered as documented.
4022       *
4023       *     @type int    $blog_id (unused)
4024       *     @type string $username
4025       *     @type string $password
4026       * }
4027       * @return array|IXR_Error
4028       */
4029  	public function wp_getCommentStatusList( $args ) {
4030          $this->escape( $args );
4031  
4032          $username = $args[1];
4033          $password = $args[2];
4034  
4035          $user = $this->login( $username, $password );
4036          if ( ! $user ) {
4037              return $this->error;
4038          }
4039  
4040          if ( ! current_user_can( 'publish_posts' ) ) {
4041              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4042          }
4043  
4044          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4045          do_action( 'xmlrpc_call', 'wp.getCommentStatusList', $args, $this );
4046  
4047          return get_comment_statuses();
4048      }
4049  
4050      /**
4051       * Retrieve comment count.
4052       *
4053       * @since 2.5.0
4054       *
4055       * @param array $args {
4056       *     Method arguments. Note: arguments must be ordered as documented.
4057       *
4058       *     @type int    $blog_id (unused)
4059       *     @type string $username
4060       *     @type string $password
4061       *     @type int    $post_id
4062       * }
4063       * @return array|IXR_Error
4064       */
4065  	public function wp_getCommentCount( $args ) {
4066          $this->escape( $args );
4067  
4068          $username = $args[1];
4069          $password = $args[2];
4070          $post_id  = (int) $args[3];
4071  
4072          $user = $this->login( $username, $password );
4073          if ( ! $user ) {
4074              return $this->error;
4075          }
4076  
4077          $post = get_post( $post_id, ARRAY_A );
4078          if ( empty( $post['ID'] ) ) {
4079              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4080          }
4081  
4082          if ( ! current_user_can( 'edit_post', $post_id ) ) {
4083              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details of this post.' ) );
4084          }
4085  
4086          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4087          do_action( 'xmlrpc_call', 'wp.getCommentCount', $args, $this );
4088  
4089          $count = wp_count_comments( $post_id );
4090  
4091          return array(
4092              'approved'            => $count->approved,
4093              'awaiting_moderation' => $count->moderated,
4094              'spam'                => $count->spam,
4095              'total_comments'      => $count->total_comments,
4096          );
4097      }
4098  
4099      /**
4100       * Retrieve post statuses.
4101       *
4102       * @since 2.5.0
4103       *
4104       * @param array $args {
4105       *     Method arguments. Note: arguments must be ordered as documented.
4106       *
4107       *     @type int    $blog_id (unused)
4108       *     @type string $username
4109       *     @type string $password
4110       * }
4111       * @return array|IXR_Error
4112       */
4113  	public function wp_getPostStatusList( $args ) {
4114          $this->escape( $args );
4115  
4116          $username = $args[1];
4117          $password = $args[2];
4118  
4119          $user = $this->login( $username, $password );
4120          if ( ! $user ) {
4121              return $this->error;
4122          }
4123  
4124          if ( ! current_user_can( 'edit_posts' ) ) {
4125              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4126          }
4127  
4128          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4129          do_action( 'xmlrpc_call', 'wp.getPostStatusList', $args, $this );
4130  
4131          return get_post_statuses();
4132      }
4133  
4134      /**
4135       * Retrieve page statuses.
4136       *
4137       * @since 2.5.0
4138       *
4139       * @param array $args {
4140       *     Method arguments. Note: arguments must be ordered as documented.
4141       *
4142       *     @type int    $blog_id (unused)
4143       *     @type string $username
4144       *     @type string $password
4145       * }
4146       * @return array|IXR_Error
4147       */
4148  	public function wp_getPageStatusList( $args ) {
4149          $this->escape( $args );
4150  
4151          $username = $args[1];
4152          $password = $args[2];
4153  
4154          $user = $this->login( $username, $password );
4155          if ( ! $user ) {
4156              return $this->error;
4157          }
4158  
4159          if ( ! current_user_can( 'edit_pages' ) ) {
4160              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4161          }
4162  
4163          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4164          do_action( 'xmlrpc_call', 'wp.getPageStatusList', $args, $this );
4165  
4166          return get_page_statuses();
4167      }
4168  
4169      /**
4170       * Retrieve page templates.
4171       *
4172       * @since 2.6.0
4173       *
4174       * @param array $args {
4175       *     Method arguments. Note: arguments must be ordered as documented.
4176       *
4177       *     @type int    $blog_id (unused)
4178       *     @type string $username
4179       *     @type string $password
4180       * }
4181       * @return array|IXR_Error
4182       */
4183  	public function wp_getPageTemplates( $args ) {
4184          $this->escape( $args );
4185  
4186          $username = $args[1];
4187          $password = $args[2];
4188  
4189          $user = $this->login( $username, $password );
4190          if ( ! $user ) {
4191              return $this->error;
4192          }
4193  
4194          if ( ! current_user_can( 'edit_pages' ) ) {
4195              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4196          }
4197  
4198          $templates            = get_page_templates();
4199          $templates['Default'] = 'default';
4200  
4201          return $templates;
4202      }
4203  
4204      /**
4205       * Retrieve blog options.
4206       *
4207       * @since 2.6.0
4208       *
4209       * @param array $args {
4210       *     Method arguments. Note: arguments must be ordered as documented.
4211       *
4212       *     @type int    $blog_id (unused)
4213       *     @type string $username
4214       *     @type string $password
4215       *     @type array  $options
4216       * }
4217       * @return array|IXR_Error
4218       */
4219  	public function wp_getOptions( $args ) {
4220          $this->escape( $args );
4221  
4222          $username = $args[1];
4223          $password = $args[2];
4224          $options  = isset( $args[3] ) ? (array) $args[3] : array();
4225  
4226          $user = $this->login( $username, $password );
4227          if ( ! $user ) {
4228              return $this->error;
4229          }
4230  
4231          // If no specific options where asked for, return all of them.
4232          if ( count( $options ) == 0 ) {
4233              $options = array_keys( $this->blog_options );
4234          }
4235  
4236          return $this->_getOptions( $options );
4237      }
4238  
4239      /**
4240       * Retrieve blog options value from list.
4241       *
4242       * @since 2.6.0
4243       *
4244       * @param array $options Options to retrieve.
4245       * @return array
4246       */
4247  	public function _getOptions( $options ) {
4248          $data       = array();
4249          $can_manage = current_user_can( 'manage_options' );
4250          foreach ( $options as $option ) {
4251              if ( array_key_exists( $option, $this->blog_options ) ) {
4252                  $data[ $option ] = $this->blog_options[ $option ];
4253                  // Is the value static or dynamic?
4254                  if ( isset( $data[ $option ]['option'] ) ) {
4255                      $data[ $option ]['value'] = get_option( $data[ $option ]['option'] );
4256                      unset( $data[ $option ]['option'] );
4257                  }
4258  
4259                  if ( ! $can_manage ) {
4260                      $data[ $option ]['readonly'] = true;
4261                  }
4262              }
4263          }
4264  
4265          return $data;
4266      }
4267  
4268      /**
4269       * Update blog options.
4270       *
4271       * @since 2.6.0
4272       *
4273       * @param array $args {
4274       *     Method arguments. Note: arguments must be ordered as documented.
4275       *
4276       *     @type int    $blog_id (unused)
4277       *     @type string $username
4278       *     @type string $password
4279       *     @type array  $options
4280       * }
4281       * @return array|IXR_Error
4282       */
4283  	public function wp_setOptions( $args ) {
4284          $this->escape( $args );
4285  
4286          $username = $args[1];
4287          $password = $args[2];
4288          $options  = (array) $args[3];
4289  
4290          $user = $this->login( $username, $password );
4291          if ( ! $user ) {
4292              return $this->error;
4293          }
4294  
4295          if ( ! current_user_can( 'manage_options' ) ) {
4296              return new IXR_Error( 403, __( 'Sorry, you are not allowed to update options.' ) );
4297          }
4298  
4299          $option_names = array();
4300          foreach ( $options as $o_name => $o_value ) {
4301              $option_names[] = $o_name;
4302              if ( ! array_key_exists( $o_name, $this->blog_options ) ) {
4303                  continue;
4304              }
4305  
4306              if ( true == $this->blog_options[ $o_name ]['readonly'] ) {
4307                  continue;
4308              }
4309  
4310              update_option( $this->blog_options[ $o_name ]['option'], wp_unslash( $o_value ) );
4311          }
4312  
4313          // Now return the updated values.
4314          return $this->_getOptions( $option_names );
4315      }
4316  
4317      /**
4318       * Retrieve a media item by ID
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 int    $attachment_id
4329       * }
4330       * @return array|IXR_Error Associative array contains:
4331       *  - 'date_created_gmt'
4332       *  - 'parent'
4333       *  - 'link'
4334       *  - 'thumbnail'
4335       *  - 'title'
4336       *  - 'caption'
4337       *  - 'description'
4338       *  - 'metadata'
4339       */
4340  	public function wp_getMediaItem( $args ) {
4341          $this->escape( $args );
4342  
4343          $username      = $args[1];
4344          $password      = $args[2];
4345          $attachment_id = (int) $args[3];
4346  
4347          $user = $this->login( $username, $password );
4348          if ( ! $user ) {
4349              return $this->error;
4350          }
4351  
4352          if ( ! current_user_can( 'upload_files' ) ) {
4353              return new IXR_Error( 403, __( 'Sorry, you are not allowed to upload files.' ) );
4354          }
4355  
4356          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4357          do_action( 'xmlrpc_call', 'wp.getMediaItem', $args, $this );
4358  
4359          $attachment = get_post( $attachment_id );
4360          if ( ! $attachment || 'attachment' !== $attachment->post_type ) {
4361              return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
4362          }
4363  
4364          return $this->_prepare_media_item( $attachment );
4365      }
4366  
4367      /**
4368       * Retrieves a collection of media library items (or attachments)
4369       *
4370       * Besides the common blog_id (unused), username, and password arguments, it takes a filter
4371       * array as last argument.
4372       *
4373       * Accepted 'filter' keys are 'parent_id', 'mime_type', 'offset', and 'number'.
4374       *
4375       * The defaults are as follows:
4376       * - 'number' - Default is 5. Total number of media items to retrieve.
4377       * - 'offset' - Default is 0. See WP_Query::query() for more.
4378       * - 'parent_id' - Default is ''. The post where the media item is attached. Empty string shows all media items. 0 shows unattached media items.
4379       * - 'mime_type' - Default is ''. Filter by mime type (e.g., 'image/jpeg', 'application/pdf')
4380       *
4381       * @since 3.1.0
4382       *
4383       * @param array $args {
4384       *     Method arguments. Note: arguments must be ordered as documented.
4385       *
4386       *     @type int    $blog_id (unused)
4387       *     @type string $username
4388       *     @type string $password
4389       *     @type array  $struct
4390       * }
4391       * @return array|IXR_Error Contains a collection of media items. See wp_xmlrpc_server::wp_getMediaItem() for a description of each item contents
4392       */
4393  	public function wp_getMediaLibrary( $args ) {
4394          $this->escape( $args );
4395  
4396          $username = $args[1];
4397          $password = $args[2];
4398          $struct   = isset( $args[3] ) ? $args[3] : array();
4399  
4400          $user = $this->login( $username, $password );
4401          if ( ! $user ) {
4402              return $this->error;
4403          }
4404  
4405          if ( ! current_user_can( 'upload_files' ) ) {
4406              return new IXR_Error( 401, __( 'Sorry, you are not allowed to upload files.' ) );
4407          }
4408  
4409          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4410          do_action( 'xmlrpc_call', 'wp.getMediaLibrary', $args, $this );
4411  
4412          $parent_id = ( isset( $struct['parent_id'] ) ) ? absint( $struct['parent_id'] ) : '';
4413          $mime_type = ( isset( $struct['mime_type'] ) ) ? $struct['mime_type'] : '';
4414          $offset    = ( isset( $struct['offset'] ) ) ? absint( $struct['offset'] ) : 0;
4415          $number    = ( isset( $struct['number'] ) ) ? absint( $struct['number'] ) : -1;
4416  
4417          $attachments = get_posts(
4418              array(
4419                  'post_type'      => 'attachment',
4420                  'post_parent'    => $parent_id,
4421                  'offset'         => $offset,
4422                  'numberposts'    => $number,
4423                  'post_mime_type' => $mime_type,
4424              )
4425          );
4426  
4427          $attachments_struct = array();
4428  
4429          foreach ( $attachments as $attachment ) {
4430              $attachments_struct[] = $this->_prepare_media_item( $attachment );
4431          }
4432  
4433          return $attachments_struct;
4434      }
4435  
4436      /**
4437       * Retrieves a list of post formats used by the site.
4438       *
4439       * @since 3.1.0
4440       *
4441       * @param array $args {
4442       *     Method arguments. Note: arguments must be ordered as documented.
4443       *
4444       *     @type int    $blog_id (unused)
4445       *     @type string $username
4446       *     @type string $password
4447       * }
4448       * @return array|IXR_Error List of post formats, otherwise IXR_Error object.
4449       */
4450  	public function wp_getPostFormats( $args ) {
4451          $this->escape( $args );
4452  
4453          $username = $args[1];
4454          $password = $args[2];
4455  
4456          $user = $this->login( $username, $password );
4457          if ( ! $user ) {
4458              return $this->error;
4459          }
4460  
4461          if ( ! current_user_can( 'edit_posts' ) ) {
4462              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4463          }
4464  
4465          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4466          do_action( 'xmlrpc_call', 'wp.getPostFormats', $args, $this );
4467  
4468          $formats = get_post_format_strings();
4469  
4470          // Find out if they want a list of currently supports formats.
4471          if ( isset( $args[3] ) && is_array( $args[3] ) ) {
4472              if ( $args[3]['show-supported'] ) {
4473                  if ( current_theme_supports( 'post-formats' ) ) {
4474                      $supported = get_theme_support( 'post-formats' );
4475  
4476                      $data              = array();
4477                      $data['all']       = $formats;
4478                      $data['supported'] = $supported[0];
4479  
4480                      $formats = $data;
4481                  }
4482              }
4483          }
4484  
4485          return $formats;
4486      }
4487  
4488      /**
4489       * Retrieves a post type
4490       *
4491       * @since 3.4.0
4492       *
4493       * @see get_post_type_object()
4494       *
4495       * @param array $args {
4496       *     Method arguments. Note: arguments must be ordered as documented.
4497       *
4498       *     @type int    $blog_id (unused)
4499       *     @type string $username
4500       *     @type string $password
4501       *     @type string $post_type_name
4502       *     @type array  $fields (optional)
4503       * }
4504       * @return array|IXR_Error Array contains:
4505       *  - 'labels'
4506       *  - 'description'
4507       *  - 'capability_type'
4508       *  - 'cap'
4509       *  - 'map_meta_cap'
4510       *  - 'hierarchical'
4511       *  - 'menu_position'
4512       *  - 'taxonomies'
4513       *  - 'supports'
4514       */
4515  	public function wp_getPostType( $args ) {
4516          if ( ! $this->minimum_args( $args, 4 ) ) {
4517              return $this->error;
4518          }
4519  
4520          $this->escape( $args );
4521  
4522          $username       = $args[1];
4523          $password       = $args[2];
4524          $post_type_name = $args[3];
4525  
4526          if ( isset( $args[4] ) ) {
4527              $fields = $args[4];
4528          } else {
4529              /**
4530               * Filters the default query fields used by the given XML-RPC method.
4531               *
4532               * @since 3.4.0
4533               *
4534               * @param array  $fields An array of post type query fields for the given method.
4535               * @param string $method The method name.
4536               */
4537              $fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostType' );
4538          }
4539  
4540          $user = $this->login( $username, $password );
4541          if ( ! $user ) {
4542              return $this->error;
4543          }
4544  
4545          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4546          do_action( 'xmlrpc_call', 'wp.getPostType', $args, $this );
4547  
4548          if ( ! post_type_exists( $post_type_name ) ) {
4549              return new IXR_Error( 403, __( 'Invalid post type.' ) );
4550          }
4551  
4552          $post_type = get_post_type_object( $post_type_name );
4553  
4554          if ( ! current_user_can( $post_type->cap->edit_posts ) ) {
4555              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type.' ) );
4556          }
4557  
4558          return $this->_prepare_post_type( $post_type, $fields );
4559      }
4560  
4561      /**
4562       * Retrieves a post types
4563       *
4564       * @since 3.4.0
4565       *
4566       * @see get_post_types()
4567       *
4568       * @param array $args {
4569       *     Method arguments. Note: arguments must be ordered as documented.
4570       *
4571       *     @type int    $blog_id (unused)
4572       *     @type string $username
4573       *     @type string $password
4574       *     @type array  $filter (optional)
4575       *     @type array  $fields (optional)
4576       * }
4577       * @return array|IXR_Error
4578       */
4579  	public function wp_getPostTypes( $args ) {
4580          if ( ! $this->minimum_args( $args, 3 ) ) {
4581              return $this->error;
4582          }
4583  
4584          $this->escape( $args );
4585  
4586          $username = $args[1];
4587          $password = $args[2];
4588          $filter   = isset( $args[3] ) ? $args[3] : array( 'public' => true );
4589  
4590          if ( isset( $args[4] ) ) {
4591              $fields = $args[4];
4592          } else {
4593              /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4594              $fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostTypes' );
4595          }
4596  
4597          $user = $this->login( $username, $password );
4598          if ( ! $user ) {
4599              return $this->error;
4600          }
4601  
4602          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4603          do_action( 'xmlrpc_call', 'wp.getPostTypes', $args, $this );
4604  
4605          $post_types = get_post_types( $filter, 'objects' );
4606  
4607          $struct = array();
4608  
4609          foreach ( $post_types as $post_type ) {
4610              if ( ! current_user_can( $post_type->cap->edit_posts ) ) {
4611                  continue;
4612              }
4613  
4614              $struct[ $post_type->name ] = $this->_prepare_post_type( $post_type, $fields );
4615          }
4616  
4617          return $struct;
4618      }
4619  
4620      /**
4621       * Retrieve revisions for a specific post.
4622       *
4623       * @since 3.5.0
4624       *
4625       * The optional $fields parameter specifies what fields will be included
4626       * in the response array.
4627       *
4628       * @uses wp_get_post_revisions()
4629       * @see wp_getPost() for more on $fields
4630       *
4631       * @param array $args {
4632       *     Method arguments. Note: arguments must be ordered as documented.
4633       *
4634       *     @type int    $blog_id (unused)
4635       *     @type string $username
4636       *     @type string $password
4637       *     @type int    $post_id
4638       *     @type array  $fields (optional)
4639       * }
4640       * @return array|IXR_Error contains a collection of posts.
4641       */
4642  	public function wp_getRevisions( $args ) {
4643          if ( ! $this->minimum_args( $args, 4 ) ) {
4644              return $this->error;
4645          }
4646  
4647          $this->escape( $args );
4648  
4649          $username = $args[1];
4650          $password = $args[2];
4651          $post_id  = (int) $args[3];
4652  
4653          if ( isset( $args[4] ) ) {
4654              $fields = $args[4];
4655          } else {
4656              /**
4657               * Filters the default revision query fields used by the given XML-RPC method.
4658               *
4659               * @since 3.5.0
4660               *
4661               * @param array  $field  An array of revision query fields.
4662               * @param string $method The method name.
4663               */
4664              $fields = apply_filters( 'xmlrpc_default_revision_fields', array( 'post_date', 'post_date_gmt' ), 'wp.getRevisions' );
4665          }
4666  
4667          $user = $this->login( $username, $password );
4668          if ( ! $user ) {
4669              return $this->error;
4670          }
4671  
4672          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4673          do_action( 'xmlrpc_call', 'wp.getRevisions', $args, $this );
4674  
4675          $post = get_post( $post_id );
4676          if ( ! $post ) {
4677              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4678          }
4679  
4680          if ( ! current_user_can( 'edit_post', $post_id ) ) {
4681              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
4682          }
4683  
4684          // Check if revisions are enabled.
4685          if ( ! wp_revisions_enabled( $post ) ) {
4686              return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) );
4687          }
4688  
4689          $revisions = wp_get_post_revisions( $post_id );
4690  
4691          if ( ! $revisions ) {
4692              return array();
4693          }
4694  
4695          $struct = array();
4696  
4697          foreach ( $revisions as $revision ) {
4698              if ( ! current_user_can( 'read_post', $revision->ID ) ) {
4699                  continue;
4700              }
4701  
4702              // Skip autosaves.
4703              if ( wp_is_post_autosave( $revision ) ) {
4704                  continue;
4705              }
4706  
4707              $struct[] = $this->_prepare_post( get_object_vars( $revision ), $fields );
4708          }
4709  
4710          return $struct;
4711      }
4712  
4713      /**
4714       * Restore a post revision
4715       *
4716       * @since 3.5.0
4717       *
4718       * @uses wp_restore_post_revision()
4719       *
4720       * @param array $args {
4721       *     Method arguments. Note: arguments must be ordered as documented.
4722       *
4723       *     @type int    $blog_id (unused)
4724       *     @type string $username
4725       *     @type string $password
4726       *     @type int    $revision_id
4727       * }
4728       * @return bool|IXR_Error false if there was an error restoring, true if success.
4729       */
4730  	public function wp_restoreRevision( $args ) {
4731          if ( ! $this->minimum_args( $args, 3 ) ) {
4732              return $this->error;
4733          }
4734  
4735          $this->escape( $args );
4736  
4737          $username    = $args[1];
4738          $password    = $args[2];
4739          $revision_id = (int) $args[3];
4740  
4741          $user = $this->login( $username, $password );
4742          if ( ! $user ) {
4743              return $this->error;
4744          }
4745  
4746          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4747          do_action( 'xmlrpc_call', 'wp.restoreRevision', $args, $this );
4748  
4749          $revision = wp_get_post_revision( $revision_id );
4750          if ( ! $revision ) {
4751              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4752          }
4753  
4754          if ( wp_is_post_autosave( $revision ) ) {
4755              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4756          }
4757  
4758          $post = get_post( $revision->post_parent );
4759          if ( ! $post ) {
4760              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4761          }
4762  
4763          if ( ! current_user_can( 'edit_post', $revision->post_parent ) ) {
4764              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
4765          }
4766  
4767          // Check if revisions are disabled.
4768          if ( ! wp_revisions_enabled( $post ) ) {
4769              return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) );
4770          }
4771  
4772          $post = wp_restore_post_revision( $revision_id );
4773  
4774          return (bool) $post;
4775      }
4776  
4777      /*
4778       * Blogger API functions.
4779       * Specs on http://plant.blogger.com/api and https://groups.yahoo.com/group/bloggerDev/
4780       */
4781  
4782      /**
4783       * Retrieve blogs that user owns.
4784       *
4785       * Will make more sense once we support multiple blogs.
4786       *
4787       * @since 1.5.0
4788       *
4789       * @param array $args {
4790       *     Method arguments. Note: arguments must be ordered as documented.
4791       *
4792       *     @type int    $blog_id (unused)
4793       *     @type string $username
4794       *     @type string $password
4795       * }
4796       * @return array|IXR_Error
4797       */
4798  	public function blogger_getUsersBlogs( $args ) {
4799          if ( ! $this->minimum_args( $args, 3 ) ) {
4800              return $this->error;
4801          }
4802  
4803          if ( is_multisite() ) {
4804              return $this->_multisite_getUsersBlogs( $args );
4805          }
4806  
4807          $this->escape( $args );
4808  
4809          $username = $args[1];
4810          $password = $args[2];
4811  
4812          $user = $this->login( $username, $password );
4813          if ( ! $user ) {
4814              return $this->error;
4815          }
4816  
4817          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4818          do_action( 'xmlrpc_call', 'blogger.getUsersBlogs', $args, $this );
4819  
4820          $is_admin = current_user_can( 'manage_options' );
4821  
4822          $struct = array(
4823              'isAdmin'  => $is_admin,
4824              'url'      => get_option( 'home' ) . '/',
4825              'blogid'   => '1',
4826              'blogName' => get_option( 'blogname' ),
4827              'xmlrpc'   => site_url( 'xmlrpc.php', 'rpc' ),
4828          );
4829  
4830          return array( $struct );
4831      }
4832  
4833      /**
4834       * Private function for retrieving a users blogs for multisite setups
4835       *
4836       * @since 3.0.0
4837       *
4838       * @param array $args {
4839       *     Method arguments. Note: arguments must be ordered as documented.
4840       *
4841       *     @type string $username Username.
4842       *     @type string $password Password.
4843       * }
4844       * @return array|IXR_Error
4845       */
4846  	protected function _multisite_getUsersBlogs( $args ) {
4847          $current_blog = get_site();
4848  
4849          $domain = $current_blog->domain;
4850          $path   = $current_blog->path . 'xmlrpc.php';
4851  
4852          $rpc = new IXR_Client( set_url_scheme( "http://{$domain}{$path}" ) );
4853          $rpc->query( 'wp.getUsersBlogs', $args[1], $args[2] );
4854          $blogs = $rpc->getResponse();
4855  
4856          if ( isset( $blogs['faultCode'] ) ) {
4857              return new IXR_Error( $blogs['faultCode'], $blogs['faultString'] );
4858          }
4859  
4860          if ( $_SERVER['HTTP_HOST'] == $domain && $_SERVER['REQUEST_URI'] == $path ) {
4861              return $blogs;
4862          } else {
4863              foreach ( (array) $blogs as $blog ) {
4864                  if ( strpos( $blog['url'], $_SERVER['HTTP_HOST'] ) ) {
4865                      return array( $blog );
4866                  }
4867              }
4868              return array();
4869          }
4870      }
4871  
4872      /**
4873       * Retrieve user's data.
4874       *
4875       * Gives your client some info about you, so you don't have to.
4876       *
4877       * @since 1.5.0
4878       *
4879       * @param array $args {
4880       *     Method arguments. Note: arguments must be ordered as documented.
4881       *
4882       *     @type int    $blog_id (unused)
4883       *     @type string $username
4884       *     @type string $password
4885       * }
4886       * @return array|IXR_Error
4887       */
4888  	public function blogger_getUserInfo( $args ) {
4889          $this->escape( $args );
4890  
4891          $username = $args[1];
4892          $password = $args[2];
4893  
4894          $user = $this->login( $username, $password );
4895          if ( ! $user ) {
4896              return $this->error;
4897          }
4898  
4899          if ( ! current_user_can( 'edit_posts' ) ) {
4900              return new IXR_Error( 401, __( 'Sorry, you are not allowed to access user data on this site.' ) );
4901          }
4902  
4903          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4904          do_action( 'xmlrpc_call', 'blogger.getUserInfo', $args, $this );
4905  
4906          $struct = array(
4907              'nickname'  => $user->nickname,
4908              'userid'    => $user->ID,
4909              'url'       => $user->user_url,
4910              'lastname'  => $user->last_name,
4911              'firstname' => $user->first_name,
4912          );
4913  
4914          return $struct;
4915      }
4916  
4917      /**
4918       * Retrieve post.
4919       *
4920       * @since 1.5.0
4921       *
4922       * @param array $args {
4923       *     Method arguments. Note: arguments must be ordered as documented.
4924       *
4925       *     @type int    $blog_id (unused)
4926       *     @type int    $post_ID
4927       *     @type string $username
4928       *     @type string $password
4929       * }
4930       * @return array|IXR_Error
4931       */
4932  	public function blogger_getPost( $args ) {
4933          $this->escape( $args );
4934  
4935          $post_ID  = (int) $args[1];
4936          $username = $args[2];
4937          $password = $args[3];
4938  
4939          $user = $this->login( $username, $password );
4940          if ( ! $user ) {
4941              return $this->error;
4942          }
4943  
4944          $post_data = get_post( $post_ID, ARRAY_A );
4945          if ( ! $post_data ) {
4946              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4947          }
4948  
4949          if ( ! current_user_can( 'edit_post', $post_ID ) ) {
4950              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
4951          }
4952  
4953          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4954          do_action( 'xmlrpc_call', 'blogger.getPost', $args, $this );
4955  
4956          $categories = implode( ',', wp_get_post_categories( $post_ID ) );
4957  
4958          $content  = '<title>' . wp_unslash( $post_data['post_title'] ) . '</title>';
4959          $content .= '<category>' . $categories . '</category>';
4960          $content .= wp_unslash( $post_data['post_content'] );
4961  
4962          $struct = array(
4963              'userid'      => $post_data['post_author'],
4964              'dateCreated' => $this->_convert_date( $post_data['post_date'] ),
4965              'content'     => $content,
4966              'postid'      => (string) $post_data['ID'],
4967          );
4968  
4969          return $struct;
4970      }
4971  
4972      /**
4973       * Retrieve list of recent posts.
4974       *
4975       * @since 1.5.0
4976       *
4977       * @param array $args {
4978       *     Method arguments. Note: arguments must be ordered as documented.
4979       *
4980       *     @type string $appkey (unused)
4981       *     @type int    $blog_id (unused)
4982       *     @type string $username
4983       *     @type string $password
4984       *     @type int    $numberposts (optional)
4985       * }
4986       * @return array|IXR_Error
4987       */
4988  	public function blogger_getRecentPosts( $args ) {
4989  
4990          $this->escape( $args );
4991  
4992          // $args[0] = appkey - ignored.
4993          $username = $args[2];
4994          $password = $args[3];
4995          if ( isset( $args[4] ) ) {
4996              $query = array( 'numberposts' => absint( $args[4] ) );
4997          } else {
4998              $query = array();
4999          }
5000  
5001          $user = $this->login( $username, $password );
5002          if ( ! $user ) {
5003              return $this->error;
5004          }
5005  
5006          if ( ! current_user_can( 'edit_posts' ) ) {
5007              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
5008          }
5009  
5010          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5011          do_action( 'xmlrpc_call', 'blogger.getRecentPosts', $args, $this );
5012  
5013          $posts_list = wp_get_recent_posts( $query );
5014  
5015          if ( ! $posts_list ) {
5016              $this->error = new IXR_Error( 500, __( 'Either there are no posts, or something went wrong.' ) );
5017              return $this->error;
5018          }
5019  
5020          $recent_posts = array();
5021          foreach ( $posts_list as $entry ) {
5022              if ( ! current_user_can( 'edit_post', $entry['ID'] ) ) {
5023                  continue;
5024              }
5025  
5026              $post_date  = $this->_convert_date( $entry['post_date'] );
5027              $categories = implode( ',', wp_get_post_categories( $entry['ID'] ) );
5028  
5029              $content  = '<title>' . wp_unslash( $entry['post_title'] ) . '</title>';
5030              $content .= '<category>' . $categories . '</category>';
5031              $content .= wp_unslash( $entry['post_content'] );
5032  
5033              $recent_posts[] = array(
5034                  'userid'      => $entry['post_author'],
5035                  'dateCreated' => $post_date,
5036                  'content'     => $content,
5037                  'postid'      => (string) $entry['ID'],
5038              );
5039          }
5040  
5041          return $recent_posts;
5042      }
5043  
5044      /**
5045       * Deprecated.
5046       *
5047       * @since 1.5.0
5048       * @deprecated 3.5.0
5049       *
5050       * @param array $args Unused.
5051       * @return IXR_Error Error object.
5052       */
5053  	public function blogger_getTemplate( $args ) {
5054          return new IXR_Error( 403, __( 'Sorry, this method is not supported.' ) );
5055      }
5056  
5057      /**
5058       * Deprecated.
5059       *
5060       * @since 1.5.0
5061       * @deprecated 3.5.0
5062       *
5063       * @param array $args Unused.
5064       * @return IXR_Error Error object.
5065       */
5066  	public function blogger_setTemplate( $args ) {
5067          return new IXR_Error( 403, __( 'Sorry, this method is not supported.' ) );
5068      }
5069  
5070      /**
5071       * Creates new post.
5072       *
5073       * @since 1.5.0
5074       *
5075       * @param array $args {
5076       *     Method arguments. Note: arguments must be ordered as documented.
5077       *
5078       *     @type string $appkey (unused)
5079       *     @type int    $blog_id (unused)
5080       *     @type string $username
5081       *     @type string $password
5082       *     @type string $content
5083       *     @type string $publish
5084       * }
5085       * @return int|IXR_Error
5086       */
5087  	public function blogger_newPost( $args ) {
5088          $this->escape( $args );
5089  
5090          $username = $args[2];
5091          $password = $args[3];
5092          $content  = $args[4];
5093          $publish  = $args[5];
5094  
5095          $user = $this->login( $username, $password );
5096          if ( ! $user ) {
5097              return $this->error;
5098          }
5099  
5100          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5101          do_action( 'xmlrpc_call', 'blogger.newPost', $args, $this );
5102  
5103          $cap = ( $publish ) ? 'publish_posts' : 'edit_posts';
5104          if ( ! current_user_can( get_post_type_object( 'post' )->cap->create_posts ) || ! current_user_can( $cap ) ) {
5105              return new IXR_Error( 401, __( 'Sorry, you are not allowed to post on this site.' ) );
5106          }
5107  
5108          $post_status = ( $publish ) ? 'publish' : 'draft';
5109  
5110          $post_author = $user->ID;
5111  
5112          $post_title    = xmlrpc_getposttitle( $content );
5113          $post_category = xmlrpc_getpostcategory( $content );
5114          $post_content  = xmlrpc_removepostdata( $content );
5115  
5116          $post_date     = current_time( 'mysql' );
5117          $post_date_gmt = current_time( 'mysql', 1 );
5118  
5119          $post_data = compact( 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status' );
5120  
5121          $post_ID = wp_insert_post( $post_data );
5122          if ( is_wp_error( $post_ID ) ) {
5123              return new IXR_Error( 500, $post_ID->get_error_message() );
5124          }
5125  
5126          if ( ! $post_ID ) {
5127              return new IXR_Error( 500, __( 'Sorry, the post could not be created.' ) );
5128          }
5129  
5130          $this->attach_uploads( $post_ID, $post_content );
5131  
5132          /**
5133           * Fires after a new post has been successfully created via the XML-RPC Blogger API.
5134           *
5135           * @since 3.4.0
5136           *
5137           * @param int   $post_ID ID of the new post.
5138           * @param array $args    An array of new post arguments.
5139           */
5140          do_action( 'xmlrpc_call_success_blogger_newPost', $post_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
5141  
5142          return $post_ID;
5143      }
5144  
5145      /**
5146       * Edit a post.
5147       *
5148       * @since 1.5.0
5149       *
5150       * @param array $args {
5151       *     Method arguments. Note: arguments must be ordered as documented.
5152       *
5153       *     @type int    $blog_id (unused)
5154       *     @type int    $post_ID
5155       *     @type string $username
5156       *     @type string $password
5157       *     @type string $content
5158       *     @type bool   $publish
5159       * }
5160       * @return true|IXR_Error true when done.
5161       */
5162  	public function blogger_editPost( $args ) {
5163  
5164          $this->escape( $args );
5165  
5166          $post_ID  = (int) $args[1];
5167          $username = $args[2];
5168          $password = $args[3];
5169          $content  = $args[4];
5170          $publish  = $args[5];
5171  
5172          $user = $this->login( $username, $password );
5173          if ( ! $user ) {
5174              return $this->error;
5175          }
5176  
5177          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5178          do_action( 'xmlrpc_call', 'blogger.editPost', $args, $this );
5179  
5180          $actual_post = get_post( $post_ID, ARRAY_A );
5181  
5182          if ( ! $actual_post || 'post' !== $actual_post['post_type'] ) {
5183              return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
5184          }
5185  
5186          $this->escape( $actual_post );
5187  
5188          if ( ! current_user_can( 'edit_post', $post_ID ) ) {
5189              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
5190          }
5191          if ( 'publish' === $actual_post['post_status'] && ! current_user_can( 'publish_posts' ) ) {
5192              return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) );
5193          }
5194  
5195          $postdata                  = array();
5196          $postdata['ID']            = $actual_post['ID'];
5197          $postdata['post_content']  = xmlrpc_removepostdata( $content );
5198          $postdata['post_title']    = xmlrpc_getposttitle( $content );
5199          $postdata['post_category'] = xmlrpc_getpostcategory( $content );
5200          $postdata['post_status']   = $actual_post['post_status'];
5201          $postdata['post_excerpt']  = $actual_post['post_excerpt'];
5202          $postdata['post_status']   = $publish ? 'publish' : 'draft';
5203  
5204          $result = wp_update_post( $postdata );
5205  
5206          if ( ! $result ) {
5207              return new IXR_Error( 500, __( 'Sorry, the post could not be updated.' ) );
5208          }
5209          $this->attach_uploads( $actual_post['ID'], $postdata['post_content'] );
5210  
5211          /**
5212           * Fires after a post has been successfully updated via the XML-RPC Blogger API.
5213           *
5214           * @since 3.4.0
5215           *
5216           * @param int   $post_ID ID of the updated post.
5217           * @param array $args    An array of arguments for the post to edit.
5218           */
5219          do_action( 'xmlrpc_call_success_blogger_editPost', $post_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
5220  
5221          return true;
5222      }
5223  
5224      /**
5225       * Remove a post.
5226       *
5227       * @since 1.5.0
5228       *
5229       * @param array $args {
5230       *     Method arguments. Note: arguments must be ordered as documented.
5231       *
5232       *     @type int    $blog_id (unused)
5233       *     @type int    $post_ID
5234       *     @type string $username
5235       *     @type string $password
5236       * }
5237       * @return true|IXR_Error True when post is deleted.
5238       */
5239  	public function blogger_deletePost( $args ) {
5240          $this->escape( $args );
5241  
5242          $post_ID  = (int) $args[1];
5243          $username = $args[2];
5244          $password = $args[3];
5245  
5246          $user = $this->login( $username, $password );
5247          if ( ! $user ) {
5248              return $this->error;
5249          }
5250  
5251          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5252          do_action( 'xmlrpc_call', 'blogger.deletePost', $args, $this );
5253  
5254          $actual_post = get_post( $post_ID, ARRAY_A );
5255  
5256          if ( ! $actual_post || 'post' !== $actual_post['post_type'] ) {
5257              return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
5258          }
5259  
5260          if ( ! current_user_can( 'delete_post', $post_ID ) ) {
5261              return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this post.' ) );
5262          }
5263  
5264          $result = wp_delete_post( $post_ID );
5265  
5266          if ( ! $result ) {
5267              return new IXR_Error( 500, __( 'Sorry, the post could not be deleted.' ) );
5268          }
5269  
5270          /**
5271           * Fires after a post has been successfully deleted via the XML-RPC Blogger API.
5272           *
5273           * @since 3.4.0
5274           *
5275           * @param int   $post_ID ID of the deleted post.
5276           * @param array $args    An array of arguments to delete the post.
5277           */
5278          do_action( 'xmlrpc_call_success_blogger_deletePost', $post_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
5279  
5280          return true;
5281      }
5282  
5283      /*
5284       * MetaWeblog API functions.
5285       * Specs on wherever Dave Winer wants them to be.
5286       */
5287  
5288      /**
5289       * Create a new post.
5290       *
5291       * The 'content_struct' argument must contain:
5292       *  - title
5293       *  - description
5294       *  - mt_excerpt
5295       *  - mt_text_more
5296       *  - mt_keywords
5297       *  - mt_tb_ping_urls
5298       *  - categories
5299       *
5300       * Also, it can optionally contain:
5301       *  - wp_slug
5302       *  - wp_password
5303       *  - wp_page_parent_id
5304       *  - wp_page_order
5305       *  - wp_author_id
5306       *  - post_status | page_status - can be 'draft', 'private', 'publish', or 'pending'
5307       *  - mt_allow_comments - can be 'open' or 'closed'
5308       *  - mt_allow_pings - can be 'open' or 'closed'
5309       *  - date_created_gmt
5310       *  - dateCreated
5311       *  - wp_post_thumbnail
5312       *
5313       * @since 1.5.0
5314       *
5315       * @param array $args {
5316       *     Method arguments. Note: arguments must be ordered as documented.
5317       *
5318       *     @type int    $blog_id (unused)
5319       *     @type string $username
5320       *     @type string $password
5321       *     @type array  $content_struct
5322       *     @type int    $publish
5323       * }
5324       * @return int|IXR_Error
5325       */
5326  	public function mw_newPost( $args ) {
5327          $this->escape( $args );
5328  
5329          $username       = $args[1];
5330          $password       = $args[2];
5331          $content_struct = $args[3];
5332          $publish        = isset( $args[4] ) ? $args[4] : 0;
5333  
5334          $user = $this->login( $username, $password );
5335          if ( ! $user ) {
5336              return $this->error;
5337          }
5338  
5339          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5340          do_action( 'xmlrpc_call', 'metaWeblog.newPost', $args, $this );
5341  
5342          $page_template = '';
5343          if ( ! empty( $content_struct['post_type'] ) ) {
5344              if ( 'page' === $content_struct['post_type'] ) {
5345                  if ( $publish ) {
5346                      $cap = 'publish_pages';
5347                  } elseif ( isset( $content_struct['page_status'] ) && 'publish' === $content_struct['page_status'] ) {
5348                      $cap = 'publish_pages';
5349                  } else {
5350                      $cap = 'edit_pages';
5351                  }
5352                  $error_message = __( 'Sorry, you are not allowed to publish pages on this site.' );
5353                  $post_type     = 'page';
5354                  if ( ! empty( $content_struct['wp_page_template'] ) ) {
5355                      $page_template = $content_struct['wp_page_template'];
5356                  }
5357              } elseif ( 'post' === $content_struct['post_type'] ) {
5358                  if ( $publish ) {
5359                      $cap = 'publish_posts';
5360                  } elseif ( isset( $content_struct['post_status'] ) && 'publish' === $content_struct['post_status'] ) {
5361                      $cap = 'publish_posts';
5362                  } else {
5363                      $cap = 'edit_posts';
5364                  }
5365                  $error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
5366                  $post_type     = 'post';
5367              } else {
5368                  // No other 'post_type' values are allowed here.
5369                  return new IXR_Error( 401, __( 'Invalid post type.' ) );
5370              }
5371          } else {
5372              if ( $publish ) {
5373                  $cap = 'publish_posts';
5374              } elseif ( isset( $content_struct['post_status'] ) && 'publish' === $content_struct['post_status'] ) {
5375                  $cap = 'publish_posts';
5376              } else {
5377                  $cap = 'edit_posts';
5378              }
5379              $error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
5380              $post_type     = 'post';
5381          }
5382  
5383          if ( ! current_user_can( get_post_type_object( $post_type )->cap->create_posts ) ) {
5384              return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts on this site.' ) );
5385          }
5386          if ( ! current_user_can( $cap ) ) {
5387              return new IXR_Error( 401, $error_message );
5388          }
5389  
5390          // Check for a valid post format if one was given.
5391          if ( isset( $content_struct['wp_post_format'] ) ) {
5392              $content_struct['wp_post_format'] = sanitize_key( $content_struct['wp_post_format'] );
5393              if ( ! array_key_exists( $content_struct['wp_post_format'], get_post_format_strings() ) ) {
5394                  return new IXR_Error( 404, __( 'Invalid post format.' ) );
5395              }
5396          }
5397  
5398          // Let WordPress generate the 'post_name' (slug) unless
5399          // one has been provided.
5400          $post_name = '';
5401          if ( isset( $content_struct['wp_slug'] ) ) {
5402              $post_name = $content_struct['wp_slug'];
5403          }
5404  
5405          // Only use a password if one was given.
5406          if ( isset( $content_struct['wp_password'] ) ) {
5407              $post_password = $content_struct['wp_password'];
5408          } else {
5409              $post_password = '';
5410          }
5411  
5412          // Only set a post parent if one was given.
5413          if ( isset( $content_struct['wp_page_parent_id'] ) ) {
5414              $post_parent = $content_struct['wp_page_parent_id'];
5415          } else {
5416              $post_parent = 0;
5417          }
5418  
5419          // Only set the 'menu_order' if it was given.
5420          if ( isset( $content_struct['wp_page_order'] ) ) {
5421              $menu_order = $content_struct['wp_page_order'];
5422          } else {
5423              $menu_order = 0;
5424          }
5425  
5426          $post_author = $user->ID;
5427  
5428          // If an author id was provided then use it instead.
5429          if ( isset( $content_struct['wp_author_id'] ) && ( $user->ID != $content_struct['wp_author_id'] ) ) {
5430              switch ( $post_type ) {
5431                  case 'post':
5432                      if ( ! current_user_can( 'edit_others_posts' ) ) {
5433                          return new IXR_Error( 401, __( 'Sorry, you are not allowed to create posts as this user.' ) );
5434                      }
5435                      break;
5436                  case 'page':
5437                      if ( ! current_user_can( 'edit_others_pages' ) ) {
5438                          return new IXR_Error( 401, __( 'Sorry, you are not allowed to create pages as this user.' ) );
5439                      }
5440                      break;
5441                  default:
5442                      return new IXR_Error( 401, __( 'Invalid post type.' ) );
5443              }
5444              $author = get_userdata( $content_struct['wp_author_id'] );
5445              if ( ! $author ) {
5446                  return new IXR_Error( 404, __( 'Invalid author ID.' ) );
5447              }
5448              $post_author = $content_struct['wp_author_id'];
5449          }
5450  
5451          $post_title   = isset( $content_struct['title'] ) ? $content_struct['title'] : null;
5452          $post_content = isset( $content_struct['description'] ) ? $content_struct['description'] : null;
5453  
5454          $post_status = $publish ? 'publish' : 'draft';
5455  
5456          if ( isset( $content_struct[ "{$post_type}_status" ] ) ) {
5457              switch ( $content_struct[ "{$post_type}_status" ] ) {
5458                  case 'draft':
5459                  case 'pending':
5460                  case 'private':
5461                  case 'publish':
5462                      $post_status = $content_struct[ "{$post_type}_status" ];
5463                      break;
5464                  default:
5465                      $post_status = $publish ? 'publish' : 'draft';
5466                      break;
5467              }
5468          }
5469  
5470          $post_excerpt = isset( $content_struct['mt_excerpt'] ) ? $content_struct['mt_excerpt'] : null;
5471          $post_more    = isset( $content_struct['mt_text_more'] ) ? $content_struct['mt_text_more'] : null;
5472  
5473          $tags_input = isset( $content_struct['mt_keywords'] ) ? $content_struct['mt_keywords'] : null;
5474  
5475          if ( isset( $content_struct['mt_allow_comments'] ) ) {
5476              if ( ! is_numeric( $content_struct['mt_allow_comments'] ) ) {
5477                  switch ( $content_struct['mt_allow_comments'] ) {
5478                      case 'closed':
5479                          $comment_status = 'closed';
5480                          break;
5481                      case 'open':
5482                          $comment_status = 'open';
5483                          break;
5484                      default:
5485                          $comment_status = get_default_comment_status( $post_type );
5486                          break;
5487                  }
5488              } else {
5489                  switch ( (int) $content_struct['mt_allow_comments'] ) {
5490                      case 0:
5491                      case 2:
5492                          $comment_status = 'closed';
5493                          break;
5494                      case 1:
5495                          $comment_status = 'open';
5496                          break;
5497                      default:
5498                          $comment_status = get_default_comment_status( $post_type );
5499                          break;
5500                  }
5501              }
5502          } else {
5503              $comment_status = get_default_comment_status( $post_type );
5504          }
5505  
5506          if ( isset( $content_struct['mt_allow_pings'] ) ) {
5507              if ( ! is_numeric( $content_struct['mt_allow_pings'] ) ) {
5508                  switch ( $content_struct['mt_allow_pings'] ) {
5509                      case 'closed':
5510                          $ping_status = 'closed';
5511                          break;
5512                      case 'open':
5513                          $ping_status = 'open';
5514                          break;
5515                      default:
5516                          $ping_status = get_default_comment_status( $post_type, 'pingback' );
5517                          break;
5518                  }
5519              } else {
5520                  switch ( (int) $content_struct['mt_allow_pings'] ) {
5521                      case 0:
5522                          $ping_status = 'closed';
5523                          break;
5524                      case 1:
5525                          $ping_status = 'open';
5526                          break;
5527                      default:
5528                          $ping_status = get_default_comment_status( $post_type, 'pingback' );
5529                          break;
5530                  }
5531              }
5532          } else {
5533              $ping_status = get_default_comment_status( $post_type, 'pingback' );
5534          }
5535  
5536          if ( $post_more ) {
5537              $post_content = $post_content . '<!--more-->' . $post_more;
5538          }
5539  
5540          $to_ping = null;
5541          if ( isset( $content_struct['mt_tb_ping_urls'] ) ) {
5542              $to_ping = $content_struct['mt_tb_ping_urls'];
5543              if ( is_array( $to_ping ) ) {
5544                  $to_ping = implode( ' ', $to_ping );
5545              }
5546          }
5547  
5548          // Do some timestamp voodoo.
5549          if ( ! empty( $content_struct['date_created_gmt'] ) ) {
5550              // We know this is supposed to be GMT, so we're going to slap that Z on there by force.
5551              $dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
5552          } elseif ( ! empty( $content_struct['dateCreated'] ) ) {
5553              $dateCreated = $content_struct['dateCreated']->getIso();
5554          }
5555  
5556          if ( ! empty( $dateCreated ) ) {
5557              $post_date     = iso8601_to_datetime( $dateCreated );
5558              $post_date_gmt = iso8601_to_datetime( $dateCreated, 'gmt' );
5559          } else {
5560              $post_date     = '';
5561              $post_date_gmt = '';
5562          }
5563  
5564          $post_category = array();
5565          if ( isset( $content_struct['categories'] ) ) {
5566              $catnames = $content_struct['categories'];
5567  
5568              if ( is_array( $catnames ) ) {
5569                  foreach ( $catnames as $cat ) {
5570                      $post_category[] = get_cat_ID( $cat );
5571                  }
5572              }
5573          }
5574  
5575          $postdata = compact( 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'comment_status', 'ping_status', 'to_ping', 'post_type', 'post_name', 'post_password', 'post_parent', 'menu_order', 'tags_input', 'page_template' );
5576  
5577          $post_ID        = get_default_post_to_edit( $post_type, true )->ID;
5578          $postdata['ID'] = $post_ID;
5579  
5580          // Only posts can be sticky.
5581          if ( 'post' === $post_type && isset( $content_struct['sticky'] ) ) {
5582              $data           = $postdata;
5583              $data['sticky'] = $content_struct['sticky'];
5584              $error          = $this->_toggle_sticky( $data );
5585              if ( $error ) {
5586                  return $error;
5587              }
5588          }
5589  
5590          if ( isset( $content_struct['custom_fields'] ) ) {
5591              $this->set_custom_fields( $post_ID, $content_struct['custom_fields'] );
5592          }
5593  
5594          if ( isset( $content_struct['wp_post_thumbnail'] ) ) {
5595              if ( set_post_thumbnail( $post_ID, $content_struct['wp_post_thumbnail'] ) === false ) {
5596                  return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
5597              }
5598  
5599              unset( $content_struct['wp_post_thumbnail'] );
5600          }
5601  
5602          // Handle enclosures.
5603          $thisEnclosure = isset( $content_struct['enclosure'] ) ? $content_struct['enclosure'] : null;
5604          $this->add_enclosure_if_new( $post_ID, $thisEnclosure );
5605  
5606          $this->attach_uploads( $post_ID, $post_content );
5607  
5608          // Handle post formats if assigned, value is validated earlier
5609          // in this function.
5610          if ( isset( $content_struct['wp_post_format'] ) ) {
5611              set_post_format( $post_ID, $content_struct['wp_post_format'] );
5612          }
5613  
5614          $post_ID = wp_insert_post( $postdata, true );
5615          if ( is_wp_error( $post_ID ) ) {
5616              return new IXR_Error( 500, $post_ID->get_error_message() );
5617          }
5618  
5619          if ( ! $post_ID ) {
5620              return new IXR_Error( 500, __( 'Sorry, the post could not be created.' ) );
5621          }
5622  
5623          /**
5624           * Fires after a new post has been successfully created via the XML-RPC MovableType API.
5625           *
5626           * @since 3.4.0
5627           *
5628           * @param int   $post_ID ID of the new post.
5629           * @param array $args    An array of arguments to create the new post.
5630           */
5631          do_action( 'xmlrpc_call_success_mw_newPost', $post_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
5632  
5633          return (string) $post_ID;
5634      }
5635  
5636      /**
5637       * Adds an enclosure to a post if it's new.
5638       *
5639       * @since 2.8.0
5640       *
5641       * @param int   $post_ID   Post ID.
5642       * @param array $enclosure Enclosure data.
5643       */
5644  	public function add_enclosure_if_new( $post_ID, $enclosure ) {
5645          if ( is_array( $enclosure ) && isset( $enclosure['url'] ) && isset( $enclosure['length'] ) && isset( $enclosure['type'] ) ) {
5646              $encstring  = $enclosure['url'] . "\n" . $enclosure['length'] . "\n" . $enclosure['type'] . "\n";
5647              $found      = false;
5648              $enclosures = get_post_meta( $post_ID, 'enclosure' );
5649              if ( $enclosures ) {
5650                  foreach ( $enclosures as $enc ) {
5651                      // This method used to omit the trailing new line. #23219
5652                      if ( rtrim( $enc, "\n" ) == rtrim( $encstring, "\n" ) ) {
5653                          $found = true;
5654                          break;
5655                      }
5656                  }
5657              }
5658              if ( ! $found ) {
5659                  add_post_meta( $post_ID, 'enclosure', $encstring );
5660              }
5661          }
5662      }
5663  
5664      /**
5665       * Attach upload to a post.
5666       *
5667       * @since 2.1.0
5668       *
5669       * @global wpdb $wpdb WordPress database abstraction object.
5670       *
5671       * @param int    $post_ID      Post ID.
5672       * @param string $post_content Post Content for attachment.
5673       */
5674  	public function attach_uploads( $post_ID, $post_content ) {
5675          global $wpdb;
5676  
5677          // Find any unattached files.
5678          $attachments = $wpdb->get_results( "SELECT ID, guid FROM {$wpdb->posts} WHERE post_parent = '0' AND post_type = 'attachment'" );
5679          if ( is_array( $attachments ) ) {
5680              foreach ( $attachments as $file ) {
5681                  if ( ! empty( $file->guid ) && strpos( $post_content, $file->guid ) !== false ) {
5682                      $wpdb->update( $wpdb->posts, array( 'post_parent' => $post_ID ), array( 'ID' => $file->ID ) );
5683                  }
5684              }
5685          }
5686      }
5687  
5688      /**
5689       * Edit a post.
5690       *
5691       * @since 1.5.0
5692       *
5693       * @param array $args {
5694       *     Method arguments. Note: arguments must be ordered as documented.
5695       *
5696       *     @type int    $blog_id (unused)
5697       *     @type string $username
5698       *     @type string $password
5699       *     @type array  $content_struct
5700       *     @type int    $publish
5701       * }
5702       * @return true|IXR_Error True on success.
5703       */
5704  	public function mw_editPost( $args ) {
5705          $this->escape( $args );
5706  
5707          $post_ID        = (int) $args[0];
5708          $username       = $args[1];
5709          $password       = $args[2];
5710          $content_struct = $args[3];
5711          $publish        = isset( $args[4] ) ? $args[4] : 0;
5712  
5713          $user = $this->login( $username, $password );
5714          if ( ! $user ) {
5715              return $this->error;
5716          }
5717  
5718          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5719          do_action( 'xmlrpc_call', 'metaWeblog.editPost', $args, $this );
5720  
5721          $postdata = get_post( $post_ID, ARRAY_A );
5722  
5723          /*
5724           * If there is no post data for the give post ID, stop now and return an error.
5725           * Otherwise a new post will be created (which was the old behavior).
5726           */
5727          if ( ! $postdata || empty( $postdata['ID'] ) ) {
5728              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
5729          }
5730  
5731          if ( ! current_user_can( 'edit_post', $post_ID ) ) {
5732              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
5733          }
5734  
5735          // Use wp.editPost to edit post types other than post and page.
5736          if ( ! in_array( $postdata['post_type'], array( 'post', 'page' ), true ) ) {
5737              return new IXR_Error( 401, __( 'Invalid post type.' ) );
5738          }
5739  
5740          // Thwart attempt to change the post type.
5741          if ( ! empty( $content_struct['post_type'] ) && ( $content_struct['post_type'] != $postdata['post_type'] ) ) {
5742              return new IXR_Error( 401, __( 'The post type may not be changed.' ) );
5743          }
5744  
5745          // Check for a valid post format if one was given.
5746          if ( isset( $content_struct['wp_post_format'] ) ) {
5747              $content_struct['wp_post_format'] = sanitize_key( $content_struct['wp_post_format'] );
5748              if ( ! array_key_exists( $content_struct['wp_post_format'], get_post_format_strings() ) ) {
5749                  return new IXR_Error( 404, __( 'Invalid post format.' ) );
5750              }
5751          }
5752  
5753          $this->escape( $postdata );
5754  
5755          $ID             = $postdata['ID'];
5756          $post_content   = $postdata['post_content'];
5757          $post_title     = $postdata['post_title'];
5758          $post_excerpt   = $postdata['post_excerpt'];
5759          $post_password  = $postdata['post_password'];
5760          $post_parent    = $postdata['post_parent'];
5761          $post_type      = $postdata['post_type'];
5762          $menu_order     = $postdata['menu_order'];
5763          $ping_status    = $postdata['ping_status'];
5764          $comment_status = $postdata['comment_status'];
5765  
5766          // Let WordPress manage slug if none was provided.
5767          $post_name = $postdata['post_name'];
5768          if ( isset( $content_struct['wp_slug'] ) ) {
5769              $post_name = $content_struct['wp_slug'];
5770          }
5771  
5772          // Only use a password if one was given.
5773          if ( isset( $content_struct['wp_password'] ) ) {
5774              $post_password = $content_struct['wp_password'];
5775          }
5776  
5777          // Only set a post parent if one was given.
5778          if ( isset( $content_struct['wp_page_parent_id'] ) ) {
5779              $post_parent = $content_struct['wp_page_parent_id'];
5780          }
5781  
5782          // Only set the 'menu_order' if it was given.
5783          if ( isset( $content_struct['wp_page_order'] ) ) {
5784              $menu_order = $content_struct['wp_page_order'];
5785          }
5786  
5787          $page_template = null;
5788          if ( ! empty( $content_struct['wp_page_template'] ) && 'page' === $post_type ) {
5789              $page_template = $content_struct['wp_page_template'];
5790          }
5791  
5792          $post_author = $postdata['post_author'];
5793  
5794          // If an author id was provided then use it instead.
5795          if ( isset( $content_struct['wp_author_id'] ) ) {
5796              // Check permissions if attempting to switch author to or from another user.
5797              if ( $user->ID != $content_struct['wp_author_id'] || $user->ID != $post_author ) {
5798                  switch ( $post_type ) {
5799                      case 'post':
5800                          if ( ! current_user_can( 'edit_others_posts' ) ) {
5801                              return new IXR_Error( 401, __( 'Sorry, you are not allowed to change the post author as this user.' ) );
5802                          }
5803                          break;
5804                      case 'page':
5805                          if ( ! current_user_can( 'edit_others_pages' ) ) {
5806                              return new IXR_Error( 401, __( 'Sorry, you are not allowed to change the page author as this user.' ) );
5807                          }
5808                          break;
5809                      default:
5810                          return new IXR_Error( 401, __( 'Invalid post type.' ) );
5811                  }
5812                  $post_author = $content_struct['wp_author_id'];
5813              }
5814          }
5815  
5816          if ( isset( $content_struct['mt_allow_comments'] ) ) {
5817              if ( ! is_numeric( $content_struct['mt_allow_comments'] ) ) {
5818                  switch ( $content_struct['mt_allow_comments'] ) {
5819                      case 'closed':
5820                          $comment_status = 'closed';
5821                          break;
5822                      case 'open':
5823                          $comment_status = 'open';
5824                          break;
5825                      default:
5826                          $comment_status = get_default_comment_status( $post_type );
5827                          break;
5828                  }
5829              } else {
5830                  switch ( (int) $content_struct['mt_allow_comments'] ) {
5831                      case 0:
5832                      case 2:
5833                          $comment_status = 'closed';
5834                          break;
5835                      case 1:
5836                          $comment_status = 'open';
5837                          break;
5838                      default:
5839                          $comment_status = get_default_comment_status( $post_type );
5840                          break;
5841                  }
5842              }
5843          }
5844  
5845          if ( isset( $content_struct['mt_allow_pings'] ) ) {
5846              if ( ! is_numeric( $content_struct['mt_allow_pings'] ) ) {
5847                  switch ( $content_struct['mt_allow_pings'] ) {
5848                      case 'closed':
5849                          $ping_status = 'closed';
5850                          break;
5851                      case 'open':
5852                          $ping_status = 'open';
5853                          break;
5854                      default:
5855                          $ping_status = get_default_comment_status( $post_type, 'pingback' );
5856                          break;
5857                  }
5858              } else {
5859                  switch ( (int) $content_struct['mt_allow_pings'] ) {
5860                      case 0:
5861                          $ping_status = 'closed';
5862                          break;
5863                      case 1:
5864                          $ping_status = 'open';
5865                          break;
5866                      default:
5867                          $ping_status = get_default_comment_status( $post_type, 'pingback' );
5868                          break;
5869                  }
5870              }
5871          }
5872  
5873          if ( isset( $content_struct['title'] ) ) {
5874              $post_title = $content_struct['title'];
5875          }
5876  
5877          if ( isset( $content_struct['description'] ) ) {
5878              $post_content = $content_struct['description'];
5879          }
5880  
5881          $post_category = array();
5882          if ( isset( $content_struct['categories'] ) ) {
5883              $catnames = $content_struct['categories'];
5884              if ( is_array( $catnames ) ) {
5885                  foreach ( $catnames as $cat ) {
5886                      $post_category[] = get_cat_ID( $cat );
5887                  }
5888              }
5889          }
5890  
5891          if ( isset( $content_struct['mt_excerpt'] ) ) {
5892              $post_excerpt = $content_struct['mt_excerpt'];
5893          }
5894  
5895          $post_more = isset( $content_struct['mt_text_more'] ) ? $content_struct['mt_text_more'] : null;
5896  
5897          $post_status = $publish ? 'publish' : 'draft';
5898          if ( isset( $content_struct[ "{$post_type}_status" ] ) ) {
5899              switch ( $content_struct[ "{$post_type}_status" ] ) {
5900                  case 'draft':
5901                  case 'pending':
5902                  case 'private':
5903                  case 'publish':
5904                      $post_status = $content_struct[ "{$post_type}_status" ];
5905                      break;
5906                  default:
5907                      $post_status = $publish ? 'publish' : 'draft';
5908                      break;
5909              }
5910          }
5911  
5912          $tags_input = isset( $content_struct['mt_keywords'] ) ? $content_struct['mt_keywords'] : null;
5913  
5914          if ( 'publish' === $post_status || 'private' === $post_status ) {
5915              if ( 'page' === $post_type && ! current_user_can( 'publish_pages' ) ) {
5916                  return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this page.' ) );
5917              } elseif ( ! current_user_can( 'publish_posts' ) ) {
5918                  return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) );
5919              }
5920          }
5921  
5922          if ( $post_more ) {
5923              $post_content = $post_content . '<!--more-->' . $post_more;
5924          }
5925  
5926          $to_ping = null;
5927          if ( isset( $content_struct['mt_tb_ping_urls'] ) ) {
5928              $to_ping = $content_struct['mt_tb_ping_urls'];
5929              if ( is_array( $to_ping ) ) {
5930                  $to_ping = implode( ' ', $to_ping );
5931              }
5932          }
5933  
5934          // Do some timestamp voodoo.
5935          if ( ! empty( $content_struct['date_created_gmt'] ) ) {
5936              // We know this is supposed to be GMT, so we're going to slap that Z on there by force.
5937              $dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
5938          } elseif ( ! empty( $content_struct['dateCreated'] ) ) {
5939              $dateCreated = $content_struct['dateCreated']->getIso();
5940          }
5941  
5942          // Default to not flagging the post date to be edited unless it's intentional.
5943          $edit_date = false;
5944  
5945          if ( ! empty( $dateCreated ) ) {
5946              $post_date     = iso8601_to_datetime( $dateCreated );
5947              $post_date_gmt = iso8601_to_datetime( $dateCreated, 'gmt' );
5948  
5949              // Flag the post date to be edited.
5950              $edit_date = true;
5951          } else {
5952              $post_date     = $postdata['post_date'];
5953              $post_date_gmt = $postdata['post_date_gmt'];
5954          }
5955  
5956          // We've got all the data -- post it.
5957          $newpost = compact( 'ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'comment_status', 'ping_status', 'edit_date', 'post_date', 'post_date_gmt', 'to_ping', 'post_name', 'post_password', 'post_parent', 'menu_order', 'post_author', 'tags_input', 'page_template' );
5958  
5959          $result = wp_update_post( $newpost, true );
5960          if ( is_wp_error( $result ) ) {
5961              return new IXR_Error( 500, $result->get_error_message() );
5962          }
5963  
5964          if ( ! $result ) {
5965              return new IXR_Error( 500, __( 'Sorry, the post could not be updated.' ) );
5966          }
5967  
5968          // Only posts can be sticky.
5969          if ( 'post' === $post_type && isset( $content_struct['sticky'] ) ) {
5970              $data              = $newpost;
5971              $data['sticky']    = $content_struct['sticky'];
5972              $data['post_type'] = 'post';
5973              $error             = $this->_toggle_sticky( $data, true );
5974              if ( $error ) {
5975                  return $error;
5976              }
5977          }
5978  
5979          if ( isset( $content_struct['custom_fields'] ) ) {
5980              $this->set_custom_fields( $post_ID, $content_struct['custom_fields'] );
5981          }
5982  
5983          if ( isset( $content_struct['wp_post_thumbnail'] ) ) {
5984  
5985              // Empty value deletes, non-empty value adds/updates.
5986              if ( empty( $content_struct['wp_post_thumbnail'] ) ) {
5987                  delete_post_thumbnail( $post_ID );
5988              } else {
5989                  if ( set_post_thumbnail( $post_ID, $content_struct['wp_post_thumbnail'] ) === false ) {
5990                      return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
5991                  }
5992              }
5993              unset( $content_struct['wp_post_thumbnail'] );
5994          }
5995  
5996          // Handle enclosures.
5997          $thisEnclosure = isset( $content_struct['enclosure'] ) ? $content_struct['enclosure'] : null;
5998          $this->add_enclosure_if_new( $post_ID, $thisEnclosure );
5999  
6000          $this->attach_uploads( $ID, $post_content );
6001  
6002          // Handle post formats if assigned, validation is handled earlier in this function.
6003          if ( isset( $content_struct['wp_post_format'] ) ) {
6004              set_post_format( $post_ID, $content_struct['wp_post_format'] );
6005          }
6006  
6007          /**
6008           * Fires after a post has been successfully updated via the XML-RPC MovableType API.
6009           *
6010           * @since 3.4.0
6011           *
6012           * @param int   $post_ID ID of the updated post.
6013           * @param array $args    An array of arguments to update the post.
6014           */
6015          do_action( 'xmlrpc_call_success_mw_editPost', $post_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
6016  
6017          return true;
6018      }
6019  
6020      /**
6021       * Retrieve post.
6022       *
6023       * @since 1.5.0
6024       *
6025       * @param array $args {
6026       *     Method arguments. Note: arguments must be ordered as documented.
6027       *
6028       *     @type int    $blog_id (unused)
6029       *     @type int    $post_ID
6030       *     @type string $username
6031       *     @type string $password
6032       * }
6033       * @return array|IXR_Error
6034       */
6035  	public function mw_getPost( $args ) {
6036          $this->escape( $args );
6037  
6038          $post_ID  = (int) $args[0];
6039          $username = $args[1];
6040          $password = $args[2];
6041  
6042          $user = $this->login( $username, $password );
6043          if ( ! $user ) {
6044              return $this->error;
6045          }
6046  
6047          $postdata = get_post( $post_ID, ARRAY_A );
6048          if ( ! $postdata ) {
6049              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
6050          }
6051  
6052          if ( ! current_user_can( 'edit_post', $post_ID ) ) {
6053              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
6054          }
6055  
6056          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6057          do_action( 'xmlrpc_call', 'metaWeblog.getPost', $args, $this );
6058  
6059          if ( '' !== $postdata['post_date'] ) {
6060              $post_date         = $this->_convert_date( $postdata['post_date'] );
6061              $post_date_gmt     = $this->_convert_date_gmt( $postdata['post_date_gmt'], $postdata['post_date'] );
6062              $post_modified     = $this->_convert_date( $postdata['post_modified'] );
6063              $post_modified_gmt = $this->_convert_date_gmt( $postdata['post_modified_gmt'], $postdata['post_modified'] );
6064  
6065              $categories = array();
6066              $catids     = wp_get_post_categories( $post_ID );
6067              foreach ( $catids as $catid ) {
6068                  $categories[] = get_cat_name( $catid );
6069              }
6070  
6071              $tagnames = array();
6072              $tags     = wp_get_post_tags( $post_ID );
6073              if ( ! empty( $tags ) ) {
6074                  foreach ( $tags as $tag ) {
6075                      $tagnames[] = $tag->name;
6076                  }
6077                  $tagnames = implode( ', ', $tagnames );
6078              } else {
6079                  $tagnames = '';
6080              }
6081  
6082              $post = get_extended( $postdata['post_content'] );
6083              $link = get_permalink( $postdata['ID'] );
6084  
6085              // Get the author info.
6086              $author = get_userdata( $postdata['post_author'] );
6087  
6088              $allow_comments = ( 'open' === $postdata['comment_status'] ) ? 1 : 0;
6089              $allow_pings    = ( 'open' === $postdata['ping_status'] ) ? 1 : 0;
6090  
6091              // Consider future posts as published.
6092              if ( 'future' === $postdata['post_status'] ) {
6093                  $postdata['post_status'] = 'publish';
6094              }
6095  
6096              // Get post format.
6097              $post_format = get_post_format( $post_ID );
6098              if ( empty( $post_format ) ) {
6099                  $post_format = 'standard';
6100              }
6101  
6102              $sticky = false;
6103              if ( is_sticky( $post_ID ) ) {
6104                  $sticky = true;
6105              }
6106  
6107              $enclosure = array();
6108              foreach ( (array) get_post_custom( $post_ID ) as $key => $val ) {
6109                  if ( 'enclosure' === $key ) {
6110                      foreach ( (array) $val as $enc ) {
6111                          $encdata             = explode( "\n", $enc );
6112                          $enclosure['url']    = trim( htmlspecialchars( $encdata[0] ) );
6113                          $enclosure['length'] = (int) trim( $encdata[1] );
6114                          $enclosure['type']   = trim( $encdata[2] );
6115                          break 2;
6116                      }
6117                  }
6118              }
6119  
6120              $resp = array(
6121                  'dateCreated'            => $post_date,
6122                  'userid'                 => $postdata['post_author'],
6123                  'postid'                 => $postdata['ID'],
6124                  'description'            => $post['main'],
6125                  'title'                  => $postdata['post_title'],
6126                  'link'                   => $link,
6127                  'permaLink'              => $link,
6128                  // Commented out because no other tool seems to use this.
6129                  // 'content' => $entry['post_content'],
6130                  'categories'             => $categories,
6131                  'mt_excerpt'             => $postdata['post_excerpt'],
6132                  'mt_text_more'           => $post['extended'],
6133                  'wp_more_text'           => $post['more_text'],
6134                  'mt_allow_comments'      => $allow_comments,
6135                  'mt_allow_pings'         => $allow_pings,
6136                  'mt_keywords'            => $tagnames,
6137                  'wp_slug'                => $postdata['post_name'],
6138                  'wp_password'            => $postdata['post_password'],
6139                  'wp_author_id'           => (string) $author->ID,
6140                  'wp_author_display_name' => $author->display_name,
6141                  'date_created_gmt'       => $post_date_gmt,
6142                  'post_status'            => $postdata['post_status'],
6143                  'custom_fields'          => $this->get_custom_fields( $post_ID ),
6144                  'wp_post_format'         => $post_format,
6145                  'sticky'                 => $sticky,
6146                  'date_modified'          => $post_modified,
6147                  'date_modified_gmt'      => $post_modified_gmt,
6148              );
6149  
6150              if ( ! empty( $enclosure ) ) {
6151                  $resp['enclosure'] = $enclosure;
6152              }
6153  
6154              $resp['wp_post_thumbnail'] = get_post_thumbnail_id( $postdata['ID'] );
6155  
6156              return $resp;
6157          } else {
6158              return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
6159          }
6160      }
6161  
6162      /**
6163       * Retrieve list of recent posts.
6164       *
6165       * @since 1.5.0
6166       *
6167       * @param array $args {
6168       *     Method arguments. Note: arguments must be ordered as documented.
6169       *
6170       *     @type int    $blog_id (unused)
6171       *     @type string $username
6172       *     @type string $password
6173       *     @type int    $numberposts
6174       * }
6175       * @return array|IXR_Error
6176       */
6177  	public function mw_getRecentPosts( $args ) {
6178          $this->escape( $args );
6179  
6180          $username = $args[1];
6181          $password = $args[2];
6182          if ( isset( $args[3] ) ) {
6183              $query = array( 'numberposts' => absint( $args[3] ) );
6184          } else {
6185              $query = array();
6186          }
6187  
6188          $user = $this->login( $username, $password );
6189          if ( ! $user ) {
6190              return $this->error;
6191          }
6192  
6193          if ( ! current_user_can( 'edit_posts' ) ) {
6194              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
6195          }
6196  
6197          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6198          do_action( 'xmlrpc_call', 'metaWeblog.getRecentPosts', $args, $this );
6199  
6200          $posts_list = wp_get_recent_posts( $query );
6201  
6202          if ( ! $posts_list ) {
6203              return array();
6204          }
6205  
6206          $recent_posts = array();
6207          foreach ( $posts_list as $entry ) {
6208              if ( ! current_user_can( 'edit_post', $entry['ID'] ) ) {
6209                  continue;
6210              }
6211  
6212              $post_date         = $this->_convert_date( $entry['post_date'] );
6213              $post_date_gmt     = $this->_convert_date_gmt( $entry['post_date_gmt'], $entry['post_date'] );
6214              $post_modified     = $this->_convert_date( $entry['post_modified'] );
6215              $post_modified_gmt = $this->_convert_date_gmt( $entry['post_modified_gmt'], $entry['post_modified'] );
6216  
6217              $categories = array();
6218              $catids     = wp_get_post_categories( $entry['ID'] );
6219              foreach ( $catids as $catid ) {
6220                  $categories[] = get_cat_name( $catid );
6221              }
6222  
6223              $tagnames = array();
6224              $tags     = wp_get_post_tags( $entry['ID'] );
6225              if ( ! empty( $tags ) ) {
6226                  foreach ( $tags as $tag ) {
6227                      $tagnames[] = $tag->name;
6228                  }
6229                  $tagnames = implode( ', ', $tagnames );
6230              } else {
6231                  $tagnames = '';
6232              }
6233  
6234              $post = get_extended( $entry['post_content'] );
6235              $link = get_permalink( $entry['ID'] );
6236  
6237              // Get the post author info.
6238              $author = get_userdata( $entry['post_author'] );
6239  
6240              $allow_comments = ( 'open' === $entry['comment_status'] ) ? 1 : 0;
6241              $allow_pings    = ( 'open' === $entry['ping_status'] ) ? 1 : 0;
6242  
6243              // Consider future posts as published.
6244              if ( 'future' === $entry['post_status'] ) {
6245                  $entry['post_status'] = 'publish';
6246              }
6247  
6248              // Get post format.
6249              $post_format = get_post_format( $entry['ID'] );
6250              if ( empty( $post_format ) ) {
6251                  $post_format = 'standard';
6252              }
6253  
6254              $recent_posts[] = array(
6255                  'dateCreated'            => $post_date,
6256                  'userid'                 => $entry['post_author'],
6257                  'postid'                 => (string) $entry['ID'],
6258                  'description'            => $post['main'],
6259                  'title'                  => $entry['post_title'],
6260                  'link'                   => $link,
6261                  'permaLink'              => $link,
6262                  // Commented out because no other tool seems to use this.
6263                  // 'content' => $entry['post_content'],
6264                  'categories'             => $categories,
6265                  'mt_excerpt'             => $entry['post_excerpt'],
6266                  'mt_text_more'           => $post['extended'],
6267                  'wp_more_text'           => $post['more_text'],
6268                  'mt_allow_comments'      => $allow_comments,
6269                  'mt_allow_pings'         => $allow_pings,
6270                  'mt_keywords'            => $tagnames,
6271                  'wp_slug'                => $entry['post_name'],
6272                  'wp_password'            => $entry['post_password'],
6273                  'wp_author_id'           => (string) $author->ID,
6274                  'wp_author_display_name' => $author->display_name,
6275                  'date_created_gmt'       => $post_date_gmt,
6276                  'post_status'            => $entry['post_status'],
6277                  'custom_fields'          => $this->get_custom_fields( $entry['ID'] ),
6278                  'wp_post_format'         => $post_format,
6279                  'date_modified'          => $post_modified,
6280                  'date_modified_gmt'      => $post_modified_gmt,
6281                  'sticky'                 => ( 'post' === $entry['post_type'] && is_sticky( $entry['ID'] ) ),
6282                  'wp_post_thumbnail'      => get_post_thumbnail_id( $entry['ID'] ),
6283              );
6284          }
6285  
6286          return $recent_posts;
6287      }
6288  
6289      /**
6290       * Retrieve the list of categories on a given blog.
6291       *
6292       * @since 1.5.0
6293       *
6294       * @param array $args {
6295       *     Method arguments. Note: arguments must be ordered as documented.
6296       *
6297       *     @type int    $blog_id (unused)
6298       *     @type string $username
6299       *     @type string $password
6300       * }
6301       * @return array|IXR_Error
6302       */
6303  	public function mw_getCategories( $args ) {
6304          $this->escape( $args );
6305  
6306          $username = $args[1];
6307          $password = $args[2];
6308  
6309          $user = $this->login( $username, $password );
6310          if ( ! $user ) {
6311              return $this->error;
6312          }
6313  
6314          if ( ! current_user_can( 'edit_posts' ) ) {
6315              return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
6316          }
6317  
6318          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6319          do_action( 'xmlrpc_call', 'metaWeblog.getCategories', $args, $this );
6320  
6321          $categories_struct = array();
6322  
6323          $cats = get_categories( array( 'get' => 'all' ) );
6324          if ( $cats ) {
6325              foreach ( $cats as $cat ) {
6326                  $struct                        = array();
6327                  $struct['categoryId']          = $cat->term_id;
6328                  $struct['parentId']            = $cat->parent;
6329                  $struct['description']         = $cat->name;
6330                  $struct['categoryDescription'] = $cat->description;
6331                  $struct['categoryName']        = $cat->name;
6332                  $struct['htmlUrl']             = esc_html( get_category_link( $cat->term_id ) );
6333                  $struct['rssUrl']              = esc_html( get_category_feed_link( $cat->term_id, 'rss2' ) );
6334  
6335                  $categories_struct[] = $struct;
6336              }
6337          }
6338  
6339          return $categories_struct;
6340      }
6341  
6342      /**
6343       * Uploads a file, following your settings.
6344       *
6345       * Adapted from a patch by Johann Richard.
6346       *
6347       * @link http://mycvs.org/archives/2004/06/30/file-upload-to-wordpress-in-ecto/
6348       *
6349       * @since 1.5.0
6350       *
6351       * @global wpdb $wpdb WordPress database abstraction object.
6352       *
6353       * @param array $args {
6354       *     Method arguments. Note: arguments must be ordered as documented.
6355       *
6356       *     @type int    $blog_id (unused)
6357       *     @type string $username
6358       *     @type string $password
6359       *     @type array  $data
6360       * }
6361       * @return array|IXR_Error
6362       */
6363  	public function mw_newMediaObject( $args ) {
6364          global $wpdb;
6365  
6366          $username = $this->escape( $args[1] );
6367          $password = $this->escape( $args[2] );
6368          $data     = $args[3];
6369  
6370          $name = sanitize_file_name( $data['name'] );
6371          $type = $data['type'];
6372          $bits = $data['bits'];
6373  
6374          $user = $this->login( $username, $password );
6375          if ( ! $user ) {
6376              return $this->error;
6377          }
6378  
6379          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6380          do_action( 'xmlrpc_call', 'metaWeblog.newMediaObject', $args, $this );
6381  
6382          if ( ! current_user_can( 'upload_files' ) ) {
6383              $this->error = new IXR_Error( 401, __( 'Sorry, you are not allowed to upload files.' ) );
6384              return $this->error;
6385          }
6386  
6387          if ( is_multisite() && upload_is_user_over_quota( false ) ) {
6388              $this->error = new IXR_Error(
6389                  401,
6390                  sprintf(
6391                      /* translators: %s: Allowed space allocation. */
6392                      __( 'Sorry, you have used your space allocation of %s. Please delete some files to upload more files.' ),
6393                      size_format( get_space_allowed() * MB_IN_BYTES )
6394                  )
6395              );
6396              return $this->error;
6397          }
6398  
6399          /**
6400           * Filters whether to preempt the XML-RPC media upload.
6401           *
6402           * Returning a truthy value will effectively short-circuit the media upload,
6403           * returning that value as a 500 error instead.
6404           *
6405           * @since 2.1.0
6406           *
6407           * @param bool $error Whether to pre-empt the media upload. Default false.
6408           */
6409          $upload_err = apply_filters( 'pre_upload_error', false );
6410          if ( $upload_err ) {
6411              return new IXR_Error( 500, $upload_err );
6412          }
6413  
6414          $upload = wp_upload_bits( $name, null, $bits );
6415          if ( ! empty( $upload['error'] ) ) {
6416              /* translators: 1: File name, 2: Error message. */
6417              $errorString = sprintf( __( 'Could not write file %1$s (%2$s).' ), $name, $upload['error'] );
6418              return new IXR_Error( 500, $errorString );
6419          }
6420          // Construct the attachment array.
6421          $post_id = 0;
6422          if ( ! empty( $data['post_id'] ) ) {
6423              $post_id = (int) $data['post_id'];
6424  
6425              if ( ! current_user_can( 'edit_post', $post_id ) ) {
6426                  return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
6427              }
6428          }
6429          $attachment = array(
6430              'post_title'     => $name,
6431              'post_content'   => '',
6432              'post_type'      => 'attachment',
6433              'post_parent'    => $post_id,
6434              'post_mime_type' => $type,
6435              'guid'           => $upload['url'],
6436          );
6437  
6438          // Save the data.
6439          $id = wp_insert_attachment( $attachment, $upload['file'], $post_id );
6440          wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $upload['file'] ) );
6441  
6442          /**
6443           * Fires after a new attachment has been added via the XML-RPC MovableType API.
6444           *
6445           * @since 3.4.0
6446           *
6447           * @param int   $id   ID of the new attachment.
6448           * @param array $args An array of arguments to add the attachment.
6449           */
6450          do_action( 'xmlrpc_call_success_mw_newMediaObject', $id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
6451  
6452          $struct = $this->_prepare_media_item( get_post( $id ) );
6453  
6454          // Deprecated values.
6455          $struct['id']   = $struct['attachment_id'];
6456          $struct['file'] = $struct['title'];
6457          $struct['url']  = $struct['link'];
6458  
6459          return $struct;
6460      }
6461  
6462      /*
6463       * MovableType API functions.
6464       * Specs on http://www.movabletype.org/docs/mtmanual_programmatic.html
6465       */
6466  
6467      /**
6468       * Retrieve the post titles of recent posts.
6469       *
6470       * @since 1.5.0
6471       *
6472       * @param array $args {
6473       *     Method arguments. Note: arguments must be ordered as documented.
6474       *
6475       *     @type int    $blog_id (unused)
6476       *     @type string $username
6477       *     @type string $password
6478       *     @type int    $numberposts
6479       * }
6480       * @return array|IXR_Error
6481       */
6482  	public function mt_getRecentPostTitles( $args ) {
6483          $this->escape( $args );
6484  
6485          $username = $args[1];
6486          $password = $args[2];
6487          if ( isset( $args[3] ) ) {
6488              $query = array( 'numberposts' => absint( $args[3] ) );
6489          } else {
6490              $query = array();
6491          }
6492  
6493          $user = $this->login( $username, $password );
6494          if ( ! $user ) {
6495              return $this->error;
6496          }
6497  
6498          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6499          do_action( 'xmlrpc_call', 'mt.getRecentPostTitles', $args, $this );
6500  
6501          $posts_list = wp_get_recent_posts( $query );
6502  
6503          if ( ! $posts_list ) {
6504              $this->error = new IXR_Error( 500, __( 'Either there are no posts, or something went wrong.' ) );
6505              return $this->error;
6506          }
6507  
6508          $recent_posts = array();
6509  
6510          foreach ( $posts_list as $entry ) {
6511              if ( ! current_user_can( 'edit_post', $entry['ID'] ) ) {
6512                  continue;
6513              }
6514  
6515              $post_date     = $this->_convert_date( $entry['post_date'] );
6516              $post_date_gmt = $this->_convert_date_gmt( $entry['post_date_gmt'], $entry['post_date'] );
6517  
6518              $recent_posts[] = array(
6519                  'dateCreated'      => $post_date,
6520                  'userid'           => $entry['post_author'],
6521                  'postid'           => (string) $entry['ID'],
6522                  'title'            => $entry['post_title'],
6523                  'post_status'      => $entry['post_status'],
6524                  'date_created_gmt' => $post_date_gmt,
6525              );
6526          }
6527  
6528          return $recent_posts;
6529      }
6530  
6531      /**
6532       * Retrieve list of all categories on blog.
6533       *
6534       * @since 1.5.0
6535       *
6536       * @param array $args {
6537       *     Method arguments. Note: arguments must be ordered as documented.
6538       *
6539       *     @type int    $blog_id (unused)
6540       *     @type string $username
6541       *     @type string $password
6542       * }
6543       * @return array|IXR_Error
6544       */
6545  	public function mt_getCategoryList( $args ) {
6546          $this->escape( $args );
6547  
6548          $username = $args[1];
6549          $password = $args[2];
6550  
6551          $user = $this->login( $username, $password );
6552          if ( ! $user ) {
6553              return $this->error;
6554          }
6555  
6556          if ( ! current_user_can( 'edit_posts' ) ) {
6557              return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
6558          }
6559  
6560          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6561          do_action( 'xmlrpc_call', 'mt.getCategoryList', $args, $this );
6562  
6563          $categories_struct = array();
6564  
6565          $cats = get_categories(
6566              array(
6567                  'hide_empty'   => 0,
6568                  'hierarchical' => 0,
6569              )
6570          );
6571          if ( $cats ) {
6572              foreach ( $cats as $cat ) {
6573                  $struct                 = array();
6574                  $struct['categoryId']   = $cat->term_id;
6575                  $struct['categoryName'] = $cat->name;
6576  
6577                  $categories_struct[] = $struct;
6578              }
6579          }
6580  
6581          return $categories_struct;
6582      }
6583  
6584      /**
6585       * Retrieve post categories.
6586       *
6587       * @since 1.5.0
6588       *
6589       * @param array $args {
6590       *     Method arguments. Note: arguments must be ordered as documented.
6591       *
6592       *     @type int    $post_ID
6593       *     @type string $username
6594       *     @type string $password
6595       * }
6596       * @return array|IXR_Error
6597       */
6598  	public function mt_getPostCategories( $args ) {
6599          $this->escape( $args );
6600  
6601          $post_ID  = (int) $args[0];
6602          $username = $args[1];
6603          $password = $args[2];
6604  
6605          $user = $this->login( $username, $password );
6606          if ( ! $user ) {
6607              return $this->error;
6608          }
6609  
6610          if ( ! get_post( $post_ID ) ) {
6611              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
6612          }
6613  
6614          if ( ! current_user_can( 'edit_post', $post_ID ) ) {
6615              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
6616          }
6617  
6618          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6619          do_action( 'xmlrpc_call', 'mt.getPostCategories', $args, $this );
6620  
6621          $categories = array();
6622          $catids     = wp_get_post_categories( (int) $post_ID );
6623          // First listed category will be the primary category.
6624          $isPrimary = true;
6625          foreach ( $catids as $catid ) {
6626              $categories[] = array(
6627                  'categoryName' => get_cat_name( $catid ),
6628                  'categoryId'   => (string) $catid,
6629                  'isPrimary'    => $isPrimary,
6630              );
6631              $isPrimary    = false;
6632          }
6633  
6634          return $categories;
6635      }
6636  
6637      /**
6638       * Sets categories for a post.
6639       *
6640       * @since 1.5.0
6641       *
6642       * @param array $args {
6643       *     Method arguments. Note: arguments must be ordered as documented.
6644       *
6645       *     @type int    $post_ID
6646       *     @type string $username
6647       *     @type string $password
6648       *     @type array  $categories
6649       * }
6650       * @return true|IXR_Error True on success.
6651       */
6652  	public function mt_setPostCategories( $args ) {
6653          $this->escape( $args );
6654  
6655          $post_ID    = (int) $args[0];
6656          $username   = $args[1];
6657          $password   = $args[2];
6658          $categories = $args[3];
6659  
6660          $user = $this->login( $username, $password );
6661          if ( ! $user ) {
6662              return $this->error;
6663          }
6664  
6665          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6666          do_action( 'xmlrpc_call', 'mt.setPostCategories', $args, $this );
6667  
6668          if ( ! get_post( $post_ID ) ) {
6669              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
6670          }
6671  
6672          if ( ! current_user_can( 'edit_post', $post_ID ) ) {
6673              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
6674          }
6675  
6676          $catids = array();
6677          foreach ( $categories as $cat ) {
6678              $catids[] = $cat['categoryId'];
6679          }
6680  
6681          wp_set_post_categories( $post_ID, $catids );
6682  
6683          return true;
6684      }
6685  
6686      /**
6687       * Retrieve an array of methods supported by this server.
6688       *
6689       * @since 1.5.0
6690       *
6691       * @return array
6692       */
6693  	public function mt_supportedMethods() {
6694          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6695          do_action( 'xmlrpc_call', 'mt.supportedMethods', array(), $this );
6696  
6697          return array_keys( $this->methods );
6698      }
6699  
6700      /**
6701       * Retrieve an empty array because we don't support per-post text filters.
6702       *
6703       * @since 1.5.0
6704       */
6705  	public function mt_supportedTextFilters() {
6706          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6707          do_action( 'xmlrpc_call', 'mt.supportedTextFilters', array(), $this );
6708  
6709          /**
6710           * Filters the MoveableType text filters list for XML-RPC.
6711           *
6712           * @since 2.2.0
6713           *
6714           * @param array $filters An array of text filters.
6715           */
6716          return apply_filters( 'xmlrpc_text_filters', array() );
6717      }
6718  
6719      /**
6720       * Retrieve trackbacks sent to a given post.
6721       *
6722       * @since 1.5.0
6723       *
6724       * @global wpdb $wpdb WordPress database abstraction object.
6725       *
6726       * @param int $post_ID
6727       * @return array|IXR_Error
6728       */
6729  	public function mt_getTrackbackPings( $post_ID ) {
6730          global $wpdb;
6731  
6732          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6733          do_action( 'xmlrpc_call', 'mt.getTrackbackPings', $post_ID, $this );
6734  
6735          $actual_post = get_post( $post_ID, ARRAY_A );
6736  
6737          if ( ! $actual_post ) {
6738              return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
6739          }
6740  
6741          $comments = $wpdb->get_results( $wpdb->prepare( "SELECT comment_author_url, comment_content, comment_author_IP, comment_type FROM $wpdb->comments WHERE comment_post_ID = %d", $post_ID ) );
6742  
6743          if ( ! $comments ) {
6744              return array();
6745          }
6746  
6747          $trackback_pings = array();
6748          foreach ( $comments as $comment ) {
6749              if ( 'trackback' === $comment->comment_type ) {
6750                  $content           = $comment->comment_content;
6751                  $title             = substr( $content, 8, ( strpos( $content, '</strong>' ) - 8 ) );
6752                  $trackback_pings[] = array(
6753                      'pingTitle' => $title,
6754                      'pingURL'   => $comment->comment_author_url,
6755                      'pingIP'    => $comment->comment_author_IP,
6756                  );
6757              }
6758          }
6759  
6760          return $trackback_pings;
6761      }
6762  
6763      /**
6764       * Sets a post's publish status to 'publish'.
6765       *
6766       * @since 1.5.0
6767       *
6768       * @param array $args {
6769       *     Method arguments. Note: arguments must be ordered as documented.
6770       *
6771       *     @type int    $post_ID
6772       *     @type string $username
6773       *     @type string $password
6774       * }
6775       * @return int|IXR_Error
6776       */
6777  	public function mt_publishPost( $args ) {
6778          $this->escape( $args );
6779  
6780          $post_ID  = (int) $args[0];
6781          $username = $args[1];
6782          $password = $args[2];
6783  
6784          $user = $this->login( $username, $password );
6785          if ( ! $user ) {
6786              return $this->error;
6787          }
6788  
6789          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6790          do_action( 'xmlrpc_call', 'mt.publishPost', $args, $this );
6791  
6792          $postdata = get_post( $post_ID, ARRAY_A );
6793          if ( ! $postdata ) {
6794              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
6795          }
6796  
6797          if ( ! current_user_can( 'publish_posts' ) || ! current_user_can( 'edit_post', $post_ID ) ) {
6798              return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) );
6799          }
6800  
6801          $postdata['post_status'] = 'publish';
6802  
6803          // Retain old categories.
6804          $postdata['post_category'] = wp_get_post_categories( $post_ID );
6805          $this->escape( $postdata );
6806  
6807          return wp_update_post( $postdata );
6808      }
6809  
6810      /*
6811       * Pingback functions.
6812       * Specs on www.hixie.ch/specs/pingback/pingback
6813       */
6814  
6815      /**
6816       * Retrieves a pingback and registers it.
6817       *
6818       * @since 1.5.0
6819       *
6820       * @param array $args {
6821       *     Method arguments. Note: arguments must be ordered as documented.
6822       *
6823       *     @type string $pagelinkedfrom
6824       *     @type string $pagelinkedto
6825       * }
6826       * @return string|IXR_Error
6827       */
6828  	public function pingback_ping( $args ) {
6829          global $wpdb;
6830  
6831          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6832          do_action( 'xmlrpc_call', 'pingback.ping', $args, $this );
6833  
6834          $this->escape( $args );
6835  
6836          $pagelinkedfrom = str_replace( '&amp;', '&', $args[0] );
6837          $pagelinkedto   = str_replace( '&amp;', '&', $args[1] );
6838          $pagelinkedto   = str_replace( '&', '&amp;', $pagelinkedto );
6839  
6840          /**
6841           * Filters the pingback source URI.
6842           *
6843           * @since 3.6.0
6844           *
6845           * @param string $pagelinkedfrom URI of the page linked from.
6846           * @param string $pagelinkedto   URI of the page linked to.
6847           */
6848          $pagelinkedfrom = apply_filters( 'pingback_ping_source_uri', $pagelinkedfrom, $pagelinkedto );
6849  
6850          if ( ! $pagelinkedfrom ) {
6851              return $this->pingback_error( 0, __( 'A valid URL was not provided.' ) );
6852          }
6853  
6854          // Check if the page linked to is on our site.
6855          $pos1 = strpos( $pagelinkedto, str_replace( array( 'http://www.', 'http://', 'https://www.', 'https://' ), '', get_option( 'home' ) ) );
6856          if ( ! $pos1 ) {
6857              return $this->pingback_error( 0, __( 'Is there no link to us?' ) );
6858          }
6859  
6860          /*
6861           * Let's find which post is linked to.
6862           * FIXME: Does url_to_postid() cover all these cases already?
6863           * If so, then let's use it and drop the old code.
6864           */
6865          $urltest = parse_url( $pagelinkedto );
6866          $post_ID = url_to_postid( $pagelinkedto );
6867          if ( $post_ID ) {
6868              // $way
6869          } elseif ( isset( $urltest['path'] ) && preg_match( '#p/[0-9]{1,}#', $urltest['path'], $match ) ) {
6870              // The path defines the post_ID (archives/p/XXXX).
6871              $blah    = explode( '/', $match[0] );
6872              $post_ID = (int) $blah[1];
6873          } elseif ( isset( $urltest['query'] ) && preg_match( '#p=[0-9]{1,}#', $urltest['query'], $match ) ) {
6874              // The query string defines the post_ID (?p=XXXX).
6875              $blah    = explode( '=', $match[0] );
6876              $post_ID = (int) $blah[1];
6877          } elseif ( isset( $urltest['fragment'] ) ) {
6878              // An #anchor is there, it's either...
6879              if ( (int) $urltest['fragment'] ) {
6880                  // ...an integer #XXXX (simplest case),
6881                  $post_ID = (int) $urltest['fragment'];
6882              } elseif ( preg_match( '/post-[0-9]+/', $urltest['fragment'] ) ) {
6883                  // ...a post ID in the form 'post-###',
6884                  $post_ID = preg_replace( '/[^0-9]+/', '', $urltest['fragment'] );
6885              } elseif ( is_string( $urltest['fragment'] ) ) {
6886                  // ...or a string #title, a little more complicated.
6887                  $title   = preg_replace( '/[^a-z0-9]/i', '.', $urltest['fragment'] );
6888                  $sql     = $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_title RLIKE %s", $title );
6889                  $post_ID = $wpdb->get_var( $sql );
6890                  if ( ! $post_ID ) {
6891                      // Returning unknown error '0' is better than die()'ing.
6892                      return $this->pingback_error( 0, '' );
6893                  }
6894              }
6895          } else {
6896              // TODO: Attempt to extract a post ID from the given URL.
6897              return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either does not exist, or it is not a pingback-enabled resource.' ) );
6898          }
6899          $post_ID = (int) $post_ID;
6900  
6901          $post = get_post( $post_ID );
6902  
6903          if ( ! $post ) { // Post not found.
6904              return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either does not exist, or it is not a pingback-enabled resource.' ) );
6905          }
6906  
6907          if ( url_to_postid( $pagelinkedfrom ) == $post_ID ) {
6908              return $this->pingback_error( 0, __( 'The source URL and the target URL cannot both point to the same resource.' ) );
6909          }
6910  
6911          // Check if pings are on.
6912          if ( ! pings_open( $post ) ) {
6913              return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either does not exist, or it is not a pingback-enabled resource.' ) );
6914          }
6915  
6916          // Let's check that the remote site didn't already pingback this entry.
6917          if ( $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_author_url = %s", $post_ID, $pagelinkedfrom ) ) ) {
6918              return $this->pingback_error( 48, __( 'The pingback has already been registered.' ) );
6919          }
6920  
6921          // Very stupid, but gives time to the 'from' server to publish!
6922          sleep( 1 );
6923  
6924          $remote_ip = preg_replace( '/[^0-9a-fA-F:., ]/', '', $_SERVER['REMOTE_ADDR'] );
6925  
6926          /** This filter is documented in wp-includes/class-wp-http.php */
6927          $user_agent = apply_filters( 'http_headers_useragent', 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ), $pagelinkedfrom );
6928  
6929          // Let's check the remote site.
6930          $http_api_args = array(
6931              'timeout'             => 10,
6932              'redirection'         => 0,
6933              'limit_response_size' => 153600, // 150 KB
6934              'user-agent'          => "$user_agent; verifying pingback from $remote_ip",
6935              'headers'             => array(
6936                  'X-Pingback-Forwarded-For' => $remote_ip,
6937              ),
6938          );
6939  
6940          $request                = wp_safe_remote_get( $pagelinkedfrom, $http_api_args );
6941          $remote_source          = wp_remote_retrieve_body( $request );
6942          $remote_source_original = $remote_source;
6943  
6944          if ( ! $remote_source ) {
6945              return $this->pingback_error( 16, __( 'The source URL does not exist.' ) );
6946          }
6947  
6948          /**
6949           * Filters the pingback remote source.
6950           *
6951           * @since 2.5.0
6952           *
6953           * @param string $remote_source Response source for the page linked from.
6954           * @param string $pagelinkedto  URL of the page linked to.
6955           */
6956          $remote_source = apply_filters( 'pre_remote_source', $remote_source, $pagelinkedto );
6957  
6958          // Work around bug in strip_tags():
6959          $remote_source = str_replace( '<!DOC', '<DOC', $remote_source );
6960          $remote_source = preg_replace( '/[\r\n\t ]+/', ' ', $remote_source ); // normalize spaces
6961          $remote_source = preg_replace( '/<\/*(h1|h2|h3|h4|h5|h6|p|th|td|li|dt|dd|pre|caption|input|textarea|button|body)[^>]*>/', "\n\n", $remote_source );
6962  
6963          preg_match( '|<title>([^<]*?)</title>|is', $remote_source, $matchtitle );
6964          $title = isset( $matchtitle[1] ) ? $matchtitle[1] : '';
6965          if ( empty( $title ) ) {
6966              return $this->pingback_error( 32, __( 'A title on that page cannot be found.' ) );
6967          }
6968  
6969          // Remove all script and style tags including their content.
6970          $remote_source = preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $remote_source );
6971          // Just keep the tag we need.
6972          $remote_source = strip_tags( $remote_source, '<a>' );
6973  
6974          $p = explode( "\n\n", $remote_source );
6975  
6976          $preg_target = preg_quote( $pagelinkedto, '|' );
6977  
6978          foreach ( $p as $para ) {
6979              if ( strpos( $para, $pagelinkedto ) !== false ) { // It exists, but is it a link?
6980                  preg_match( '|<a[^>]+?' . $preg_target . '[^>]*>([^>]+?)</a>|', $para, $context );
6981  
6982                  // If the URL isn't in a link context, keep looking.
6983                  if ( empty( $context ) ) {
6984                      continue;
6985                  }
6986  
6987                  // We're going to use this fake tag to mark the context in a bit.
6988                  // The marker is needed in case the link text appears more than once in the paragraph.
6989                  $excerpt = preg_replace( '|\</?wpcontext\>|', '', $para );
6990  
6991                  // prevent really long link text
6992                  if ( strlen( $context[1] ) > 100 ) {
6993                      $context[1] = substr( $context[1], 0, 100 ) . '&#8230;';
6994                  }
6995  
6996                  $marker      = '<wpcontext>' . $context[1] . '</wpcontext>';  // Set up our marker.
6997                  $excerpt     = str_replace( $context[0], $marker, $excerpt ); // Swap out the link for our marker.
6998                  $excerpt     = strip_tags( $excerpt, '<wpcontext>' );         // Strip all tags but our context marker.
6999                  $excerpt     = trim( $excerpt );
7000                  $preg_marker = preg_quote( $marker, '|' );
7001                  $excerpt     = preg_replace( "|.*?\s(.{0,100}$preg_marker.{0,100})\s.*|s", '$1', $excerpt );
7002                  $excerpt     = strip_tags( $excerpt ); // YES, again, to remove the marker wrapper.
7003                  break;
7004              }
7005          }
7006  
7007          if ( empty( $context ) ) { // Link to target not found.
7008              return $this->pingback_error( 17, __( 'The source URL does not contain a link to the target URL, and so cannot be used as a source.' ) );
7009          }
7010  
7011          $pagelinkedfrom = str_replace( '&', '&amp;', $pagelinkedfrom );
7012  
7013          $context        = '[&#8230;] ' . esc_html( $excerpt ) . ' [&#8230;]';
7014          $pagelinkedfrom = $this->escape( $pagelinkedfrom );
7015  
7016          $comment_post_ID      = (int) $post_ID;
7017          $comment_author       = $title;
7018          $comment_author_email = '';
7019          $this->escape( $comment_author );
7020          $comment_author_url = $pagelinkedfrom;
7021          $comment_content    = $context;
7022          $this->escape( $comment_content );
7023          $comment_type = 'pingback';
7024  
7025          $commentdata = compact(
7026              'comment_post_ID',
7027              'comment_author',
7028              'comment_author_url',
7029              'comment_author_email',
7030              'comment_content',
7031              'comment_type',
7032              'remote_source',
7033              'remote_source_original'
7034          );
7035  
7036          $comment_ID = wp_new_comment( $commentdata );
7037  
7038          if ( is_wp_error( $comment_ID ) ) {
7039              return $this->pingback_error( 0, $comment_ID->get_error_message() );
7040          }
7041  
7042          /**
7043           * Fires after a post pingback has been sent.
7044           *
7045           * @since 0.71
7046           *
7047           * @param int $comment_ID Comment ID.
7048           */
7049          do_action( 'pingback_post', $comment_ID );
7050  
7051          /* translators: 1: URL of the page linked from, 2: URL of the page linked to. */
7052          return sprintf( __( 'Pingback from %1$s to %2$s registered. Keep the web talking! :-)' ), $pagelinkedfrom, $pagelinkedto );
7053      }
7054  
7055      /**
7056       * Retrieve array of URLs that pingbacked the given URL.
7057       *
7058       * Specs on http://www.aquarionics.com/misc/archives/blogite/0198.html
7059       *
7060       * @since 1.5.0
7061       *
7062       * @global wpdb $wpdb WordPress database abstraction object.
7063       *
7064       * @param string $url
7065       * @return array|IXR_Error
7066       */
7067  	public function pingback_extensions_getPingbacks( $url ) {
7068          global $wpdb;
7069  
7070          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
7071          do_action( 'xmlrpc_call', 'pingback.extensions.getPingbacks', $url, $this );
7072  
7073          $url = $this->escape( $url );
7074  
7075          $post_ID = url_to_postid( $url );
7076          if ( ! $post_ID ) {
7077              // We aren't sure that the resource is available and/or pingback enabled.
7078              return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either does not exist, or it is not a pingback-enabled resource.' ) );
7079          }
7080  
7081          $actual_post = get_post( $post_ID, ARRAY_A );
7082  
7083          if ( ! $actual_post ) {
7084              // No such post = resource not found.
7085              return $this->pingback_error( 32, __( 'The specified target URL does not exist.' ) );
7086          }
7087  
7088          $comments = $wpdb->get_results( $wpdb->prepare( "SELECT comment_author_url, comment_content, comment_author_IP, comment_type FROM $wpdb->comments WHERE comment_post_ID = %d", $post_ID ) );
7089  
7090          if ( ! $comments ) {
7091              return array();
7092          }
7093  
7094          $pingbacks = array();
7095          foreach ( $comments as $comment ) {
7096              if ( 'pingback' === $comment->comment_type ) {
7097                  $pingbacks[] = $comment->comment_author_url;
7098              }
7099          }
7100  
7101          return $pingbacks;
7102      }
7103  
7104      /**
7105       * Sends a pingback error based on the given error code and message.
7106       *
7107       * @since 3.6.0
7108       *
7109       * @param int    $code    Error code.
7110       * @param string $message Error message.
7111       * @return IXR_Error Error object.
7112       */
7113  	protected function pingback_error( $code, $message ) {
7114          /**
7115           * Filters the XML-RPC pingback error return.
7116           *
7117           * @since 3.5.1
7118           *
7119           * @param IXR_Error $error An IXR_Error object containing the error code and message.
7120           */
7121          return apply_filters( 'xmlrpc_pingback_error', new IXR_Error( $code, $message ) );
7122      }
7123  }


Generated: Sun Dec 22 01:00:02 2024 Cross-referenced by PHPXref 0.7.1