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