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


Generated : Fri Feb 21 08:20:01 2025 Cross-referenced by PHPXref