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