[ 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              $dateCreated = rtrim( $post_data['post_date_gmt']->getIso(), 'Z' ) . 'Z';
1517          } elseif ( ! empty( $post_data['post_date'] ) ) {
1518              $dateCreated = $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( $dateCreated ) ) {
1525              $post_data['post_date']     = iso8601_to_datetime( $dateCreated );
1526              $post_data['post_date_gmt'] = iso8601_to_datetime( $dateCreated, '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 ( $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              $dateCreated                 = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
3841              $comment['comment_date']     = get_date_from_gmt( $dateCreated );
3842              $comment['comment_date_gmt'] = iso8601_to_datetime( $dateCreated, 'gmt' );
3843          }
3844  
3845          if ( isset( $content_struct['content'] ) ) {
3846              $comment['comment_content'] = $content_struct['content'];
3847          }
3848  
3849          if ( isset( $content_struct['author'] ) ) {
3850              $comment['comment_author'] = $content_struct['author'];
3851          }
3852  
3853          if ( isset( $content_struct['author_url'] ) ) {
3854              $comment['comment_author_url'] = $content_struct['author_url'];
3855          }
3856  
3857          if ( isset( $content_struct['author_email'] ) ) {
3858              $comment['comment_author_email'] = $content_struct['author_email'];
3859          }
3860  
3861          $result = wp_update_comment( $comment, true );
3862          if ( is_wp_error( $result ) ) {
3863              return new IXR_Error( 500, $result->get_error_message() );
3864          }
3865  
3866          if ( ! $result ) {
3867              return new IXR_Error( 500, __( 'Sorry, the comment could not be updated.' ) );
3868          }
3869  
3870          /**
3871           * Fires after a comment has been successfully updated via XML-RPC.
3872           *
3873           * @since 3.4.0
3874           *
3875           * @param int   $comment_id ID of the updated comment.
3876           * @param array $args       An array of arguments to update the comment.
3877           */
3878          do_action( 'xmlrpc_call_success_wp_editComment', $comment_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
3879  
3880          return true;
3881      }
3882  
3883      /**
3884       * Creates a new comment.
3885       *
3886       * @since 2.7.0
3887       *
3888       * @param array $args {
3889       *     Method arguments. Note: arguments must be ordered as documented.
3890       *
3891       *     @type int        $0 Blog ID (unused).
3892       *     @type string     $1 Username.
3893       *     @type string     $2 Password.
3894       *     @type string|int $3 Post ID or URL.
3895       *     @type array      $4 Content structure.
3896       * }
3897       * @return int|IXR_Error See wp_new_comment().
3898       */
3899  	public function wp_newComment( $args ) {
3900          $this->escape( $args );
3901  
3902          $username       = $args[1];
3903          $password       = $args[2];
3904          $post           = $args[3];
3905          $content_struct = $args[4];
3906  
3907          /**
3908           * Filters whether to allow anonymous comments over XML-RPC.
3909           *
3910           * @since 2.7.0
3911           *
3912           * @param bool $allow Whether to allow anonymous commenting via XML-RPC.
3913           *                    Default false.
3914           */
3915          $allow_anon = apply_filters( 'xmlrpc_allow_anonymous_comments', false );
3916  
3917          $user = $this->login( $username, $password );
3918  
3919          if ( ! $user ) {
3920              $logged_in = false;
3921              if ( $allow_anon && get_option( 'comment_registration' ) ) {
3922                  return new IXR_Error( 403, __( 'Sorry, you must be logged in to comment.' ) );
3923              } elseif ( ! $allow_anon ) {
3924                  return $this->error;
3925              }
3926          } else {
3927              $logged_in = true;
3928          }
3929  
3930          if ( is_numeric( $post ) ) {
3931              $post_id = absint( $post );
3932          } else {
3933              $post_id = url_to_postid( $post );
3934          }
3935  
3936          if ( ! $post_id ) {
3937              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
3938          }
3939  
3940          if ( ! get_post( $post_id ) ) {
3941              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
3942          }
3943  
3944          if ( ! comments_open( $post_id ) ) {
3945              return new IXR_Error( 403, __( 'Sorry, comments are closed for this item.' ) );
3946          }
3947  
3948          if (
3949              'publish' === get_post_status( $post_id ) &&
3950              ! current_user_can( 'edit_post', $post_id ) &&
3951              post_password_required( $post_id )
3952          ) {
3953              return new IXR_Error( 403, __( 'Sorry, you are not allowed to comment on this post.' ) );
3954          }
3955  
3956          if (
3957              'private' === get_post_status( $post_id ) &&
3958              ! current_user_can( 'read_post', $post_id )
3959          ) {
3960              return new IXR_Error( 403, __( 'Sorry, you are not allowed to comment on this post.' ) );
3961          }
3962  
3963          $comment = array(
3964              'comment_post_ID' => $post_id,
3965              'comment_content' => trim( $content_struct['content'] ),
3966          );
3967  
3968          if ( $logged_in ) {
3969              $display_name = $user->display_name;
3970              $user_email   = $user->user_email;
3971              $user_url     = $user->user_url;
3972  
3973              $comment['comment_author']       = $this->escape( $display_name );
3974              $comment['comment_author_email'] = $this->escape( $user_email );
3975              $comment['comment_author_url']   = $this->escape( $user_url );
3976              $comment['user_id']              = $user->ID;
3977          } else {
3978              $comment['comment_author'] = '';
3979              if ( isset( $content_struct['author'] ) ) {
3980                  $comment['comment_author'] = $content_struct['author'];
3981              }
3982  
3983              $comment['comment_author_email'] = '';
3984              if ( isset( $content_struct['author_email'] ) ) {
3985                  $comment['comment_author_email'] = $content_struct['author_email'];
3986              }
3987  
3988              $comment['comment_author_url'] = '';
3989              if ( isset( $content_struct['author_url'] ) ) {
3990                  $comment['comment_author_url'] = $content_struct['author_url'];
3991              }
3992  
3993              $comment['user_id'] = 0;
3994  
3995              if ( get_option( 'require_name_email' ) ) {
3996                  if ( strlen( $comment['comment_author_email'] ) < 6 || '' === $comment['comment_author'] ) {
3997                      return new IXR_Error( 403, __( 'Comment author name and email are required.' ) );
3998                  } elseif ( ! is_email( $comment['comment_author_email'] ) ) {
3999                      return new IXR_Error( 403, __( 'A valid email address is required.' ) );
4000                  }
4001              }
4002          }
4003  
4004          $comment['comment_parent'] = isset( $content_struct['comment_parent'] ) ? absint( $content_struct['comment_parent'] ) : 0;
4005  
4006          /** This filter is documented in wp-includes/comment.php */
4007          $allow_empty = apply_filters( 'allow_empty_comment', false, $comment );
4008  
4009          if ( ! $allow_empty && '' === $comment['comment_content'] ) {
4010              return new IXR_Error( 403, __( 'Comment is required.' ) );
4011          }
4012  
4013          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4014          do_action( 'xmlrpc_call', 'wp.newComment', $args, $this );
4015  
4016          $comment_id = wp_new_comment( $comment, true );
4017          if ( is_wp_error( $comment_id ) ) {
4018              return new IXR_Error( 403, $comment_id->get_error_message() );
4019          }
4020  
4021          if ( ! $comment_id ) {
4022              return new IXR_Error( 403, __( 'Something went wrong.' ) );
4023          }
4024  
4025          /**
4026           * Fires after a new comment has been successfully created via XML-RPC.
4027           *
4028           * @since 3.4.0
4029           *
4030           * @param int   $comment_id ID of the new comment.
4031           * @param array $args       An array of new comment arguments.
4032           */
4033          do_action( 'xmlrpc_call_success_wp_newComment', $comment_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
4034  
4035          return $comment_id;
4036      }
4037  
4038      /**
4039       * Retrieves all of the comment status.
4040       *
4041       * @since 2.7.0
4042       *
4043       * @param array $args {
4044       *     Method arguments. Note: arguments must be ordered as documented.
4045       *
4046       *     @type int    $0 Blog ID (unused).
4047       *     @type string $1 Username.
4048       *     @type string $2 Password.
4049       * }
4050       * @return array|IXR_Error
4051       */
4052  	public function wp_getCommentStatusList( $args ) {
4053          $this->escape( $args );
4054  
4055          $username = $args[1];
4056          $password = $args[2];
4057  
4058          $user = $this->login( $username, $password );
4059          if ( ! $user ) {
4060              return $this->error;
4061          }
4062  
4063          if ( ! current_user_can( 'publish_posts' ) ) {
4064              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4065          }
4066  
4067          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4068          do_action( 'xmlrpc_call', 'wp.getCommentStatusList', $args, $this );
4069  
4070          return get_comment_statuses();
4071      }
4072  
4073      /**
4074       * Retrieves comment counts.
4075       *
4076       * @since 2.5.0
4077       *
4078       * @param array $args {
4079       *     Method arguments. Note: arguments must be ordered as documented.
4080       *
4081       *     @type int    $0 Blog ID (unused).
4082       *     @type string $1 Username.
4083       *     @type string $2 Password.
4084       *     @type int    $3 Post ID.
4085       * }
4086       * @return array|IXR_Error
4087       */
4088  	public function wp_getCommentCount( $args ) {
4089          $this->escape( $args );
4090  
4091          $username = $args[1];
4092          $password = $args[2];
4093          $post_id  = (int) $args[3];
4094  
4095          $user = $this->login( $username, $password );
4096          if ( ! $user ) {
4097              return $this->error;
4098          }
4099  
4100          $post = get_post( $post_id, ARRAY_A );
4101          if ( empty( $post['ID'] ) ) {
4102              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4103          }
4104  
4105          if ( ! current_user_can( 'edit_post', $post_id ) ) {
4106              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details of this post.' ) );
4107          }
4108  
4109          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4110          do_action( 'xmlrpc_call', 'wp.getCommentCount', $args, $this );
4111  
4112          $count = wp_count_comments( $post_id );
4113  
4114          return array(
4115              'approved'            => $count->approved,
4116              'awaiting_moderation' => $count->moderated,
4117              'spam'                => $count->spam,
4118              'total_comments'      => $count->total_comments,
4119          );
4120      }
4121  
4122      /**
4123       * Retrieves post statuses.
4124       *
4125       * @since 2.5.0
4126       *
4127       * @param array $args {
4128       *     Method arguments. Note: arguments must be ordered as documented.
4129       *
4130       *     @type int    $0 Blog ID (unused).
4131       *     @type string $1 Username.
4132       *     @type string $2 Password.
4133       * }
4134       * @return array|IXR_Error
4135       */
4136  	public function wp_getPostStatusList( $args ) {
4137          $this->escape( $args );
4138  
4139          $username = $args[1];
4140          $password = $args[2];
4141  
4142          $user = $this->login( $username, $password );
4143          if ( ! $user ) {
4144              return $this->error;
4145          }
4146  
4147          if ( ! current_user_can( 'edit_posts' ) ) {
4148              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4149          }
4150  
4151          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4152          do_action( 'xmlrpc_call', 'wp.getPostStatusList', $args, $this );
4153  
4154          return get_post_statuses();
4155      }
4156  
4157      /**
4158       * Retrieves page statuses.
4159       *
4160       * @since 2.5.0
4161       *
4162       * @param array $args {
4163       *     Method arguments. Note: arguments must be ordered as documented.
4164       *
4165       *     @type int    $0 Blog ID (unused).
4166       *     @type string $1 Username.
4167       *     @type string $2 Password.
4168       * }
4169       * @return array|IXR_Error
4170       */
4171  	public function wp_getPageStatusList( $args ) {
4172          $this->escape( $args );
4173  
4174          $username = $args[1];
4175          $password = $args[2];
4176  
4177          $user = $this->login( $username, $password );
4178          if ( ! $user ) {
4179              return $this->error;
4180          }
4181  
4182          if ( ! current_user_can( 'edit_pages' ) ) {
4183              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4184          }
4185  
4186          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4187          do_action( 'xmlrpc_call', 'wp.getPageStatusList', $args, $this );
4188  
4189          return get_page_statuses();
4190      }
4191  
4192      /**
4193       * Retrieves page templates.
4194       *
4195       * @since 2.6.0
4196       *
4197       * @param array $args {
4198       *     Method arguments. Note: arguments must be ordered as documented.
4199       *
4200       *     @type int    $0 Blog ID (unused).
4201       *     @type string $1 Username.
4202       *     @type string $2 Password.
4203       * }
4204       * @return array|IXR_Error
4205       */
4206  	public function wp_getPageTemplates( $args ) {
4207          $this->escape( $args );
4208  
4209          $username = $args[1];
4210          $password = $args[2];
4211  
4212          $user = $this->login( $username, $password );
4213          if ( ! $user ) {
4214              return $this->error;
4215          }
4216  
4217          if ( ! current_user_can( 'edit_pages' ) ) {
4218              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4219          }
4220  
4221          $templates            = get_page_templates();
4222          $templates['Default'] = 'default';
4223  
4224          return $templates;
4225      }
4226  
4227      /**
4228       * Retrieves blog options.
4229       *
4230       * @since 2.6.0
4231       *
4232       * @param array $args {
4233       *     Method arguments. Note: arguments must be ordered as documented.
4234       *
4235       *     @type int    $0 Blog ID (unused).
4236       *     @type string $1 Username.
4237       *     @type string $2 Password.
4238       *     @type array  $3 Optional. Options.
4239       * }
4240       * @return array|IXR_Error
4241       */
4242  	public function wp_getOptions( $args ) {
4243          $this->escape( $args );
4244  
4245          $username = $args[1];
4246          $password = $args[2];
4247          $options  = isset( $args[3] ) ? (array) $args[3] : array();
4248  
4249          $user = $this->login( $username, $password );
4250          if ( ! $user ) {
4251              return $this->error;
4252          }
4253  
4254          // If no specific options where asked for, return all of them.
4255          if ( count( $options ) === 0 ) {
4256              $options = array_keys( $this->blog_options );
4257          }
4258  
4259          return $this->_getOptions( $options );
4260      }
4261  
4262      /**
4263       * Retrieves blog options value from list.
4264       *
4265       * @since 2.6.0
4266       *
4267       * @param array $options Options to retrieve.
4268       * @return array
4269       */
4270  	public function _getOptions( $options ) {
4271          $data       = array();
4272          $can_manage = current_user_can( 'manage_options' );
4273          foreach ( $options as $option ) {
4274              if ( array_key_exists( $option, $this->blog_options ) ) {
4275                  $data[ $option ] = $this->blog_options[ $option ];
4276                  // Is the value static or dynamic?
4277                  if ( isset( $data[ $option ]['option'] ) ) {
4278                      $data[ $option ]['value'] = get_option( $data[ $option ]['option'] );
4279                      unset( $data[ $option ]['option'] );
4280                  }
4281  
4282                  if ( ! $can_manage ) {
4283                      $data[ $option ]['readonly'] = true;
4284                  }
4285              }
4286          }
4287  
4288          return $data;
4289      }
4290  
4291      /**
4292       * Updates blog options.
4293       *
4294       * @since 2.6.0
4295       *
4296       * @param array $args {
4297       *     Method arguments. Note: arguments must be ordered as documented.
4298       *
4299       *     @type int    $0 Blog ID (unused).
4300       *     @type string $1 Username.
4301       *     @type string $2 Password.
4302       *     @type array  $3 Options.
4303       * }
4304       * @return array|IXR_Error
4305       */
4306  	public function wp_setOptions( $args ) {
4307          $this->escape( $args );
4308  
4309          $username = $args[1];
4310          $password = $args[2];
4311          $options  = (array) $args[3];
4312  
4313          $user = $this->login( $username, $password );
4314          if ( ! $user ) {
4315              return $this->error;
4316          }
4317  
4318          if ( ! current_user_can( 'manage_options' ) ) {
4319              return new IXR_Error( 403, __( 'Sorry, you are not allowed to update options.' ) );
4320          }
4321  
4322          $option_names = array();
4323          foreach ( $options as $o_name => $o_value ) {
4324              $option_names[] = $o_name;
4325              if ( ! array_key_exists( $o_name, $this->blog_options ) ) {
4326                  continue;
4327              }
4328  
4329              if ( true == $this->blog_options[ $o_name ]['readonly'] ) {
4330                  continue;
4331              }
4332  
4333              update_option( $this->blog_options[ $o_name ]['option'], wp_unslash( $o_value ) );
4334          }
4335  
4336          // Now return the updated values.
4337          return $this->_getOptions( $option_names );
4338      }
4339  
4340      /**
4341       * Retrieves a media item by ID.
4342       *
4343       * @since 3.1.0
4344       *
4345       * @param array $args {
4346       *     Method arguments. Note: arguments must be ordered as documented.
4347       *
4348       *     @type int    $0 Blog ID (unused).
4349       *     @type string $1 Username.
4350       *     @type string $2 Password.
4351       *     @type int    $3 Attachment ID.
4352       * }
4353       * @return array|IXR_Error Associative array contains:
4354       *  - 'date_created_gmt'
4355       *  - 'parent'
4356       *  - 'link'
4357       *  - 'thumbnail'
4358       *  - 'title'
4359       *  - 'caption'
4360       *  - 'description'
4361       *  - 'metadata'
4362       */
4363  	public function wp_getMediaItem( $args ) {
4364          $this->escape( $args );
4365  
4366          $username      = $args[1];
4367          $password      = $args[2];
4368          $attachment_id = (int) $args[3];
4369  
4370          $user = $this->login( $username, $password );
4371          if ( ! $user ) {
4372              return $this->error;
4373          }
4374  
4375          if ( ! current_user_can( 'upload_files' ) ) {
4376              return new IXR_Error( 403, __( 'Sorry, you are not allowed to upload files.' ) );
4377          }
4378  
4379          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4380          do_action( 'xmlrpc_call', 'wp.getMediaItem', $args, $this );
4381  
4382          $attachment = get_post( $attachment_id );
4383          if ( ! $attachment || 'attachment' !== $attachment->post_type ) {
4384              return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
4385          }
4386  
4387          return $this->_prepare_media_item( $attachment );
4388      }
4389  
4390      /**
4391       * Retrieves a collection of media library items (or attachments).
4392       *
4393       * Besides the common blog_id (unused), username, and password arguments,
4394       * it takes a filter array as the last argument.
4395       *
4396       * Accepted 'filter' keys are 'parent_id', 'mime_type', 'offset', and 'number'.
4397       *
4398       * The defaults are as follows:
4399       * - 'number'    - Default is 5. Total number of media items to retrieve.
4400       * - 'offset'    - Default is 0. See WP_Query::query() for more.
4401       * - 'parent_id' - Default is ''. The post where the media item is attached.
4402       *                 Empty string shows all media items. 0 shows unattached media items.
4403       * - 'mime_type' - Default is ''. Filter by mime type (e.g., 'image/jpeg', 'application/pdf')
4404       *
4405       * @since 3.1.0
4406       *
4407       * @param array $args {
4408       *     Method arguments. Note: arguments must be ordered as documented.
4409       *
4410       *     @type int    $0 Blog ID (unused).
4411       *     @type string $1 Username.
4412       *     @type string $2 Password.
4413       *     @type array  $3 Optional. Query arguments.
4414       * }
4415       * @return array|IXR_Error Array containing a collection of media items.
4416       *                         See wp_xmlrpc_server::wp_getMediaItem() for a description
4417       *                         of each item contents.
4418       */
4419  	public function wp_getMediaLibrary( $args ) {
4420          $this->escape( $args );
4421  
4422          $username = $args[1];
4423          $password = $args[2];
4424          $struct   = isset( $args[3] ) ? $args[3] : array();
4425  
4426          $user = $this->login( $username, $password );
4427          if ( ! $user ) {
4428              return $this->error;
4429          }
4430  
4431          if ( ! current_user_can( 'upload_files' ) ) {
4432              return new IXR_Error( 401, __( 'Sorry, you are not allowed to upload files.' ) );
4433          }
4434  
4435          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4436          do_action( 'xmlrpc_call', 'wp.getMediaLibrary', $args, $this );
4437  
4438          $parent_id = ( isset( $struct['parent_id'] ) ) ? absint( $struct['parent_id'] ) : '';
4439          $mime_type = ( isset( $struct['mime_type'] ) ) ? $struct['mime_type'] : '';
4440          $offset    = ( isset( $struct['offset'] ) ) ? absint( $struct['offset'] ) : 0;
4441          $number    = ( isset( $struct['number'] ) ) ? absint( $struct['number'] ) : -1;
4442  
4443          $attachments = get_posts(
4444              array(
4445                  'post_type'      => 'attachment',
4446                  'post_parent'    => $parent_id,
4447                  'offset'         => $offset,
4448                  'numberposts'    => $number,
4449                  'post_mime_type' => $mime_type,
4450              )
4451          );
4452  
4453          $attachments_struct = array();
4454  
4455          foreach ( $attachments as $attachment ) {
4456              $attachments_struct[] = $this->_prepare_media_item( $attachment );
4457          }
4458  
4459          return $attachments_struct;
4460      }
4461  
4462      /**
4463       * Retrieves a list of post formats used by the site.
4464       *
4465       * @since 3.1.0
4466       *
4467       * @param array $args {
4468       *     Method arguments. Note: arguments must be ordered as documented.
4469       *
4470       *     @type int    $0 Blog ID (unused).
4471       *     @type string $1 Username.
4472       *     @type string $2 Password.
4473       * }
4474       * @return array|IXR_Error List of post formats, otherwise IXR_Error object.
4475       */
4476  	public function wp_getPostFormats( $args ) {
4477          $this->escape( $args );
4478  
4479          $username = $args[1];
4480          $password = $args[2];
4481  
4482          $user = $this->login( $username, $password );
4483          if ( ! $user ) {
4484              return $this->error;
4485          }
4486  
4487          if ( ! current_user_can( 'edit_posts' ) ) {
4488              return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) );
4489          }
4490  
4491          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4492          do_action( 'xmlrpc_call', 'wp.getPostFormats', $args, $this );
4493  
4494          $formats = get_post_format_strings();
4495  
4496          // Find out if they want a list of currently supports formats.
4497          if ( isset( $args[3] ) && is_array( $args[3] ) ) {
4498              if ( $args[3]['show-supported'] ) {
4499                  if ( current_theme_supports( 'post-formats' ) ) {
4500                      $supported = get_theme_support( 'post-formats' );
4501  
4502                      $data              = array();
4503                      $data['all']       = $formats;
4504                      $data['supported'] = $supported[0];
4505  
4506                      $formats = $data;
4507                  }
4508              }
4509          }
4510  
4511          return $formats;
4512      }
4513  
4514      /**
4515       * Retrieves a post type.
4516       *
4517       * @since 3.4.0
4518       *
4519       * @see get_post_type_object()
4520       *
4521       * @param array $args {
4522       *     Method arguments. Note: arguments must be ordered as documented.
4523       *
4524       *     @type int    $0 Blog ID (unused).
4525       *     @type string $1 Username.
4526       *     @type string $2 Password.
4527       *     @type string $3 Post type name.
4528       *     @type array  $4 Optional. Fields to fetch.
4529       * }
4530       * @return array|IXR_Error Array contains:
4531       *  - 'labels'
4532       *  - 'description'
4533       *  - 'capability_type'
4534       *  - 'cap'
4535       *  - 'map_meta_cap'
4536       *  - 'hierarchical'
4537       *  - 'menu_position'
4538       *  - 'taxonomies'
4539       *  - 'supports'
4540       */
4541  	public function wp_getPostType( $args ) {
4542          if ( ! $this->minimum_args( $args, 4 ) ) {
4543              return $this->error;
4544          }
4545  
4546          $this->escape( $args );
4547  
4548          $username       = $args[1];
4549          $password       = $args[2];
4550          $post_type_name = $args[3];
4551  
4552          if ( isset( $args[4] ) ) {
4553              $fields = $args[4];
4554          } else {
4555              /**
4556               * Filters the default post type query fields used by the given XML-RPC method.
4557               *
4558               * @since 3.4.0
4559               *
4560               * @param array  $fields An array of post type fields to retrieve. By default,
4561               *                       contains 'labels', 'cap', and 'taxonomies'.
4562               * @param string $method The method name.
4563               */
4564              $fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostType' );
4565          }
4566  
4567          $user = $this->login( $username, $password );
4568          if ( ! $user ) {
4569              return $this->error;
4570          }
4571  
4572          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4573          do_action( 'xmlrpc_call', 'wp.getPostType', $args, $this );
4574  
4575          if ( ! post_type_exists( $post_type_name ) ) {
4576              return new IXR_Error( 403, __( 'Invalid post type.' ) );
4577          }
4578  
4579          $post_type = get_post_type_object( $post_type_name );
4580  
4581          if ( ! current_user_can( $post_type->cap->edit_posts ) ) {
4582              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type.' ) );
4583          }
4584  
4585          return $this->_prepare_post_type( $post_type, $fields );
4586      }
4587  
4588      /**
4589       * Retrieves post types.
4590       *
4591       * @since 3.4.0
4592       *
4593       * @see get_post_types()
4594       *
4595       * @param array $args {
4596       *     Method arguments. Note: arguments must be ordered as documented.
4597       *
4598       *     @type int    $0 Blog ID (unused).
4599       *     @type string $1 Username.
4600       *     @type string $2 Password.
4601       *     @type array  $3 Optional. Query arguments.
4602       *     @type array  $4 Optional. Fields to fetch.
4603       * }
4604       * @return array|IXR_Error
4605       */
4606  	public function wp_getPostTypes( $args ) {
4607          if ( ! $this->minimum_args( $args, 3 ) ) {
4608              return $this->error;
4609          }
4610  
4611          $this->escape( $args );
4612  
4613          $username = $args[1];
4614          $password = $args[2];
4615          $filter   = isset( $args[3] ) ? $args[3] : array( 'public' => true );
4616  
4617          if ( isset( $args[4] ) ) {
4618              $fields = $args[4];
4619          } else {
4620              /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4621              $fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostTypes' );
4622          }
4623  
4624          $user = $this->login( $username, $password );
4625          if ( ! $user ) {
4626              return $this->error;
4627          }
4628  
4629          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4630          do_action( 'xmlrpc_call', 'wp.getPostTypes', $args, $this );
4631  
4632          $post_types = get_post_types( $filter, 'objects' );
4633  
4634          $struct = array();
4635  
4636          foreach ( $post_types as $post_type ) {
4637              if ( ! current_user_can( $post_type->cap->edit_posts ) ) {
4638                  continue;
4639              }
4640  
4641              $struct[ $post_type->name ] = $this->_prepare_post_type( $post_type, $fields );
4642          }
4643  
4644          return $struct;
4645      }
4646  
4647      /**
4648       * Retrieves revisions for a specific post.
4649       *
4650       * @since 3.5.0
4651       *
4652       * The optional $fields parameter specifies what fields will be included
4653       * in the response array.
4654       *
4655       * @uses wp_get_post_revisions()
4656       * @see wp_getPost() for more on $fields
4657       *
4658       * @param array $args {
4659       *     Method arguments. Note: arguments must be ordered as documented.
4660       *
4661       *     @type int    $0 Blog ID (unused).
4662       *     @type string $1 Username.
4663       *     @type string $2 Password.
4664       *     @type int    $3 Post ID.
4665       *     @type array  $4 Optional. Fields to fetch.
4666       * }
4667       * @return array|IXR_Error Array containing a collection of posts.
4668       */
4669  	public function wp_getRevisions( $args ) {
4670          if ( ! $this->minimum_args( $args, 4 ) ) {
4671              return $this->error;
4672          }
4673  
4674          $this->escape( $args );
4675  
4676          $username = $args[1];
4677          $password = $args[2];
4678          $post_id  = (int) $args[3];
4679  
4680          if ( isset( $args[4] ) ) {
4681              $fields = $args[4];
4682          } else {
4683              /**
4684               * Filters the default revision query fields used by the given XML-RPC method.
4685               *
4686               * @since 3.5.0
4687               *
4688               * @param array  $field  An array of revision fields to retrieve. By default,
4689               *                       contains 'post_date' and 'post_date_gmt'.
4690               * @param string $method The method name.
4691               */
4692              $fields = apply_filters( 'xmlrpc_default_revision_fields', array( 'post_date', 'post_date_gmt' ), 'wp.getRevisions' );
4693          }
4694  
4695          $user = $this->login( $username, $password );
4696          if ( ! $user ) {
4697              return $this->error;
4698          }
4699  
4700          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4701          do_action( 'xmlrpc_call', 'wp.getRevisions', $args, $this );
4702  
4703          $post = get_post( $post_id );
4704          if ( ! $post ) {
4705              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4706          }
4707  
4708          if ( ! current_user_can( 'edit_post', $post_id ) ) {
4709              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
4710          }
4711  
4712          // Check if revisions are enabled.
4713          if ( ! wp_revisions_enabled( $post ) ) {
4714              return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) );
4715          }
4716  
4717          $revisions = wp_get_post_revisions( $post_id );
4718  
4719          if ( ! $revisions ) {
4720              return array();
4721          }
4722  
4723          $struct = array();
4724  
4725          foreach ( $revisions as $revision ) {
4726              if ( ! current_user_can( 'read_post', $revision->ID ) ) {
4727                  continue;
4728              }
4729  
4730              // Skip autosaves.
4731              if ( wp_is_post_autosave( $revision ) ) {
4732                  continue;
4733              }
4734  
4735              $struct[] = $this->_prepare_post( get_object_vars( $revision ), $fields );
4736          }
4737  
4738          return $struct;
4739      }
4740  
4741      /**
4742       * Restores a post revision.
4743       *
4744       * @since 3.5.0
4745       *
4746       * @uses wp_restore_post_revision()
4747       *
4748       * @param array $args {
4749       *     Method arguments. Note: arguments must be ordered as documented.
4750       *
4751       *     @type int    $0 Blog ID (unused).
4752       *     @type string $1 Username.
4753       *     @type string $2 Password.
4754       *     @type int    $3 Revision ID.
4755       * }
4756       * @return bool|IXR_Error false if there was an error restoring, true if success.
4757       */
4758  	public function wp_restoreRevision( $args ) {
4759          if ( ! $this->minimum_args( $args, 3 ) ) {
4760              return $this->error;
4761          }
4762  
4763          $this->escape( $args );
4764  
4765          $username    = $args[1];
4766          $password    = $args[2];
4767          $revision_id = (int) $args[3];
4768  
4769          $user = $this->login( $username, $password );
4770          if ( ! $user ) {
4771              return $this->error;
4772          }
4773  
4774          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4775          do_action( 'xmlrpc_call', 'wp.restoreRevision', $args, $this );
4776  
4777          $revision = wp_get_post_revision( $revision_id );
4778          if ( ! $revision ) {
4779              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4780          }
4781  
4782          if ( wp_is_post_autosave( $revision ) ) {
4783              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4784          }
4785  
4786          $post = get_post( $revision->post_parent );
4787          if ( ! $post ) {
4788              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4789          }
4790  
4791          if ( ! current_user_can( 'edit_post', $revision->post_parent ) ) {
4792              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
4793          }
4794  
4795          // Check if revisions are disabled.
4796          if ( ! wp_revisions_enabled( $post ) ) {
4797              return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) );
4798          }
4799  
4800          $post = wp_restore_post_revision( $revision_id );
4801  
4802          return (bool) $post;
4803      }
4804  
4805      /*
4806       * Blogger API functions.
4807       * Specs on http://plant.blogger.com/api and https://groups.yahoo.com/group/bloggerDev/
4808       */
4809  
4810      /**
4811       * Retrieves blogs that user owns.
4812       *
4813       * Will make more sense once we support multiple blogs.
4814       *
4815       * @since 1.5.0
4816       *
4817       * @param array $args {
4818       *     Method arguments. Note: arguments must be ordered as documented.
4819       *
4820       *     @type int    $0 Blog ID (unused).
4821       *     @type string $1 Username.
4822       *     @type string $2 Password.
4823       * }
4824       * @return array|IXR_Error
4825       */
4826  	public function blogger_getUsersBlogs( $args ) {
4827          if ( ! $this->minimum_args( $args, 3 ) ) {
4828              return $this->error;
4829          }
4830  
4831          if ( is_multisite() ) {
4832              return $this->_multisite_getUsersBlogs( $args );
4833          }
4834  
4835          $this->escape( $args );
4836  
4837          $username = $args[1];
4838          $password = $args[2];
4839  
4840          $user = $this->login( $username, $password );
4841          if ( ! $user ) {
4842              return $this->error;
4843          }
4844  
4845          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4846          do_action( 'xmlrpc_call', 'blogger.getUsersBlogs', $args, $this );
4847  
4848          $is_admin = current_user_can( 'manage_options' );
4849  
4850          $struct = array(
4851              'isAdmin'  => $is_admin,
4852              'url'      => get_option( 'home' ) . '/',
4853              'blogid'   => '1',
4854              'blogName' => get_option( 'blogname' ),
4855              'xmlrpc'   => site_url( 'xmlrpc.php', 'rpc' ),
4856          );
4857  
4858          return array( $struct );
4859      }
4860  
4861      /**
4862       * Private function for retrieving a users blogs for multisite setups.
4863       *
4864       * @since 3.0.0
4865       *
4866       * @param array $args {
4867       *     Method arguments. Note: arguments must be ordered as documented.
4868       *
4869       *     @type int    $0 Blog ID (unused).
4870       *     @type string $1 Username.
4871       *     @type string $2 Password.
4872       * }
4873       * @return array|IXR_Error
4874       */
4875  	protected function _multisite_getUsersBlogs( $args ) {
4876          $current_blog = get_site();
4877  
4878          $domain = $current_blog->domain;
4879          $path   = $current_blog->path . 'xmlrpc.php';
4880  
4881          $blogs = $this->wp_getUsersBlogs( $args );
4882          if ( $blogs instanceof IXR_Error ) {
4883              return $blogs;
4884          }
4885  
4886          if ( $_SERVER['HTTP_HOST'] == $domain && $_SERVER['REQUEST_URI'] == $path ) {
4887              return $blogs;
4888          } else {
4889              foreach ( (array) $blogs as $blog ) {
4890                  if ( str_contains( $blog['url'], $_SERVER['HTTP_HOST'] ) ) {
4891                      return array( $blog );
4892                  }
4893              }
4894              return array();
4895          }
4896      }
4897  
4898      /**
4899       * Retrieves user's data.
4900       *
4901       * Gives your client some info about you, so you don't have to.
4902       *
4903       * @since 1.5.0
4904       *
4905       * @param array $args {
4906       *     Method arguments. Note: arguments must be ordered as documented.
4907       *
4908       *     @type int    $0 Blog ID (unused).
4909       *     @type string $1 Username.
4910       *     @type string $2 Password.
4911       * }
4912       * @return array|IXR_Error
4913       */
4914  	public function blogger_getUserInfo( $args ) {
4915          $this->escape( $args );
4916  
4917          $username = $args[1];
4918          $password = $args[2];
4919  
4920          $user = $this->login( $username, $password );
4921          if ( ! $user ) {
4922              return $this->error;
4923          }
4924  
4925          if ( ! current_user_can( 'edit_posts' ) ) {
4926              return new IXR_Error( 401, __( 'Sorry, you are not allowed to access user data on this site.' ) );
4927          }
4928  
4929          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4930          do_action( 'xmlrpc_call', 'blogger.getUserInfo', $args, $this );
4931  
4932          $struct = array(
4933              'nickname'  => $user->nickname,
4934              'userid'    => $user->ID,
4935              'url'       => $user->user_url,
4936              'lastname'  => $user->last_name,
4937              'firstname' => $user->first_name,
4938          );
4939  
4940          return $struct;
4941      }
4942  
4943      /**
4944       * Retrieves a post.
4945       *
4946       * @since 1.5.0
4947       *
4948       * @param array $args {
4949       *     Method arguments. Note: arguments must be ordered as documented.
4950       *
4951       *     @type int    $0 Blog ID (unused).
4952       *     @type int    $1 Post ID.
4953       *     @type string $2 Username.
4954       *     @type string $3 Password.
4955       * }
4956       * @return array|IXR_Error
4957       */
4958  	public function blogger_getPost( $args ) {
4959          $this->escape( $args );
4960  
4961          $post_id  = (int) $args[1];
4962          $username = $args[2];
4963          $password = $args[3];
4964  
4965          $user = $this->login( $username, $password );
4966          if ( ! $user ) {
4967              return $this->error;
4968          }
4969  
4970          $post_data = get_post( $post_id, ARRAY_A );
4971          if ( ! $post_data ) {
4972              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4973          }
4974  
4975          if ( ! current_user_can( 'edit_post', $post_id ) ) {
4976              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
4977          }
4978  
4979          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4980          do_action( 'xmlrpc_call', 'blogger.getPost', $args, $this );
4981  
4982          $categories = implode( ',', wp_get_post_categories( $post_id ) );
4983  
4984          $content  = '<title>' . wp_unslash( $post_data['post_title'] ) . '</title>';
4985          $content .= '<category>' . $categories . '</category>';
4986          $content .= wp_unslash( $post_data['post_content'] );
4987  
4988          $struct = array(
4989              'userid'      => $post_data['post_author'],
4990              'dateCreated' => $this->_convert_date( $post_data['post_date'] ),
4991              'content'     => $content,
4992              'postid'      => (string) $post_data['ID'],
4993          );
4994  
4995          return $struct;
4996      }
4997  
4998      /**
4999       * Retrieves the list of recent posts.
5000       *
5001       * @since 1.5.0
5002       *
5003       * @param array $args {
5004       *     Method arguments. Note: arguments must be ordered as documented.
5005       *
5006       *     @type string $0 App key (unused).
5007       *     @type int    $1 Blog ID (unused).
5008       *     @type string $2 Username.
5009       *     @type string $3 Password.
5010       *     @type int    $4 Optional. Number of posts.
5011       * }
5012       * @return array|IXR_Error
5013       */
5014  	public function blogger_getRecentPosts( $args ) {
5015  
5016          $this->escape( $args );
5017  
5018          // $args[0] = appkey - ignored.
5019          $username = $args[2];
5020          $password = $args[3];
5021          if ( isset( $args[4] ) ) {
5022              $query = array( 'numberposts' => absint( $args[4] ) );
5023          } else {
5024              $query = array();
5025          }
5026  
5027          $user = $this->login( $username, $password );
5028          if ( ! $user ) {
5029              return $this->error;
5030          }
5031  
5032          if ( ! current_user_can( 'edit_posts' ) ) {
5033              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
5034          }
5035  
5036          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5037          do_action( 'xmlrpc_call', 'blogger.getRecentPosts', $args, $this );
5038  
5039          $posts_list = wp_get_recent_posts( $query );
5040  
5041          if ( ! $posts_list ) {
5042              $this->error = new IXR_Error( 500, __( 'Either there are no posts, or something went wrong.' ) );
5043              return $this->error;
5044          }
5045  
5046          $recent_posts = array();
5047          foreach ( $posts_list as $entry ) {
5048              if ( ! current_user_can( 'edit_post', $entry['ID'] ) ) {
5049                  continue;
5050              }
5051  
5052              $post_date  = $this->_convert_date( $entry['post_date'] );
5053              $categories = implode( ',', wp_get_post_categories( $entry['ID'] ) );
5054  
5055              $content  = '<title>' . wp_unslash( $entry['post_title'] ) . '</title>';
5056              $content .= '<category>' . $categories . '</category>';
5057              $content .= wp_unslash( $entry['post_content'] );
5058  
5059              $recent_posts[] = array(
5060                  'userid'      => $entry['post_author'],
5061                  'dateCreated' => $post_date,
5062                  'content'     => $content,
5063                  'postid'      => (string) $entry['ID'],
5064              );
5065          }
5066  
5067          return $recent_posts;
5068      }
5069  
5070      /**
5071       * Deprecated.
5072       *
5073       * @since 1.5.0
5074       * @deprecated 3.5.0
5075       *
5076       * @param array $args Unused.
5077       * @return IXR_Error Error object.
5078       */
5079  	public function blogger_getTemplate( $args ) {
5080          return new IXR_Error( 403, __( 'Sorry, this method is not supported.' ) );
5081      }
5082  
5083      /**
5084       * Deprecated.
5085       *
5086       * @since 1.5.0
5087       * @deprecated 3.5.0
5088       *
5089       * @param array $args Unused.
5090       * @return IXR_Error Error object.
5091       */
5092  	public function blogger_setTemplate( $args ) {
5093          return new IXR_Error( 403, __( 'Sorry, this method is not supported.' ) );
5094      }
5095  
5096      /**
5097       * Creates a new post.
5098       *
5099       * @since 1.5.0
5100       *
5101       * @param array $args {
5102       *     Method arguments. Note: arguments must be ordered as documented.
5103       *
5104       *     @type string $0 App key (unused).
5105       *     @type int    $1 Blog ID (unused).
5106       *     @type string $2 Username.
5107       *     @type string $3 Password.
5108       *     @type string $4 Content.
5109       *     @type int    $5 Publish flag. 0 for draft, 1 for publish.
5110       * }
5111       * @return int|IXR_Error
5112       */
5113  	public function blogger_newPost( $args ) {
5114          $this->escape( $args );
5115  
5116          $username = $args[2];
5117          $password = $args[3];
5118          $content  = $args[4];
5119          $publish  = $args[5];
5120  
5121          $user = $this->login( $username, $password );
5122          if ( ! $user ) {
5123              return $this->error;
5124          }
5125  
5126          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5127          do_action( 'xmlrpc_call', 'blogger.newPost', $args, $this );
5128  
5129          $cap = ( $publish ) ? 'publish_posts' : 'edit_posts';
5130          if ( ! current_user_can( get_post_type_object( 'post' )->cap->create_posts ) || ! current_user_can( $cap ) ) {
5131              return new IXR_Error( 401, __( 'Sorry, you are not allowed to post on this site.' ) );
5132          }
5133  
5134          $post_status = ( $publish ) ? 'publish' : 'draft';
5135  
5136          $post_author = $user->ID;
5137  
5138          $post_title    = xmlrpc_getposttitle( $content );
5139          $post_category = xmlrpc_getpostcategory( $content );
5140          $post_content  = xmlrpc_removepostdata( $content );
5141  
5142          $post_date     = current_time( 'mysql' );
5143          $post_date_gmt = current_time( 'mysql', 1 );
5144  
5145          $post_data = compact( 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status' );
5146  
5147          $post_id = wp_insert_post( $post_data );
5148          if ( is_wp_error( $post_id ) ) {
5149              return new IXR_Error( 500, $post_id->get_error_message() );
5150          }
5151  
5152          if ( ! $post_id ) {
5153              return new IXR_Error( 500, __( 'Sorry, the post could not be created.' ) );
5154          }
5155  
5156          $this->attach_uploads( $post_id, $post_content );
5157  
5158          /**
5159           * Fires after a new post has been successfully created via the XML-RPC Blogger API.
5160           *
5161           * @since 3.4.0
5162           *
5163           * @param int   $post_id ID of the new post.
5164           * @param array $args    An array of new post arguments.
5165           */
5166          do_action( 'xmlrpc_call_success_blogger_newPost', $post_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
5167  
5168          return $post_id;
5169      }
5170  
5171      /**
5172       * Edits a post.
5173       *
5174       * @since 1.5.0
5175       *
5176       * @param array $args {
5177       *     Method arguments. Note: arguments must be ordered as documented.
5178       *
5179       *     @type int    $0 Blog ID (unused).
5180       *     @type int    $1 Post ID.
5181       *     @type string $2 Username.
5182       *     @type string $3 Password.
5183       *     @type string $4 Content
5184       *     @type int    $5 Publish flag. 0 for draft, 1 for publish.
5185       * }
5186       * @return true|IXR_Error true when done.
5187       */
5188  	public function blogger_editPost( $args ) {
5189  
5190          $this->escape( $args );
5191  
5192          $post_id  = (int) $args[1];
5193          $username = $args[2];
5194          $password = $args[3];
5195          $content  = $args[4];
5196          $publish  = $args[5];
5197  
5198          $user = $this->login( $username, $password );
5199          if ( ! $user ) {
5200              return $this->error;
5201          }
5202  
5203          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5204          do_action( 'xmlrpc_call', 'blogger.editPost', $args, $this );
5205  
5206          $actual_post = get_post( $post_id, ARRAY_A );
5207  
5208          if ( ! $actual_post || 'post' !== $actual_post['post_type'] ) {
5209              return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
5210          }
5211  
5212          $this->escape( $actual_post );
5213  
5214          if ( ! current_user_can( 'edit_post', $post_id ) ) {
5215              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
5216          }
5217          if ( 'publish' === $actual_post['post_status'] && ! current_user_can( 'publish_posts' ) ) {
5218              return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) );
5219          }
5220  
5221          $postdata                  = array();
5222          $postdata['ID']            = $actual_post['ID'];
5223          $postdata['post_content']  = xmlrpc_removepostdata( $content );
5224          $postdata['post_title']    = xmlrpc_getposttitle( $content );
5225          $postdata['post_category'] = xmlrpc_getpostcategory( $content );
5226          $postdata['post_status']   = $actual_post['post_status'];
5227          $postdata['post_excerpt']  = $actual_post['post_excerpt'];
5228          $postdata['post_status']   = $publish ? 'publish' : 'draft';
5229  
5230          $result = wp_update_post( $postdata );
5231  
5232          if ( ! $result ) {
5233              return new IXR_Error( 500, __( 'Sorry, the post could not be updated.' ) );
5234          }
5235          $this->attach_uploads( $actual_post['ID'], $postdata['post_content'] );
5236  
5237          /**
5238           * Fires after a post has been successfully updated via the XML-RPC Blogger API.
5239           *
5240           * @since 3.4.0
5241           *
5242           * @param int   $post_id ID of the updated post.
5243           * @param array $args    An array of arguments for the post to edit.
5244           */
5245          do_action( 'xmlrpc_call_success_blogger_editPost', $post_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
5246  
5247          return true;
5248      }
5249  
5250      /**
5251       * Deletes a post.
5252       *
5253       * @since 1.5.0
5254       *
5255       * @param array $args {
5256       *     Method arguments. Note: arguments must be ordered as documented.
5257       *
5258       *     @type int    $0 Blog ID (unused).
5259       *     @type int    $1 Post ID.
5260       *     @type string $2 Username.
5261       *     @type string $3 Password.
5262       * }
5263       * @return true|IXR_Error True when post is deleted.
5264       */
5265  	public function blogger_deletePost( $args ) {
5266          $this->escape( $args );
5267  
5268          $post_id  = (int) $args[1];
5269          $username = $args[2];
5270          $password = $args[3];
5271  
5272          $user = $this->login( $username, $password );
5273          if ( ! $user ) {
5274              return $this->error;
5275          }
5276  
5277          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5278          do_action( 'xmlrpc_call', 'blogger.deletePost', $args, $this );
5279  
5280          $actual_post = get_post( $post_id, ARRAY_A );
5281  
5282          if ( ! $actual_post || 'post' !== $actual_post['post_type'] ) {
5283              return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
5284          }
5285  
5286          if ( ! current_user_can( 'delete_post', $post_id ) ) {
5287              return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this post.' ) );
5288          }
5289  
5290          $result = wp_delete_post( $post_id );
5291  
5292          if ( ! $result ) {
5293              return new IXR_Error( 500, __( 'Sorry, the post could not be deleted.' ) );
5294          }
5295  
5296          /**
5297           * Fires after a post has been successfully deleted via the XML-RPC Blogger API.
5298           *
5299           * @since 3.4.0
5300           *
5301           * @param int   $post_id ID of the deleted post.
5302           * @param array $args    An array of arguments to delete the post.
5303           */
5304          do_action( 'xmlrpc_call_success_blogger_deletePost', $post_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
5305  
5306          return true;
5307      }
5308  
5309      /*
5310       * MetaWeblog API functions.
5311       * Specs on wherever Dave Winer wants them to be.
5312       */
5313  
5314      /**
5315       * Creates a new post.
5316       *
5317       * The 'content_struct' argument must contain:
5318       *  - title
5319       *  - description
5320       *  - mt_excerpt
5321       *  - mt_text_more
5322       *  - mt_keywords
5323       *  - mt_tb_ping_urls
5324       *  - categories
5325       *
5326       * Also, it can optionally contain:
5327       *  - wp_slug
5328       *  - wp_password
5329       *  - wp_page_parent_id
5330       *  - wp_page_order
5331       *  - wp_author_id
5332       *  - post_status | page_status - can be 'draft', 'private', 'publish', or 'pending'
5333       *  - mt_allow_comments - can be 'open' or 'closed'
5334       *  - mt_allow_pings - can be 'open' or 'closed'
5335       *  - date_created_gmt
5336       *  - dateCreated
5337       *  - wp_post_thumbnail
5338       *
5339       * @since 1.5.0
5340       *
5341       * @param array $args {
5342       *     Method arguments. Note: arguments must be ordered as documented.
5343       *
5344       *     @type int    $0 Blog ID (unused).
5345       *     @type string $1 Username.
5346       *     @type string $2 Password.
5347       *     @type array  $3 Content structure.
5348       *     @type int    $4 Optional. Publish flag. 0 for draft, 1 for publish. Default 0.
5349       * }
5350       * @return int|IXR_Error
5351       */
5352  	public function mw_newPost( $args ) {
5353          $this->escape( $args );
5354  
5355          $username       = $args[1];
5356          $password       = $args[2];
5357          $content_struct = $args[3];
5358          $publish        = isset( $args[4] ) ? $args[4] : 0;
5359  
5360          $user = $this->login( $username, $password );
5361          if ( ! $user ) {
5362              return $this->error;
5363          }
5364  
5365          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5366          do_action( 'xmlrpc_call', 'metaWeblog.newPost', $args, $this );
5367  
5368          $page_template = '';
5369          if ( ! empty( $content_struct['post_type'] ) ) {
5370              if ( 'page' === $content_struct['post_type'] ) {
5371                  if ( $publish ) {
5372                      $cap = 'publish_pages';
5373                  } elseif ( isset( $content_struct['page_status'] ) && 'publish' === $content_struct['page_status'] ) {
5374                      $cap = 'publish_pages';
5375                  } else {
5376                      $cap = 'edit_pages';
5377                  }
5378                  $error_message = __( 'Sorry, you are not allowed to publish pages on this site.' );
5379                  $post_type     = 'page';
5380                  if ( ! empty( $content_struct['wp_page_template'] ) ) {
5381                      $page_template = $content_struct['wp_page_template'];
5382                  }
5383              } elseif ( 'post' === $content_struct['post_type'] ) {
5384                  if ( $publish ) {
5385                      $cap = 'publish_posts';
5386                  } elseif ( isset( $content_struct['post_status'] ) && 'publish' === $content_struct['post_status'] ) {
5387                      $cap = 'publish_posts';
5388                  } else {
5389                      $cap = 'edit_posts';
5390                  }
5391                  $error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
5392                  $post_type     = 'post';
5393              } else {
5394                  // No other 'post_type' values are allowed here.
5395                  return new IXR_Error( 401, __( 'Invalid post type.' ) );
5396              }
5397          } else {
5398              if ( $publish ) {
5399                  $cap = 'publish_posts';
5400              } elseif ( isset( $content_struct['post_status'] ) && 'publish' === $content_struct['post_status'] ) {
5401                  $cap = 'publish_posts';
5402              } else {
5403                  $cap = 'edit_posts';
5404              }
5405              $error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
5406              $post_type     = 'post';
5407          }
5408  
5409          if ( ! current_user_can( get_post_type_object( $post_type )->cap->create_posts ) ) {
5410              return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts on this site.' ) );
5411          }
5412          if ( ! current_user_can( $cap ) ) {
5413              return new IXR_Error( 401, $error_message );
5414          }
5415  
5416          // Check for a valid post format if one was given.
5417          if ( isset( $content_struct['wp_post_format'] ) ) {
5418              $content_struct['wp_post_format'] = sanitize_key( $content_struct['wp_post_format'] );
5419              if ( ! array_key_exists( $content_struct['wp_post_format'], get_post_format_strings() ) ) {
5420                  return new IXR_Error( 404, __( 'Invalid post format.' ) );
5421              }
5422          }
5423  
5424          // Let WordPress generate the 'post_name' (slug) unless
5425          // one has been provided.
5426          $post_name = null;
5427          if ( isset( $content_struct['wp_slug'] ) ) {
5428              $post_name = $content_struct['wp_slug'];
5429          }
5430  
5431          // Only use a password if one was given.
5432          $post_password = '';
5433          if ( isset( $content_struct['wp_password'] ) ) {
5434              $post_password = $content_struct['wp_password'];
5435          }
5436  
5437          // Only set a post parent if one was given.
5438          $post_parent = 0;
5439          if ( isset( $content_struct['wp_page_parent_id'] ) ) {
5440              $post_parent = $content_struct['wp_page_parent_id'];
5441          }
5442  
5443          // Only set the 'menu_order' if it was given.
5444          $menu_order = 0;
5445          if ( isset( $content_struct['wp_page_order'] ) ) {
5446              $menu_order = $content_struct['wp_page_order'];
5447          }
5448  
5449          $post_author = $user->ID;
5450  
5451          // If an author id was provided then use it instead.
5452          if ( isset( $content_struct['wp_author_id'] ) && ( $user->ID != $content_struct['wp_author_id'] ) ) {
5453              switch ( $post_type ) {
5454                  case 'post':
5455                      if ( ! current_user_can( 'edit_others_posts' ) ) {
5456                          return new IXR_Error( 401, __( 'Sorry, you are not allowed to create posts as this user.' ) );
5457                      }
5458                      break;
5459                  case 'page':
5460                      if ( ! current_user_can( 'edit_others_pages' ) ) {
5461                          return new IXR_Error( 401, __( 'Sorry, you are not allowed to create pages as this user.' ) );
5462                      }
5463                      break;
5464                  default:
5465                      return new IXR_Error( 401, __( 'Invalid post type.' ) );
5466              }
5467              $author = get_userdata( $content_struct['wp_author_id'] );
5468              if ( ! $author ) {
5469                  return new IXR_Error( 404, __( 'Invalid author ID.' ) );
5470              }
5471              $post_author = $content_struct['wp_author_id'];
5472          }
5473  
5474          $post_title   = isset( $content_struct['title'] ) ? $content_struct['title'] : '';
5475          $post_content = isset( $content_struct['description'] ) ? $content_struct['description'] : '';
5476  
5477          $post_status = $publish ? 'publish' : 'draft';
5478  
5479          if ( isset( $content_struct[ "{$post_type}_status" ] ) ) {
5480              switch ( $content_struct[ "{$post_type}_status" ] ) {
5481                  case 'draft':
5482                  case 'pending':
5483                  case 'private':
5484                  case 'publish':
5485                      $post_status = $content_struct[ "{$post_type}_status" ];
5486                      break;
5487                  default:
5488                      // Deliberably left empty.
5489                      break;
5490              }
5491          }
5492  
5493          $post_excerpt = isset( $content_struct['mt_excerpt'] ) ? $content_struct['mt_excerpt'] : '';
5494          $post_more    = isset( $content_struct['mt_text_more'] ) ? $content_struct['mt_text_more'] : '';
5495  
5496          $tags_input = isset( $content_struct['mt_keywords'] ) ? $content_struct['mt_keywords'] : array();
5497  
5498          if ( isset( $content_struct['mt_allow_comments'] ) ) {
5499              if ( ! is_numeric( $content_struct['mt_allow_comments'] ) ) {
5500                  switch ( $content_struct['mt_allow_comments'] ) {
5501                      case 'closed':
5502                          $comment_status = 'closed';
5503                          break;
5504                      case 'open':
5505                          $comment_status = 'open';
5506                          break;
5507                      default:
5508                          $comment_status = get_default_comment_status( $post_type );
5509                          break;
5510                  }
5511              } else {
5512                  switch ( (int) $content_struct['mt_allow_comments'] ) {
5513                      case 0:
5514                      case 2:
5515                          $comment_status = 'closed';
5516                          break;
5517                      case 1:
5518                          $comment_status = 'open';
5519                          break;
5520                      default:
5521                          $comment_status = get_default_comment_status( $post_type );
5522                          break;
5523                  }
5524              }
5525          } else {
5526              $comment_status = get_default_comment_status( $post_type );
5527          }
5528  
5529          if ( isset( $content_struct['mt_allow_pings'] ) ) {
5530              if ( ! is_numeric( $content_struct['mt_allow_pings'] ) ) {
5531                  switch ( $content_struct['mt_allow_pings'] ) {
5532                      case 'closed':
5533                          $ping_status = 'closed';
5534                          break;
5535                      case 'open':
5536                          $ping_status = 'open';
5537                          break;
5538                      default:
5539                          $ping_status = get_default_comment_status( $post_type, 'pingback' );
5540                          break;
5541                  }
5542              } else {
5543                  switch ( (int) $content_struct['mt_allow_pings'] ) {
5544                      case 0:
5545                          $ping_status = 'closed';
5546                          break;
5547                      case 1:
5548                          $ping_status = 'open';
5549                          break;
5550                      default:
5551                          $ping_status = get_default_comment_status( $post_type, 'pingback' );
5552                          break;
5553                  }
5554              }
5555          } else {
5556              $ping_status = get_default_comment_status( $post_type, 'pingback' );
5557          }
5558  
5559          if ( $post_more ) {
5560              $post_content .= '<!--more-->' . $post_more;
5561          }
5562  
5563          $to_ping = '';
5564          if ( isset( $content_struct['mt_tb_ping_urls'] ) ) {
5565              $to_ping = $content_struct['mt_tb_ping_urls'];
5566              if ( is_array( $to_ping ) ) {
5567                  $to_ping = implode( ' ', $to_ping );
5568              }
5569          }
5570  
5571          // Do some timestamp voodoo.
5572          if ( ! empty( $content_struct['date_created_gmt'] ) ) {
5573              // We know this is supposed to be GMT, so we're going to slap that Z on there by force.
5574              $dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
5575          } elseif ( ! empty( $content_struct['dateCreated'] ) ) {
5576              $dateCreated = $content_struct['dateCreated']->getIso();
5577          }
5578  
5579          $post_date     = '';
5580          $post_date_gmt = '';
5581          if ( ! empty( $dateCreated ) ) {
5582              $post_date     = iso8601_to_datetime( $dateCreated );
5583              $post_date_gmt = iso8601_to_datetime( $dateCreated, 'gmt' );
5584          }
5585  
5586          $post_category = array();
5587          if ( isset( $content_struct['categories'] ) ) {
5588              $catnames = $content_struct['categories'];
5589  
5590              if ( is_array( $catnames ) ) {
5591                  foreach ( $catnames as $cat ) {
5592                      $post_category[] = get_cat_ID( $cat );
5593                  }
5594              }
5595          }
5596  
5597          $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' );
5598  
5599          $post_id        = get_default_post_to_edit( $post_type, true )->ID;
5600          $postdata['ID'] = $post_id;
5601  
5602          // Only posts can be sticky.
5603          if ( 'post' === $post_type && isset( $content_struct['sticky'] ) ) {
5604              $data           = $postdata;
5605              $data['sticky'] = $content_struct['sticky'];
5606              $error          = $this->_toggle_sticky( $data );
5607              if ( $error ) {
5608                  return $error;
5609              }
5610          }
5611  
5612          if ( isset( $content_struct['custom_fields'] ) ) {
5613              $this->set_custom_fields( $post_id, $content_struct['custom_fields'] );
5614          }
5615  
5616          if ( isset( $content_struct['wp_post_thumbnail'] ) ) {
5617              if ( set_post_thumbnail( $post_id, $content_struct['wp_post_thumbnail'] ) === false ) {
5618                  return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
5619              }
5620  
5621              unset( $content_struct['wp_post_thumbnail'] );
5622          }
5623  
5624          // Handle enclosures.
5625          $thisEnclosure = isset( $content_struct['enclosure'] ) ? $content_struct['enclosure'] : null;
5626          $this->add_enclosure_if_new( $post_id, $thisEnclosure );
5627  
5628          $this->attach_uploads( $post_id, $post_content );
5629  
5630          /*
5631           * Handle post formats if assigned, value is validated earlier
5632           * in this function.
5633           */
5634          if ( isset( $content_struct['wp_post_format'] ) ) {
5635              set_post_format( $post_id, $content_struct['wp_post_format'] );
5636          }
5637  
5638          $post_id = wp_insert_post( $postdata, true );
5639          if ( is_wp_error( $post_id ) ) {
5640              return new IXR_Error( 500, $post_id->get_error_message() );
5641          }
5642  
5643          if ( ! $post_id ) {
5644              return new IXR_Error( 500, __( 'Sorry, the post could not be created.' ) );
5645          }
5646  
5647          /**
5648           * Fires after a new post has been successfully created via the XML-RPC MovableType API.
5649           *
5650           * @since 3.4.0
5651           *
5652           * @param int   $post_id ID of the new post.
5653           * @param array $args    An array of arguments to create the new post.
5654           */
5655          do_action( 'xmlrpc_call_success_mw_newPost', $post_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
5656  
5657          return (string) $post_id;
5658      }
5659  
5660      /**
5661       * Adds an enclosure to a post if it's new.
5662       *
5663       * @since 2.8.0
5664       *
5665       * @param int   $post_id   Post ID.
5666       * @param array $enclosure Enclosure data.
5667       */
5668  	public function add_enclosure_if_new( $post_id, $enclosure ) {
5669          if ( is_array( $enclosure ) && isset( $enclosure['url'] ) && isset( $enclosure['length'] ) && isset( $enclosure['type'] ) ) {
5670              $encstring  = $enclosure['url'] . "\n" . $enclosure['length'] . "\n" . $enclosure['type'] . "\n";
5671              $found      = false;
5672              $enclosures = get_post_meta( $post_id, 'enclosure' );
5673              if ( $enclosures ) {
5674                  foreach ( $enclosures as $enc ) {
5675                      // This method used to omit the trailing new line. #23219
5676                      if ( rtrim( $enc, "\n" ) === rtrim( $encstring, "\n" ) ) {
5677                          $found = true;
5678                          break;
5679                      }
5680                  }
5681              }
5682              if ( ! $found ) {
5683                  add_post_meta( $post_id, 'enclosure', $encstring );
5684              }
5685          }
5686      }
5687  
5688      /**
5689       * Attaches an upload to a post.
5690       *
5691       * @since 2.1.0
5692       *
5693       * @global wpdb $wpdb WordPress database abstraction object.
5694       *
5695       * @param int    $post_id      Post ID.
5696       * @param string $post_content Post Content for attachment.
5697       */
5698  	public function attach_uploads( $post_id, $post_content ) {
5699          global $wpdb;
5700  
5701          // Find any unattached files.
5702          $attachments = $wpdb->get_results( "SELECT ID, guid FROM {$wpdb->posts} WHERE post_parent = '0' AND post_type = 'attachment'" );
5703          if ( is_array( $attachments ) ) {
5704              foreach ( $attachments as $file ) {
5705                  if ( ! empty( $file->guid ) && str_contains( $post_content, $file->guid ) ) {
5706                      $wpdb->update( $wpdb->posts, array( 'post_parent' => $post_id ), array( 'ID' => $file->ID ) );
5707                  }
5708              }
5709          }
5710      }
5711  
5712      /**
5713       * Edits a post.
5714       *
5715       * @since 1.5.0
5716       *
5717       * @param array $args {
5718       *     Method arguments. Note: arguments must be ordered as documented.
5719       *
5720       *     @type int    $0 Post ID.
5721       *     @type string $1 Username.
5722       *     @type string $2 Password.
5723       *     @type array  $3 Content structure.
5724       *     @type int    $4 Optional. Publish flag. 0 for draft, 1 for publish. Default 0.
5725       * }
5726       * @return true|IXR_Error True on success.
5727       */
5728  	public function mw_editPost( $args ) {
5729          $this->escape( $args );
5730  
5731          $post_id        = (int) $args[0];
5732          $username       = $args[1];
5733          $password       = $args[2];
5734          $content_struct = $args[3];
5735          $publish        = isset( $args[4] ) ? $args[4] : 0;
5736  
5737          $user = $this->login( $username, $password );
5738          if ( ! $user ) {
5739              return $this->error;
5740          }
5741  
5742          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5743          do_action( 'xmlrpc_call', 'metaWeblog.editPost', $args, $this );
5744  
5745          $postdata = get_post( $post_id, ARRAY_A );
5746  
5747          /*
5748           * If there is no post data for the give post ID, stop now and return an error.
5749           * Otherwise a new post will be created (which was the old behavior).
5750           */
5751          if ( ! $postdata || empty( $postdata['ID'] ) ) {
5752              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
5753          }
5754  
5755          if ( ! current_user_can( 'edit_post', $post_id ) ) {
5756              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
5757          }
5758  
5759          // Use wp.editPost to edit post types other than post and page.
5760          if ( ! in_array( $postdata['post_type'], array( 'post', 'page' ), true ) ) {
5761              return new IXR_Error( 401, __( 'Invalid post type.' ) );
5762          }
5763  
5764          // Thwart attempt to change the post type.
5765          if ( ! empty( $content_struct['post_type'] ) && ( $content_struct['post_type'] != $postdata['post_type'] ) ) {
5766              return new IXR_Error( 401, __( 'The post type may not be changed.' ) );
5767          }
5768  
5769          // Check for a valid post format if one was given.
5770          if ( isset( $content_struct['wp_post_format'] ) ) {
5771              $content_struct['wp_post_format'] = sanitize_key( $content_struct['wp_post_format'] );
5772              if ( ! array_key_exists( $content_struct['wp_post_format'], get_post_format_strings() ) ) {
5773                  return new IXR_Error( 404, __( 'Invalid post format.' ) );
5774              }
5775          }
5776  
5777          $this->escape( $postdata );
5778  
5779          $ID             = $postdata['ID'];
5780          $post_content   = $postdata['post_content'];
5781          $post_title     = $postdata['post_title'];
5782          $post_excerpt   = $postdata['post_excerpt'];
5783          $post_password  = $postdata['post_password'];
5784          $post_parent    = $postdata['post_parent'];
5785          $post_type      = $postdata['post_type'];
5786          $menu_order     = $postdata['menu_order'];
5787          $ping_status    = $postdata['ping_status'];
5788          $comment_status = $postdata['comment_status'];
5789  
5790          // Let WordPress manage slug if none was provided.
5791          $post_name = $postdata['post_name'];
5792          if ( isset( $content_struct['wp_slug'] ) ) {
5793              $post_name = $content_struct['wp_slug'];
5794          }
5795  
5796          // Only use a password if one was given.
5797          if ( isset( $content_struct['wp_password'] ) ) {
5798              $post_password = $content_struct['wp_password'];
5799          }
5800  
5801          // Only set a post parent if one was given.
5802          if ( isset( $content_struct['wp_page_parent_id'] ) ) {
5803              $post_parent = $content_struct['wp_page_parent_id'];
5804          }
5805  
5806          // Only set the 'menu_order' if it was given.
5807          if ( isset( $content_struct['wp_page_order'] ) ) {
5808              $menu_order = $content_struct['wp_page_order'];
5809          }
5810  
5811          $page_template = '';
5812          if ( ! empty( $content_struct['wp_page_template'] ) && 'page' === $post_type ) {
5813              $page_template = $content_struct['wp_page_template'];
5814          }
5815  
5816          $post_author = $postdata['post_author'];
5817  
5818          // If an author id was provided then use it instead.
5819          if ( isset( $content_struct['wp_author_id'] ) ) {
5820              // Check permissions if attempting to switch author to or from another user.
5821              if ( $user->ID != $content_struct['wp_author_id'] || $user->ID != $post_author ) {
5822                  switch ( $post_type ) {
5823                      case 'post':
5824                          if ( ! current_user_can( 'edit_others_posts' ) ) {
5825                              return new IXR_Error( 401, __( 'Sorry, you are not allowed to change the post author as this user.' ) );
5826                          }
5827                          break;
5828                      case 'page':
5829                          if ( ! current_user_can( 'edit_others_pages' ) ) {
5830                              return new IXR_Error( 401, __( 'Sorry, you are not allowed to change the page author as this user.' ) );
5831                          }
5832                          break;
5833                      default:
5834                          return new IXR_Error( 401, __( 'Invalid post type.' ) );
5835                  }
5836                  $post_author = $content_struct['wp_author_id'];
5837              }
5838          }
5839  
5840          if ( isset( $content_struct['mt_allow_comments'] ) ) {
5841              if ( ! is_numeric( $content_struct['mt_allow_comments'] ) ) {
5842                  switch ( $content_struct['mt_allow_comments'] ) {
5843                      case 'closed':
5844                          $comment_status = 'closed';
5845                          break;
5846                      case 'open':
5847                          $comment_status = 'open';
5848                          break;
5849                      default:
5850                          $comment_status = get_default_comment_status( $post_type );
5851                          break;
5852                  }
5853              } else {
5854                  switch ( (int) $content_struct['mt_allow_comments'] ) {
5855                      case 0:
5856                      case 2:
5857                          $comment_status = 'closed';
5858                          break;
5859                      case 1:
5860                          $comment_status = 'open';
5861                          break;
5862                      default:
5863                          $comment_status = get_default_comment_status( $post_type );
5864                          break;
5865                  }
5866              }
5867          }
5868  
5869          if ( isset( $content_struct['mt_allow_pings'] ) ) {
5870              if ( ! is_numeric( $content_struct['mt_allow_pings'] ) ) {
5871                  switch ( $content_struct['mt_allow_pings'] ) {
5872                      case 'closed':
5873                          $ping_status = 'closed';
5874                          break;
5875                      case 'open':
5876                          $ping_status = 'open';
5877                          break;
5878                      default:
5879                          $ping_status = get_default_comment_status( $post_type, 'pingback' );
5880                          break;
5881                  }
5882              } else {
5883                  switch ( (int) $content_struct['mt_allow_pings'] ) {
5884                      case 0:
5885                          $ping_status = 'closed';
5886                          break;
5887                      case 1:
5888                          $ping_status = 'open';
5889                          break;
5890                      default:
5891                          $ping_status = get_default_comment_status( $post_type, 'pingback' );
5892                          break;
5893                  }
5894              }
5895          }
5896  
5897          if ( isset( $content_struct['title'] ) ) {
5898              $post_title = $content_struct['title'];
5899          }
5900  
5901          if ( isset( $content_struct['description'] ) ) {
5902              $post_content = $content_struct['description'];
5903          }
5904  
5905          $post_category = array();
5906          if ( isset( $content_struct['categories'] ) ) {
5907              $catnames = $content_struct['categories'];
5908              if ( is_array( $catnames ) ) {
5909                  foreach ( $catnames as $cat ) {
5910                      $post_category[] = get_cat_ID( $cat );
5911                  }
5912              }
5913          }
5914  
5915          if ( isset( $content_struct['mt_excerpt'] ) ) {
5916              $post_excerpt = $content_struct['mt_excerpt'];
5917          }
5918  
5919          $post_more = isset( $content_struct['mt_text_more'] ) ? $content_struct['mt_text_more'] : '';
5920  
5921          $post_status = $publish ? 'publish' : 'draft';
5922          if ( isset( $content_struct[ "{$post_type}_status" ] ) ) {
5923              switch ( $content_struct[ "{$post_type}_status" ] ) {
5924                  case 'draft':
5925                  case 'pending':
5926                  case 'private':
5927                  case 'publish':
5928                      $post_status = $content_struct[ "{$post_type}_status" ];
5929                      break;
5930                  default:
5931                      $post_status = $publish ? 'publish' : 'draft';
5932                      break;
5933              }
5934          }
5935  
5936          $tags_input = isset( $content_struct['mt_keywords'] ) ? $content_struct['mt_keywords'] : array();
5937  
5938          if ( 'publish' === $post_status || 'private' === $post_status ) {
5939              if ( 'page' === $post_type && ! current_user_can( 'publish_pages' ) ) {
5940                  return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this page.' ) );
5941              } elseif ( ! current_user_can( 'publish_posts' ) ) {
5942                  return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) );
5943              }
5944          }
5945  
5946          if ( $post_more ) {
5947              $post_content = $post_content . '<!--more-->' . $post_more;
5948          }
5949  
5950          $to_ping = '';
5951          if ( isset( $content_struct['mt_tb_ping_urls'] ) ) {
5952              $to_ping = $content_struct['mt_tb_ping_urls'];
5953              if ( is_array( $to_ping ) ) {
5954                  $to_ping = implode( ' ', $to_ping );
5955              }
5956          }
5957  
5958          // Do some timestamp voodoo.
5959          if ( ! empty( $content_struct['date_created_gmt'] ) ) {
5960              // We know this is supposed to be GMT, so we're going to slap that Z on there by force.
5961              $dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
5962          } elseif ( ! empty( $content_struct['dateCreated'] ) ) {
5963              $dateCreated = $content_struct['dateCreated']->getIso();
5964          }
5965  
5966          // Default to not flagging the post date to be edited unless it's intentional.
5967          $edit_date = false;
5968  
5969          if ( ! empty( $dateCreated ) ) {
5970              $post_date     = iso8601_to_datetime( $dateCreated );
5971              $post_date_gmt = iso8601_to_datetime( $dateCreated, 'gmt' );
5972  
5973              // Flag the post date to be edited.
5974              $edit_date = true;
5975          } else {
5976              $post_date     = $postdata['post_date'];
5977              $post_date_gmt = $postdata['post_date_gmt'];
5978          }
5979  
5980          // We've got all the data -- post it.
5981          $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' );
5982  
5983          $result = wp_update_post( $newpost, true );
5984          if ( is_wp_error( $result ) ) {
5985              return new IXR_Error( 500, $result->get_error_message() );
5986          }
5987  
5988          if ( ! $result ) {
5989              return new IXR_Error( 500, __( 'Sorry, the post could not be updated.' ) );
5990          }
5991  
5992          // Only posts can be sticky.
5993          if ( 'post' === $post_type && isset( $content_struct['sticky'] ) ) {
5994              $data              = $newpost;
5995              $data['sticky']    = $content_struct['sticky'];
5996              $data['post_type'] = 'post';
5997              $error             = $this->_toggle_sticky( $data, true );
5998              if ( $error ) {
5999                  return $error;
6000              }
6001          }
6002  
6003          if ( isset( $content_struct['custom_fields'] ) ) {
6004              $this->set_custom_fields( $post_id, $content_struct['custom_fields'] );
6005          }
6006  
6007          if ( isset( $content_struct['wp_post_thumbnail'] ) ) {
6008  
6009              // Empty value deletes, non-empty value adds/updates.
6010              if ( empty( $content_struct['wp_post_thumbnail'] ) ) {
6011                  delete_post_thumbnail( $post_id );
6012              } else {
6013                  if ( set_post_thumbnail( $post_id, $content_struct['wp_post_thumbnail'] ) === false ) {
6014                      return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
6015                  }
6016              }
6017              unset( $content_struct['wp_post_thumbnail'] );
6018          }
6019  
6020          // Handle enclosures.
6021          $thisEnclosure = isset( $content_struct['enclosure'] ) ? $content_struct['enclosure'] : null;
6022          $this->add_enclosure_if_new( $post_id, $thisEnclosure );
6023  
6024          $this->attach_uploads( $ID, $post_content );
6025  
6026          // Handle post formats if assigned, validation is handled earlier in this function.
6027          if ( isset( $content_struct['wp_post_format'] ) ) {
6028              set_post_format( $post_id, $content_struct['wp_post_format'] );
6029          }
6030  
6031          /**
6032           * Fires after a post has been successfully updated via the XML-RPC MovableType API.
6033           *
6034           * @since 3.4.0
6035           *
6036           * @param int   $post_id ID of the updated post.
6037           * @param array $args    An array of arguments to update the post.
6038           */
6039          do_action( 'xmlrpc_call_success_mw_editPost', $post_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
6040  
6041          return true;
6042      }
6043  
6044      /**
6045       * Retrieves a post.
6046       *
6047       * @since 1.5.0
6048       *
6049       * @param array $args {
6050       *     Method arguments. Note: arguments must be ordered as documented.
6051       *
6052       *     @type int    $0 Post ID.
6053       *     @type string $1 Username.
6054       *     @type string $2 Password.
6055       * }
6056       * @return array|IXR_Error
6057       */
6058  	public function mw_getPost( $args ) {
6059          $this->escape( $args );
6060  
6061          $post_id  = (int) $args[0];
6062          $username = $args[1];
6063          $password = $args[2];
6064  
6065          $user = $this->login( $username, $password );
6066          if ( ! $user ) {
6067              return $this->error;
6068          }
6069  
6070          $postdata = get_post( $post_id, ARRAY_A );
6071          if ( ! $postdata ) {
6072              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
6073          }
6074  
6075          if ( ! current_user_can( 'edit_post', $post_id ) ) {
6076              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
6077          }
6078  
6079          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6080          do_action( 'xmlrpc_call', 'metaWeblog.getPost', $args, $this );
6081  
6082          if ( '' !== $postdata['post_date'] ) {
6083              $post_date         = $this->_convert_date( $postdata['post_date'] );
6084              $post_date_gmt     = $this->_convert_date_gmt( $postdata['post_date_gmt'], $postdata['post_date'] );
6085              $post_modified     = $this->_convert_date( $postdata['post_modified'] );
6086              $post_modified_gmt = $this->_convert_date_gmt( $postdata['post_modified_gmt'], $postdata['post_modified'] );
6087  
6088              $categories = array();
6089              $catids     = wp_get_post_categories( $post_id );
6090              foreach ( $catids as $catid ) {
6091                  $categories[] = get_cat_name( $catid );
6092              }
6093  
6094              $tagnames = array();
6095              $tags     = wp_get_post_tags( $post_id );
6096              if ( ! empty( $tags ) ) {
6097                  foreach ( $tags as $tag ) {
6098                      $tagnames[] = $tag->name;
6099                  }
6100                  $tagnames = implode( ', ', $tagnames );
6101              } else {
6102                  $tagnames = '';
6103              }
6104  
6105              $post = get_extended( $postdata['post_content'] );
6106              $link = get_permalink( $postdata['ID'] );
6107  
6108              // Get the author info.
6109              $author = get_userdata( $postdata['post_author'] );
6110  
6111              $allow_comments = ( 'open' === $postdata['comment_status'] ) ? 1 : 0;
6112              $allow_pings    = ( 'open' === $postdata['ping_status'] ) ? 1 : 0;
6113  
6114              // Consider future posts as published.
6115              if ( 'future' === $postdata['post_status'] ) {
6116                  $postdata['post_status'] = 'publish';
6117              }
6118  
6119              // Get post format.
6120              $post_format = get_post_format( $post_id );
6121              if ( empty( $post_format ) ) {
6122                  $post_format = 'standard';
6123              }
6124  
6125              $sticky = false;
6126              if ( is_sticky( $post_id ) ) {
6127                  $sticky = true;
6128              }
6129  
6130              $enclosure = array();
6131              foreach ( (array) get_post_custom( $post_id ) as $key => $val ) {
6132                  if ( 'enclosure' === $key ) {
6133                      foreach ( (array) $val as $enc ) {
6134                          $encdata             = explode( "\n", $enc );
6135                          $enclosure['url']    = trim( htmlspecialchars( $encdata[0] ) );
6136                          $enclosure['length'] = (int) trim( $encdata[1] );
6137                          $enclosure['type']   = trim( $encdata[2] );
6138                          break 2;
6139                      }
6140                  }
6141              }
6142  
6143              $resp = array(
6144                  'dateCreated'            => $post_date,
6145                  'userid'                 => $postdata['post_author'],
6146                  'postid'                 => $postdata['ID'],
6147                  'description'            => $post['main'],
6148                  'title'                  => $postdata['post_title'],
6149                  'link'                   => $link,
6150                  'permaLink'              => $link,
6151                  // Commented out because no other tool seems to use this.
6152                  // 'content' => $entry['post_content'],
6153                  'categories'             => $categories,
6154                  'mt_excerpt'             => $postdata['post_excerpt'],
6155                  'mt_text_more'           => $post['extended'],
6156                  'wp_more_text'           => $post['more_text'],
6157                  'mt_allow_comments'      => $allow_comments,
6158                  'mt_allow_pings'         => $allow_pings,
6159                  'mt_keywords'            => $tagnames,
6160                  'wp_slug'                => $postdata['post_name'],
6161                  'wp_password'            => $postdata['post_password'],
6162                  'wp_author_id'           => (string) $author->ID,
6163                  'wp_author_display_name' => $author->display_name,
6164                  'date_created_gmt'       => $post_date_gmt,
6165                  'post_status'            => $postdata['post_status'],
6166                  'custom_fields'          => $this->get_custom_fields( $post_id ),
6167                  'wp_post_format'         => $post_format,
6168                  'sticky'                 => $sticky,
6169                  'date_modified'          => $post_modified,
6170                  'date_modified_gmt'      => $post_modified_gmt,
6171              );
6172  
6173              if ( ! empty( $enclosure ) ) {
6174                  $resp['enclosure'] = $enclosure;
6175              }
6176  
6177              $resp['wp_post_thumbnail'] = get_post_thumbnail_id( $postdata['ID'] );
6178  
6179              return $resp;
6180          } else {
6181              return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
6182          }
6183      }
6184  
6185      /**
6186       * Retrieves list of recent posts.
6187       *
6188       * @since 1.5.0
6189       *
6190       * @param array $args {
6191       *     Method arguments. Note: arguments must be ordered as documented.
6192       *
6193       *     @type int    $0 Blog ID (unused).
6194       *     @type string $1 Username.
6195       *     @type string $2 Password.
6196       *     @type int    $3 Optional. Number of posts.
6197       * }
6198       * @return array|IXR_Error
6199       */
6200  	public function mw_getRecentPosts( $args ) {
6201          $this->escape( $args );
6202  
6203          $username = $args[1];
6204          $password = $args[2];
6205          if ( isset( $args[3] ) ) {
6206              $query = array( 'numberposts' => absint( $args[3] ) );
6207          } else {
6208              $query = array();
6209          }
6210  
6211          $user = $this->login( $username, $password );
6212          if ( ! $user ) {
6213              return $this->error;
6214          }
6215  
6216          if ( ! current_user_can( 'edit_posts' ) ) {
6217              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
6218          }
6219  
6220          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6221          do_action( 'xmlrpc_call', 'metaWeblog.getRecentPosts', $args, $this );
6222  
6223          $posts_list = wp_get_recent_posts( $query );
6224  
6225          if ( ! $posts_list ) {
6226              return array();
6227          }
6228  
6229          $recent_posts = array();
6230          foreach ( $posts_list as $entry ) {
6231              if ( ! current_user_can( 'edit_post', $entry['ID'] ) ) {
6232                  continue;
6233              }
6234  
6235              $post_date         = $this->_convert_date( $entry['post_date'] );
6236              $post_date_gmt     = $this->_convert_date_gmt( $entry['post_date_gmt'], $entry['post_date'] );
6237              $post_modified     = $this->_convert_date( $entry['post_modified'] );
6238              $post_modified_gmt = $this->_convert_date_gmt( $entry['post_modified_gmt'], $entry['post_modified'] );
6239  
6240              $categories = array();
6241              $catids     = wp_get_post_categories( $entry['ID'] );
6242              foreach ( $catids as $catid ) {
6243                  $categories[] = get_cat_name( $catid );
6244              }
6245  
6246              $tagnames = array();
6247              $tags     = wp_get_post_tags( $entry['ID'] );
6248              if ( ! empty( $tags ) ) {
6249                  foreach ( $tags as $tag ) {
6250                      $tagnames[] = $tag->name;
6251                  }
6252                  $tagnames = implode( ', ', $tagnames );
6253              } else {
6254                  $tagnames = '';
6255              }
6256  
6257              $post = get_extended( $entry['post_content'] );
6258              $link = get_permalink( $entry['ID'] );
6259  
6260              // Get the post author info.
6261              $author = get_userdata( $entry['post_author'] );
6262  
6263              $allow_comments = ( 'open' === $entry['comment_status'] ) ? 1 : 0;
6264              $allow_pings    = ( 'open' === $entry['ping_status'] ) ? 1 : 0;
6265  
6266              // Consider future posts as published.
6267              if ( 'future' === $entry['post_status'] ) {
6268                  $entry['post_status'] = 'publish';
6269              }
6270  
6271              // Get post format.
6272              $post_format = get_post_format( $entry['ID'] );
6273              if ( empty( $post_format ) ) {
6274                  $post_format = 'standard';
6275              }
6276  
6277              $recent_posts[] = array(
6278                  'dateCreated'            => $post_date,
6279                  'userid'                 => $entry['post_author'],
6280                  'postid'                 => (string) $entry['ID'],
6281                  'description'            => $post['main'],
6282                  'title'                  => $entry['post_title'],
6283                  'link'                   => $link,
6284                  'permaLink'              => $link,
6285                  // Commented out because no other tool seems to use this.
6286                  // 'content' => $entry['post_content'],
6287                  'categories'             => $categories,
6288                  'mt_excerpt'             => $entry['post_excerpt'],
6289                  'mt_text_more'           => $post['extended'],
6290                  'wp_more_text'           => $post['more_text'],
6291                  'mt_allow_comments'      => $allow_comments,
6292                  'mt_allow_pings'         => $allow_pings,
6293                  'mt_keywords'            => $tagnames,
6294                  'wp_slug'                => $entry['post_name'],
6295                  'wp_password'            => $entry['post_password'],
6296                  'wp_author_id'           => (string) $author->ID,
6297                  'wp_author_display_name' => $author->display_name,
6298                  'date_created_gmt'       => $post_date_gmt,
6299                  'post_status'            => $entry['post_status'],
6300                  'custom_fields'          => $this->get_custom_fields( $entry['ID'] ),
6301                  'wp_post_format'         => $post_format,
6302                  'date_modified'          => $post_modified,
6303                  'date_modified_gmt'      => $post_modified_gmt,
6304                  'sticky'                 => ( 'post' === $entry['post_type'] && is_sticky( $entry['ID'] ) ),
6305                  'wp_post_thumbnail'      => get_post_thumbnail_id( $entry['ID'] ),
6306              );
6307          }
6308  
6309          return $recent_posts;
6310      }
6311  
6312      /**
6313       * Retrieves the list of categories on a given blog.
6314       *
6315       * @since 1.5.0
6316       *
6317       * @param array $args {
6318       *     Method arguments. Note: arguments must be ordered as documented.
6319       *
6320       *     @type int    $0 Blog ID (unused).
6321       *     @type string $1 Username.
6322       *     @type string $2 Password.
6323       * }
6324       * @return array|IXR_Error
6325       */
6326  	public function mw_getCategories( $args ) {
6327          $this->escape( $args );
6328  
6329          $username = $args[1];
6330          $password = $args[2];
6331  
6332          $user = $this->login( $username, $password );
6333          if ( ! $user ) {
6334              return $this->error;
6335          }
6336  
6337          if ( ! current_user_can( 'edit_posts' ) ) {
6338              return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
6339          }
6340  
6341          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6342          do_action( 'xmlrpc_call', 'metaWeblog.getCategories', $args, $this );
6343  
6344          $categories_struct = array();
6345  
6346          $cats = get_categories( array( 'get' => 'all' ) );
6347          if ( $cats ) {
6348              foreach ( $cats as $cat ) {
6349                  $struct                        = array();
6350                  $struct['categoryId']          = $cat->term_id;
6351                  $struct['parentId']            = $cat->parent;
6352                  $struct['description']         = $cat->name;
6353                  $struct['categoryDescription'] = $cat->description;
6354                  $struct['categoryName']        = $cat->name;
6355                  $struct['htmlUrl']             = esc_html( get_category_link( $cat->term_id ) );
6356                  $struct['rssUrl']              = esc_html( get_category_feed_link( $cat->term_id, 'rss2' ) );
6357  
6358                  $categories_struct[] = $struct;
6359              }
6360          }
6361  
6362          return $categories_struct;
6363      }
6364  
6365      /**
6366       * Uploads a file, following your settings.
6367       *
6368       * Adapted from a patch by Johann Richard.
6369       *
6370       * @link http://mycvs.org/archives/2004/06/30/file-upload-to-wordpress-in-ecto/
6371       *
6372       * @since 1.5.0
6373       *
6374       * @param array $args {
6375       *     Method arguments. Note: arguments must be ordered as documented.
6376       *
6377       *     @type int    $0 Blog ID (unused).
6378       *     @type string $1 Username.
6379       *     @type string $2 Password.
6380       *     @type array  $3 Data.
6381       * }
6382       * @return array|IXR_Error
6383       */
6384  	public function mw_newMediaObject( $args ) {
6385          $username = $this->escape( $args[1] );
6386          $password = $this->escape( $args[2] );
6387          $data     = $args[3];
6388  
6389          $name = sanitize_file_name( $data['name'] );
6390          $type = $data['type'];
6391          $bits = $data['bits'];
6392  
6393          $user = $this->login( $username, $password );
6394          if ( ! $user ) {
6395              return $this->error;
6396          }
6397  
6398          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6399          do_action( 'xmlrpc_call', 'metaWeblog.newMediaObject', $args, $this );
6400  
6401          if ( ! current_user_can( 'upload_files' ) ) {
6402              $this->error = new IXR_Error( 401, __( 'Sorry, you are not allowed to upload files.' ) );
6403              return $this->error;
6404          }
6405  
6406          if ( is_multisite() && upload_is_user_over_quota( false ) ) {
6407              $this->error = new IXR_Error(
6408                  401,
6409                  sprintf(
6410                      /* translators: %s: Allowed space allocation. */
6411                      __( 'Sorry, you have used your space allocation of %s. Please delete some files to upload more files.' ),
6412                      size_format( get_space_allowed() * MB_IN_BYTES )
6413                  )
6414              );
6415              return $this->error;
6416          }
6417  
6418          /**
6419           * Filters whether to preempt the XML-RPC media upload.
6420           *
6421           * Returning a truthy value will effectively short-circuit the media upload,
6422           * returning that value as a 500 error instead.
6423           *
6424           * @since 2.1.0
6425           *
6426           * @param bool $error Whether to pre-empt the media upload. Default false.
6427           */
6428          $upload_err = apply_filters( 'pre_upload_error', false );
6429          if ( $upload_err ) {
6430              return new IXR_Error( 500, $upload_err );
6431          }
6432  
6433          $upload = wp_upload_bits( $name, null, $bits );
6434          if ( ! empty( $upload['error'] ) ) {
6435              /* translators: 1: File name, 2: Error message. */
6436              $errorString = sprintf( __( 'Could not write file %1$s (%2$s).' ), $name, $upload['error'] );
6437              return new IXR_Error( 500, $errorString );
6438          }
6439          // Construct the attachment array.
6440          $post_id = 0;
6441          if ( ! empty( $data['post_id'] ) ) {
6442              $post_id = (int) $data['post_id'];
6443  
6444              if ( ! current_user_can( 'edit_post', $post_id ) ) {
6445                  return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
6446              }
6447          }
6448          $attachment = array(
6449              'post_title'     => $name,
6450              'post_content'   => '',
6451              'post_type'      => 'attachment',
6452              'post_parent'    => $post_id,
6453              'post_mime_type' => $type,
6454              'guid'           => $upload['url'],
6455          );
6456  
6457          // Save the data.
6458          $id = wp_insert_attachment( $attachment, $upload['file'], $post_id );
6459          wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $upload['file'] ) );
6460  
6461          /**
6462           * Fires after a new attachment has been added via the XML-RPC MovableType API.
6463           *
6464           * @since 3.4.0
6465           *
6466           * @param int   $id   ID of the new attachment.
6467           * @param array $args An array of arguments to add the attachment.
6468           */
6469          do_action( 'xmlrpc_call_success_mw_newMediaObject', $id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
6470  
6471          $struct = $this->_prepare_media_item( get_post( $id ) );
6472  
6473          // Deprecated values.
6474          $struct['id']   = $struct['attachment_id'];
6475          $struct['file'] = $struct['title'];
6476          $struct['url']  = $struct['link'];
6477  
6478          return $struct;
6479      }
6480  
6481      /*
6482       * MovableType API functions.
6483       * Specs archive on http://web.archive.org/web/20050220091302/http://www.movabletype.org:80/docs/mtmanual_programmatic.html
6484       */
6485  
6486      /**
6487       * Retrieves the post titles of recent posts.
6488       *
6489       * @since 1.5.0
6490       *
6491       * @param array $args {
6492       *     Method arguments. Note: arguments must be ordered as documented.
6493       *
6494       *     @type int    $0 Blog ID (unused).
6495       *     @type string $1 Username.
6496       *     @type string $2 Password.
6497       *     @type int    $3 Optional. Number of posts.
6498       * }
6499       * @return array|IXR_Error
6500       */
6501  	public function mt_getRecentPostTitles( $args ) {
6502          $this->escape( $args );
6503  
6504          $username = $args[1];
6505          $password = $args[2];
6506          if ( isset( $args[3] ) ) {
6507              $query = array( 'numberposts' => absint( $args[3] ) );
6508          } else {
6509              $query = array();
6510          }
6511  
6512          $user = $this->login( $username, $password );
6513          if ( ! $user ) {
6514              return $this->error;
6515          }
6516  
6517          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6518          do_action( 'xmlrpc_call', 'mt.getRecentPostTitles', $args, $this );
6519  
6520          $posts_list = wp_get_recent_posts( $query );
6521  
6522          if ( ! $posts_list ) {
6523              $this->error = new IXR_Error( 500, __( 'Either there are no posts, or something went wrong.' ) );
6524              return $this->error;
6525          }
6526  
6527          $recent_posts = array();
6528  
6529          foreach ( $posts_list as $entry ) {
6530              if ( ! current_user_can( 'edit_post', $entry['ID'] ) ) {
6531                  continue;
6532              }
6533  
6534              $post_date     = $this->_convert_date( $entry['post_date'] );
6535              $post_date_gmt = $this->_convert_date_gmt( $entry['post_date_gmt'], $entry['post_date'] );
6536  
6537              $recent_posts[] = array(
6538                  'dateCreated'      => $post_date,
6539                  'userid'           => $entry['post_author'],
6540                  'postid'           => (string) $entry['ID'],
6541                  'title'            => $entry['post_title'],
6542                  'post_status'      => $entry['post_status'],
6543                  'date_created_gmt' => $post_date_gmt,
6544              );
6545          }
6546  
6547          return $recent_posts;
6548      }
6549  
6550      /**
6551       * Retrieves the list of all categories on a blog.
6552       *
6553       * @since 1.5.0
6554       *
6555       * @param array $args {
6556       *     Method arguments. Note: arguments must be ordered as documented.
6557       *
6558       *     @type int    $0 Blog ID (unused).
6559       *     @type string $1 Username.
6560       *     @type string $2 Password.
6561       * }
6562       * @return array|IXR_Error
6563       */
6564  	public function mt_getCategoryList( $args ) {
6565          $this->escape( $args );
6566  
6567          $username = $args[1];
6568          $password = $args[2];
6569  
6570          $user = $this->login( $username, $password );
6571          if ( ! $user ) {
6572              return $this->error;
6573          }
6574  
6575          if ( ! current_user_can( 'edit_posts' ) ) {
6576              return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
6577          }
6578  
6579          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6580          do_action( 'xmlrpc_call', 'mt.getCategoryList', $args, $this );
6581  
6582          $categories_struct = array();
6583  
6584          $cats = get_categories(
6585              array(
6586                  'hide_empty'   => 0,
6587                  'hierarchical' => 0,
6588              )
6589          );
6590          if ( $cats ) {
6591              foreach ( $cats as $cat ) {
6592                  $struct                 = array();
6593                  $struct['categoryId']   = $cat->term_id;
6594                  $struct['categoryName'] = $cat->name;
6595  
6596                  $categories_struct[] = $struct;
6597              }
6598          }
6599  
6600          return $categories_struct;
6601      }
6602  
6603      /**
6604       * Retrieves post categories.
6605       *
6606       * @since 1.5.0
6607       *
6608       * @param array $args {
6609       *     Method arguments. Note: arguments must be ordered as documented.
6610       *
6611       *     @type int    $0 Post ID.
6612       *     @type string $1 Username.
6613       *     @type string $2 Password.
6614       * }
6615       * @return array|IXR_Error
6616       */
6617  	public function mt_getPostCategories( $args ) {
6618          $this->escape( $args );
6619  
6620          $post_id  = (int) $args[0];
6621          $username = $args[1];
6622          $password = $args[2];
6623  
6624          $user = $this->login( $username, $password );
6625          if ( ! $user ) {
6626              return $this->error;
6627          }
6628  
6629          if ( ! get_post( $post_id ) ) {
6630              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
6631          }
6632  
6633          if ( ! current_user_can( 'edit_post', $post_id ) ) {
6634              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
6635          }
6636  
6637          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6638          do_action( 'xmlrpc_call', 'mt.getPostCategories', $args, $this );
6639  
6640          $categories = array();
6641          $catids     = wp_get_post_categories( (int) $post_id );
6642          // First listed category will be the primary category.
6643          $isPrimary = true;
6644          foreach ( $catids as $catid ) {
6645              $categories[] = array(
6646                  'categoryName' => get_cat_name( $catid ),
6647                  'categoryId'   => (string) $catid,
6648                  'isPrimary'    => $isPrimary,
6649              );
6650              $isPrimary    = false;
6651          }
6652  
6653          return $categories;
6654      }
6655  
6656      /**
6657       * Sets categories for a post.
6658       *
6659       * @since 1.5.0
6660       *
6661       * @param array $args {
6662       *     Method arguments. Note: arguments must be ordered as documented.
6663       *
6664       *     @type int    $0 Post ID.
6665       *     @type string $1 Username.
6666       *     @type string $2 Password.
6667       *     @type array  $3 Categories.
6668       * }
6669       * @return true|IXR_Error True on success.
6670       */
6671  	public function mt_setPostCategories( $args ) {
6672          $this->escape( $args );
6673  
6674          $post_id    = (int) $args[0];
6675          $username   = $args[1];
6676          $password   = $args[2];
6677          $categories = $args[3];
6678  
6679          $user = $this->login( $username, $password );
6680          if ( ! $user ) {
6681              return $this->error;
6682          }
6683  
6684          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6685          do_action( 'xmlrpc_call', 'mt.setPostCategories', $args, $this );
6686  
6687          if ( ! get_post( $post_id ) ) {
6688              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
6689          }
6690  
6691          if ( ! current_user_can( 'edit_post', $post_id ) ) {
6692              return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
6693          }
6694  
6695          $catids = array();
6696          foreach ( $categories as $cat ) {
6697              $catids[] = $cat['categoryId'];
6698          }
6699  
6700          wp_set_post_categories( $post_id, $catids );
6701  
6702          return true;
6703      }
6704  
6705      /**
6706       * Retrieves an array of methods supported by this server.
6707       *
6708       * @since 1.5.0
6709       *
6710       * @return array
6711       */
6712  	public function mt_supportedMethods() {
6713          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6714          do_action( 'xmlrpc_call', 'mt.supportedMethods', array(), $this );
6715  
6716          return array_keys( $this->methods );
6717      }
6718  
6719      /**
6720       * Retrieves an empty array because we don't support per-post text filters.
6721       *
6722       * @since 1.5.0
6723       */
6724  	public function mt_supportedTextFilters() {
6725          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6726          do_action( 'xmlrpc_call', 'mt.supportedTextFilters', array(), $this );
6727  
6728          /**
6729           * Filters the MoveableType text filters list for XML-RPC.
6730           *
6731           * @since 2.2.0
6732           *
6733           * @param array $filters An array of text filters.
6734           */
6735          return apply_filters( 'xmlrpc_text_filters', array() );
6736      }
6737  
6738      /**
6739       * Retrieves trackbacks sent to a given post.
6740       *
6741       * @since 1.5.0
6742       *
6743       * @global wpdb $wpdb WordPress database abstraction object.
6744       *
6745       * @param int $post_id
6746       * @return array|IXR_Error
6747       */
6748  	public function mt_getTrackbackPings( $post_id ) {
6749          global $wpdb;
6750  
6751          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6752          do_action( 'xmlrpc_call', 'mt.getTrackbackPings', $post_id, $this );
6753  
6754          $actual_post = get_post( $post_id, ARRAY_A );
6755  
6756          if ( ! $actual_post ) {
6757              return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
6758          }
6759  
6760          $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 ) );
6761  
6762          if ( ! $comments ) {
6763              return array();
6764          }
6765  
6766          $trackback_pings = array();
6767          foreach ( $comments as $comment ) {
6768              if ( 'trackback' === $comment->comment_type ) {
6769                  $content           = $comment->comment_content;
6770                  $title             = substr( $content, 8, ( strpos( $content, '</strong>' ) - 8 ) );
6771                  $trackback_pings[] = array(
6772                      'pingTitle' => $title,
6773                      'pingURL'   => $comment->comment_author_url,
6774                      'pingIP'    => $comment->comment_author_IP,
6775                  );
6776              }
6777          }
6778  
6779          return $trackback_pings;
6780      }
6781  
6782      /**
6783       * Sets a post's publish status to 'publish'.
6784       *
6785       * @since 1.5.0
6786       *
6787       * @param array $args {
6788       *     Method arguments. Note: arguments must be ordered as documented.
6789       *
6790       *     @type int    $0 Post ID.
6791       *     @type string $1 Username.
6792       *     @type string $2 Password.
6793       * }
6794       * @return int|IXR_Error
6795       */
6796  	public function mt_publishPost( $args ) {
6797          $this->escape( $args );
6798  
6799          $post_id  = (int) $args[0];
6800          $username = $args[1];
6801          $password = $args[2];
6802  
6803          $user = $this->login( $username, $password );
6804          if ( ! $user ) {
6805              return $this->error;
6806          }
6807  
6808          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6809          do_action( 'xmlrpc_call', 'mt.publishPost', $args, $this );
6810  
6811          $postdata = get_post( $post_id, ARRAY_A );
6812          if ( ! $postdata ) {
6813              return new IXR_Error( 404, __( 'Invalid post ID.' ) );
6814          }
6815  
6816          if ( ! current_user_can( 'publish_posts' ) || ! current_user_can( 'edit_post', $post_id ) ) {
6817              return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) );
6818          }
6819  
6820          $postdata['post_status'] = 'publish';
6821  
6822          // Retain old categories.
6823          $postdata['post_category'] = wp_get_post_categories( $post_id );
6824          $this->escape( $postdata );
6825  
6826          return wp_update_post( $postdata );
6827      }
6828  
6829      /*
6830       * Pingback functions.
6831       * Specs on www.hixie.ch/specs/pingback/pingback
6832       */
6833  
6834      /**
6835       * Retrieves a pingback and registers it.
6836       *
6837       * @since 1.5.0
6838       *
6839       * @global wpdb $wpdb WordPress database abstraction object.
6840       *
6841       * @param array $args {
6842       *     Method arguments. Note: arguments must be ordered as documented.
6843       *
6844       *     @type string $0 URL of page linked from.
6845       *     @type string $1 URL of page linked to.
6846       * }
6847       * @return string|IXR_Error
6848       */
6849  	public function pingback_ping( $args ) {
6850          global $wpdb;
6851  
6852          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6853          do_action( 'xmlrpc_call', 'pingback.ping', $args, $this );
6854  
6855          $this->escape( $args );
6856  
6857          $pagelinkedfrom = str_replace( '&amp;', '&', $args[0] );
6858          $pagelinkedto   = str_replace( '&amp;', '&', $args[1] );
6859          $pagelinkedto   = str_replace( '&', '&amp;', $pagelinkedto );
6860  
6861          /**
6862           * Filters the pingback source URI.
6863           *
6864           * @since 3.6.0
6865           *
6866           * @param string $pagelinkedfrom URI of the page linked from.
6867           * @param string $pagelinkedto   URI of the page linked to.
6868           */
6869          $pagelinkedfrom = apply_filters( 'pingback_ping_source_uri', $pagelinkedfrom, $pagelinkedto );
6870  
6871          if ( ! $pagelinkedfrom ) {
6872              return $this->pingback_error( 0, __( 'A valid URL was not provided.' ) );
6873          }
6874  
6875          // Check if the page linked to is on our site.
6876          $pos1 = strpos( $pagelinkedto, str_replace( array( 'http://www.', 'http://', 'https://www.', 'https://' ), '', get_option( 'home' ) ) );
6877          if ( ! $pos1 ) {
6878              return $this->pingback_error( 0, __( 'Is there no link to us?' ) );
6879          }
6880  
6881          /*
6882           * Let's find which post is linked to.
6883           * FIXME: Does url_to_postid() cover all these cases already?
6884           * If so, then let's use it and drop the old code.
6885           */
6886          $urltest = parse_url( $pagelinkedto );
6887          $post_id = url_to_postid( $pagelinkedto );
6888          if ( $post_id ) {
6889              // $way
6890          } elseif ( isset( $urltest['path'] ) && preg_match( '#p/[0-9]{1,}#', $urltest['path'], $match ) ) {
6891              // The path defines the post_ID (archives/p/XXXX).
6892              $blah    = explode( '/', $match[0] );
6893              $post_id = (int) $blah[1];
6894          } elseif ( isset( $urltest['query'] ) && preg_match( '#p=[0-9]{1,}#', $urltest['query'], $match ) ) {
6895              // The query string defines the post_ID (?p=XXXX).
6896              $blah    = explode( '=', $match[0] );
6897              $post_id = (int) $blah[1];
6898          } elseif ( isset( $urltest['fragment'] ) ) {
6899              // An #anchor is there, it's either...
6900              if ( (int) $urltest['fragment'] ) {
6901                  // ...an integer #XXXX (simplest case),
6902                  $post_id = (int) $urltest['fragment'];
6903              } elseif ( preg_match( '/post-[0-9]+/', $urltest['fragment'] ) ) {
6904                  // ...a post ID in the form 'post-###',
6905                  $post_id = preg_replace( '/[^0-9]+/', '', $urltest['fragment'] );
6906              } elseif ( is_string( $urltest['fragment'] ) ) {
6907                  // ...or a string #title, a little more complicated.
6908                  $title   = preg_replace( '/[^a-z0-9]/i', '.', $urltest['fragment'] );
6909                  $sql     = $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_title RLIKE %s", $title );
6910                  $post_id = $wpdb->get_var( $sql );
6911                  if ( ! $post_id ) {
6912                      // Returning unknown error '0' is better than die()'ing.
6913                      return $this->pingback_error( 0, '' );
6914                  }
6915              }
6916          } else {
6917              // TODO: Attempt to extract a post ID from the given URL.
6918              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.' ) );
6919          }
6920          $post_id = (int) $post_id;
6921  
6922          $post = get_post( $post_id );
6923  
6924          if ( ! $post ) { // Post not found.
6925              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.' ) );
6926          }
6927  
6928          if ( url_to_postid( $pagelinkedfrom ) == $post_id ) {
6929              return $this->pingback_error( 0, __( 'The source URL and the target URL cannot both point to the same resource.' ) );
6930          }
6931  
6932          // Check if pings are on.
6933          if ( ! pings_open( $post ) ) {
6934              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.' ) );
6935          }
6936  
6937          // Let's check that the remote site didn't already pingback this entry.
6938          if ( $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_author_url = %s", $post_id, $pagelinkedfrom ) ) ) {
6939              return $this->pingback_error( 48, __( 'The pingback has already been registered.' ) );
6940          }
6941  
6942          /*
6943           * The remote site may have sent the pingback before it finished publishing its own content
6944           * containing this pingback URL. If that happens then it won't be immediately possible to fetch
6945           * the pinging post; adding a small delay reduces the likelihood of this happening.
6946           *
6947           * While there are more robust methods than calling `sleep()` here (because `sleep()` merely
6948           * mitigates the risk of requesting the remote post before it's available), this is effective
6949           * enough for most cases and avoids introducing more complexity into this code.
6950           *
6951           * One way to improve the reliability of this code might be to add failure-handling to the remote
6952           * fetch and retry up to a set number of times if it receives a 404. This could also handle 401 and
6953           * 403 responses to differentiate the "does not exist" failure from the "may not access" failure.
6954           */
6955          sleep( 1 );
6956  
6957          $remote_ip = preg_replace( '/[^0-9a-fA-F:., ]/', '', $_SERVER['REMOTE_ADDR'] );
6958  
6959          /** This filter is documented in wp-includes/class-wp-http.php */
6960          $user_agent = apply_filters( 'http_headers_useragent', 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ), $pagelinkedfrom );
6961  
6962          // Let's check the remote site.
6963          $http_api_args = array(
6964              'timeout'             => 10,
6965              'redirection'         => 0,
6966              'limit_response_size' => 153600, // 150 KB
6967              'user-agent'          => "$user_agent; verifying pingback from $remote_ip",
6968              'headers'             => array(
6969                  'X-Pingback-Forwarded-For' => $remote_ip,
6970              ),
6971          );
6972  
6973          $request                = wp_safe_remote_get( $pagelinkedfrom, $http_api_args );
6974          $remote_source          = wp_remote_retrieve_body( $request );
6975          $remote_source_original = $remote_source;
6976  
6977          if ( ! $remote_source ) {
6978              return $this->pingback_error( 16, __( 'The source URL does not exist.' ) );
6979          }
6980  
6981          /**
6982           * Filters the pingback remote source.
6983           *
6984           * @since 2.5.0
6985           *
6986           * @param string $remote_source Response source for the page linked from.
6987           * @param string $pagelinkedto  URL of the page linked to.
6988           */
6989          $remote_source = apply_filters( 'pre_remote_source', $remote_source, $pagelinkedto );
6990  
6991          // Work around bug in strip_tags():
6992          $remote_source = str_replace( '<!DOC', '<DOC', $remote_source );
6993          $remote_source = preg_replace( '/[\r\n\t ]+/', ' ', $remote_source ); // normalize spaces
6994          $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 );
6995  
6996          preg_match( '|<title>([^<]*?)</title>|is', $remote_source, $matchtitle );
6997          $title = isset( $matchtitle[1] ) ? $matchtitle[1] : '';
6998          if ( empty( $title ) ) {
6999              return $this->pingback_error( 32, __( 'A title on that page cannot be found.' ) );
7000          }
7001  
7002          // Remove all script and style tags including their content.
7003          $remote_source = preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $remote_source );
7004          // Just keep the tag we need.
7005          $remote_source = strip_tags( $remote_source, '<a>' );
7006  
7007          $p = explode( "\n\n", $remote_source );
7008  
7009          $preg_target = preg_quote( $pagelinkedto, '|' );
7010  
7011          foreach ( $p as $para ) {
7012              if ( str_contains( $para, $pagelinkedto ) ) { // It exists, but is it a link?
7013                  preg_match( '|<a[^>]+?' . $preg_target . '[^>]*>([^>]+?)</a>|', $para, $context );
7014  
7015                  // If the URL isn't in a link context, keep looking.
7016                  if ( empty( $context ) ) {
7017                      continue;
7018                  }
7019  
7020                  /*
7021                   * We're going to use this fake tag to mark the context in a bit.
7022                   * The marker is needed in case the link text appears more than once in the paragraph.
7023                   */
7024                  $excerpt = preg_replace( '|\</?wpcontext\>|', '', $para );
7025  
7026                  // prevent really long link text
7027                  if ( strlen( $context[1] ) > 100 ) {
7028                      $context[1] = substr( $context[1], 0, 100 ) . '&#8230;';
7029                  }
7030  
7031                  $marker      = '<wpcontext>' . $context[1] . '</wpcontext>';  // Set up our marker.
7032                  $excerpt     = str_replace( $context[0], $marker, $excerpt ); // Swap out the link for our marker.
7033                  $excerpt     = strip_tags( $excerpt, '<wpcontext>' );         // Strip all tags but our context marker.
7034                  $excerpt     = trim( $excerpt );
7035                  $preg_marker = preg_quote( $marker, '|' );
7036                  $excerpt     = preg_replace( "|.*?\s(.{0,100}$preg_marker.{0,100})\s.*|s", '$1', $excerpt );
7037                  $excerpt     = strip_tags( $excerpt ); // YES, again, to remove the marker wrapper.
7038                  break;
7039              }
7040          }
7041  
7042          if ( empty( $context ) ) { // Link to target not found.
7043              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.' ) );
7044          }
7045  
7046          $pagelinkedfrom = str_replace( '&', '&amp;', $pagelinkedfrom );
7047  
7048          $context        = '[&#8230;] ' . esc_html( $excerpt ) . ' [&#8230;]';
7049          $pagelinkedfrom = $this->escape( $pagelinkedfrom );
7050  
7051          $comment_post_id      = (int) $post_id;
7052          $comment_author       = $title;
7053          $comment_author_email = '';
7054          $this->escape( $comment_author );
7055          $comment_author_url = $pagelinkedfrom;
7056          $comment_content    = $context;
7057          $this->escape( $comment_content );
7058          $comment_type = 'pingback';
7059  
7060          $commentdata = array(
7061              'comment_post_ID' => $comment_post_id,
7062          );
7063  
7064          $commentdata += compact(
7065              'comment_author',
7066              'comment_author_url',
7067              'comment_author_email',
7068              'comment_content',
7069              'comment_type',
7070              'remote_source',
7071              'remote_source_original'
7072          );
7073  
7074          $comment_id = wp_new_comment( $commentdata );
7075  
7076          if ( is_wp_error( $comment_id ) ) {
7077              return $this->pingback_error( 0, $comment_id->get_error_message() );
7078          }
7079  
7080          /**
7081           * Fires after a post pingback has been sent.
7082           *
7083           * @since 0.71
7084           *
7085           * @param int $comment_id Comment ID.
7086           */
7087          do_action( 'pingback_post', $comment_id );
7088  
7089          /* translators: 1: URL of the page linked from, 2: URL of the page linked to. */
7090          return sprintf( __( 'Pingback from %1$s to %2$s registered. Keep the web talking! :-)' ), $pagelinkedfrom, $pagelinkedto );
7091      }
7092  
7093      /**
7094       * Retrieves an array of URLs that pingbacked the given URL.
7095       *
7096       * Specs on http://www.aquarionics.com/misc/archives/blogite/0198.html
7097       *
7098       * @since 1.5.0
7099       *
7100       * @global wpdb $wpdb WordPress database abstraction object.
7101       *
7102       * @param string $url
7103       * @return array|IXR_Error
7104       */
7105  	public function pingback_extensions_getPingbacks( $url ) {
7106          global $wpdb;
7107  
7108          /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
7109          do_action( 'xmlrpc_call', 'pingback.extensions.getPingbacks', $url, $this );
7110  
7111          $url = $this->escape( $url );
7112  
7113          $post_id = url_to_postid( $url );
7114          if ( ! $post_id ) {
7115              // We aren't sure that the resource is available and/or pingback enabled.
7116              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.' ) );
7117          }
7118  
7119          $actual_post = get_post( $post_id, ARRAY_A );
7120  
7121          if ( ! $actual_post ) {
7122              // No such post = resource not found.
7123              return $this->pingback_error( 32, __( 'The specified target URL does not exist.' ) );
7124          }
7125  
7126          $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 ) );
7127  
7128          if ( ! $comments ) {
7129              return array();
7130          }
7131  
7132          $pingbacks = array();
7133          foreach ( $comments as $comment ) {
7134              if ( 'pingback' === $comment->comment_type ) {
7135                  $pingbacks[] = $comment->comment_author_url;
7136              }
7137          }
7138  
7139          return $pingbacks;
7140      }
7141  
7142      /**
7143       * Sends a pingback error based on the given error code and message.
7144       *
7145       * @since 3.6.0
7146       *
7147       * @param int    $code    Error code.
7148       * @param string $message Error message.
7149       * @return IXR_Error Error object.
7150       */
7151  	protected function pingback_error( $code, $message ) {
7152          /**
7153           * Filters the XML-RPC pingback error return.
7154           *
7155           * @since 3.5.1
7156           *
7157           * @param IXR_Error $error An IXR_Error object containing the error code and message.
7158           */
7159          return apply_filters( 'xmlrpc_pingback_error', new IXR_Error( $code, $message ) );
7160      }
7161  }


Generated : Sat Apr 27 08:20:02 2024 Cross-referenced by PHPXref