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


Generated : Sat Aug 29 08:20:24 2026 Cross-referenced by PHPXref