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


Generated : Sat Nov 7 15:39:38 2026 Cross-referenced by PHPXref