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