[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

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


Generated : Tue Jan 21 08:20:01 2025 Cross-referenced by PHPXref