[ Index ] |
PHP Cross Reference of WordPress |
[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::login(). 18 * 19 * @since 1.5.0 20 * 21 * @see IXR_Server 22 */ 23 class wp_xmlrpc_server extends IXR_Server { 24 /** 25 * Methods. 26 * 27 * @var array 28 */ 29 public $methods; 30 31 /** 32 * Blog options. 33 * 34 * @var array 35 */ 36 public $blog_options; 37 38 /** 39 * IXR_Error instance. 40 * 41 * @var IXR_Error 42 */ 43 public $error; 44 45 /** 46 * Flags that the user authentication has failed in this instance of wp_xmlrpc_server. 47 * 48 * @var bool 49 */ 50 protected $auth_failed = false; 51 52 /** 53 * Registers all of the XMLRPC methods that XMLRPC server understands. 54 * 55 * Sets up server and method property. Passes XMLRPC 56 * methods through the {@see 'xmlrpc_methods'} filter to allow plugins to extend 57 * or replace XML-RPC methods. 58 * 59 * @since 1.5.0 60 */ 61 public function __construct() { 62 $this->methods = array( 63 // WordPress API. 64 'wp.getUsersBlogs' => 'this:wp_getUsersBlogs', 65 'wp.newPost' => 'this:wp_newPost', 66 'wp.editPost' => 'this:wp_editPost', 67 'wp.deletePost' => 'this:wp_deletePost', 68 'wp.getPost' => 'this:wp_getPost', 69 'wp.getPosts' => 'this:wp_getPosts', 70 'wp.newTerm' => 'this:wp_newTerm', 71 'wp.editTerm' => 'this:wp_editTerm', 72 'wp.deleteTerm' => 'this:wp_deleteTerm', 73 'wp.getTerm' => 'this:wp_getTerm', 74 'wp.getTerms' => 'this:wp_getTerms', 75 'wp.getTaxonomy' => 'this:wp_getTaxonomy', 76 'wp.getTaxonomies' => 'this:wp_getTaxonomies', 77 'wp.getUser' => 'this:wp_getUser', 78 'wp.getUsers' => 'this:wp_getUsers', 79 'wp.getProfile' => 'this:wp_getProfile', 80 'wp.editProfile' => 'this:wp_editProfile', 81 'wp.getPage' => 'this:wp_getPage', 82 'wp.getPages' => 'this:wp_getPages', 83 'wp.newPage' => 'this:wp_newPage', 84 'wp.deletePage' => 'this:wp_deletePage', 85 'wp.editPage' => 'this:wp_editPage', 86 'wp.getPageList' => 'this:wp_getPageList', 87 'wp.getAuthors' => 'this:wp_getAuthors', 88 'wp.getCategories' => 'this:mw_getCategories', // Alias. 89 'wp.getTags' => 'this:wp_getTags', 90 'wp.newCategory' => 'this:wp_newCategory', 91 'wp.deleteCategory' => 'this:wp_deleteCategory', 92 'wp.suggestCategories' => 'this:wp_suggestCategories', 93 'wp.uploadFile' => 'this:mw_newMediaObject', // Alias. 94 'wp.deleteFile' => 'this:wp_deletePost', // Alias. 95 'wp.getCommentCount' => 'this:wp_getCommentCount', 96 'wp.getPostStatusList' => 'this:wp_getPostStatusList', 97 'wp.getPageStatusList' => 'this:wp_getPageStatusList', 98 'wp.getPageTemplates' => 'this:wp_getPageTemplates', 99 'wp.getOptions' => 'this:wp_getOptions', 100 'wp.setOptions' => 'this:wp_setOptions', 101 'wp.getComment' => 'this:wp_getComment', 102 'wp.getComments' => 'this:wp_getComments', 103 'wp.deleteComment' => 'this:wp_deleteComment', 104 'wp.editComment' => 'this:wp_editComment', 105 'wp.newComment' => 'this:wp_newComment', 106 'wp.getCommentStatusList' => 'this:wp_getCommentStatusList', 107 'wp.getMediaItem' => 'this:wp_getMediaItem', 108 'wp.getMediaLibrary' => 'this:wp_getMediaLibrary', 109 'wp.getPostFormats' => 'this:wp_getPostFormats', 110 'wp.getPostType' => 'this:wp_getPostType', 111 'wp.getPostTypes' => 'this:wp_getPostTypes', 112 'wp.getRevisions' => 'this:wp_getRevisions', 113 'wp.restoreRevision' => 'this:wp_restoreRevision', 114 115 // Blogger API. 116 'blogger.getUsersBlogs' => 'this:blogger_getUsersBlogs', 117 'blogger.getUserInfo' => 'this:blogger_getUserInfo', 118 'blogger.getPost' => 'this:blogger_getPost', 119 'blogger.getRecentPosts' => 'this:blogger_getRecentPosts', 120 'blogger.newPost' => 'this:blogger_newPost', 121 'blogger.editPost' => 'this:blogger_editPost', 122 'blogger.deletePost' => 'this:blogger_deletePost', 123 124 // MetaWeblog API (with MT extensions to structs). 125 'metaWeblog.newPost' => 'this:mw_newPost', 126 'metaWeblog.editPost' => 'this:mw_editPost', 127 'metaWeblog.getPost' => 'this:mw_getPost', 128 'metaWeblog.getRecentPosts' => 'this:mw_getRecentPosts', 129 'metaWeblog.getCategories' => 'this:mw_getCategories', 130 'metaWeblog.newMediaObject' => 'this:mw_newMediaObject', 131 132 // MetaWeblog API aliases for Blogger API. 133 // See http://www.xmlrpc.com/stories/storyReader$2460 134 'metaWeblog.deletePost' => 'this:blogger_deletePost', 135 'metaWeblog.getUsersBlogs' => 'this:blogger_getUsersBlogs', 136 137 // MovableType API. 138 'mt.getCategoryList' => 'this:mt_getCategoryList', 139 'mt.getRecentPostTitles' => 'this:mt_getRecentPostTitles', 140 'mt.getPostCategories' => 'this:mt_getPostCategories', 141 'mt.setPostCategories' => 'this:mt_setPostCategories', 142 'mt.supportedMethods' => 'this:mt_supportedMethods', 143 'mt.supportedTextFilters' => 'this:mt_supportedTextFilters', 144 'mt.getTrackbackPings' => 'this:mt_getTrackbackPings', 145 'mt.publishPost' => 'this:mt_publishPost', 146 147 // Pingback. 148 'pingback.ping' => 'this:pingback_ping', 149 'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks', 150 151 'demo.sayHello' => 'this:sayHello', 152 'demo.addTwoNumbers' => 'this:addTwoNumbers', 153 ); 154 155 $this->initialise_blog_option_info(); 156 157 /** 158 * Filters the methods exposed by the XML-RPC server. 159 * 160 * This filter can be used to add new methods, and remove built-in methods. 161 * 162 * @since 1.5.0 163 * 164 * @param string[] $methods An array of XML-RPC methods, keyed by their methodName. 165 */ 166 $this->methods = apply_filters( 'xmlrpc_methods', $this->methods ); 167 } 168 169 /** 170 * Make private/protected methods readable for backward compatibility. 171 * 172 * @since 4.0.0 173 * 174 * @param string $name Method to call. 175 * @param array $arguments Arguments to pass when calling. 176 * @return array|IXR_Error|false Return value of the callback, false otherwise. 177 */ 178 public function __call( $name, $arguments ) { 179 if ( '_multisite_getUsersBlogs' === $name ) { 180 return $this->_multisite_getUsersBlogs( ...$arguments ); 181 } 182 return false; 183 } 184 185 /** 186 * Serves the XML-RPC request. 187 * 188 * @since 2.9.0 189 */ 190 public function serve_request() { 191 $this->IXR_Server( $this->methods ); 192 } 193 194 /** 195 * Test XMLRPC API by saying, "Hello!" to client. 196 * 197 * @since 1.5.0 198 * 199 * @return string Hello string response. 200 */ 201 public function sayHello() { 202 return 'Hello!'; 203 } 204 205 /** 206 * Test XMLRPC API by adding two numbers for client. 207 * 208 * @since 1.5.0 209 * 210 * @param array $args { 211 * Method arguments. Note: arguments must be ordered as documented. 212 * 213 * @type int $number1 A number to add. 214 * @type int $number2 A second number to add. 215 * } 216 * @return int Sum of the two given numbers. 217 */ 218 public function addTwoNumbers( $args ) { 219 $number1 = $args[0]; 220 $number2 = $args[1]; 221 return $number1 + $number2; 222 } 223 224 /** 225 * Log user in. 226 * 227 * @since 2.8.0 228 * 229 * @param string $username User's username. 230 * @param string $password User's password. 231 * @return WP_User|false WP_User object if authentication passed, false otherwise 232 */ 233 public function login( $username, $password ) { 234 /* 235 * Respect old get_option() filters left for back-compat when the 'enable_xmlrpc' 236 * option was deprecated in 3.5.0. Use the 'xmlrpc_enabled' hook instead. 237 */ 238 $enabled = apply_filters( 'pre_option_enable_xmlrpc', false ); 239 if ( false === $enabled ) { 240 $enabled = apply_filters( 'option_enable_xmlrpc', true ); 241 } 242 243 /** 244 * Filters whether XML-RPC methods requiring authentication are enabled. 245 * 246 * Contrary to the way it's named, this filter does not control whether XML-RPC is *fully* 247 * enabled, rather, it only controls whether XML-RPC methods requiring authentication - such 248 * as for publishing purposes - are enabled. 249 * 250 * Further, the filter does not control whether pingbacks or other custom endpoints that don't 251 * require authentication are enabled. This behavior is expected, and due to how parity was matched 252 * with the `enable_xmlrpc` UI option the filter replaced when it was introduced in 3.5. 253 * 254 * To disable XML-RPC methods that require authentication, use: 255 * 256 * add_filter( 'xmlrpc_enabled', '__return_false' ); 257 * 258 * For more granular control over all XML-RPC methods and requests, see the {@see 'xmlrpc_methods'} 259 * and {@see 'xmlrpc_element_limit'} hooks. 260 * 261 * @since 3.5.0 262 * 263 * @param bool $enabled Whether XML-RPC is enabled. Default true. 264 */ 265 $enabled = apply_filters( 'xmlrpc_enabled', $enabled ); 266 267 if ( ! $enabled ) { 268 $this->error = new IXR_Error( 405, sprintf( __( 'XML-RPC services are disabled on this site.' ) ) ); 269 return false; 270 } 271 272 if ( $this->auth_failed ) { 273 $user = new WP_Error( 'login_prevented' ); 274 } else { 275 $user = wp_authenticate( $username, $password ); 276 } 277 278 if ( is_wp_error( $user ) ) { 279 $this->error = new IXR_Error( 403, __( 'Incorrect username or password.' ) ); 280 281 // Flag that authentication has failed once on this wp_xmlrpc_server instance. 282 $this->auth_failed = true; 283 284 /** 285 * Filters the XML-RPC user login error message. 286 * 287 * @since 3.5.0 288 * 289 * @param IXR_Error $error The XML-RPC error message. 290 * @param WP_Error $user WP_Error object. 291 */ 292 $this->error = apply_filters( 'xmlrpc_login_error', $this->error, $user ); 293 return false; 294 } 295 296 wp_set_current_user( $user->ID ); 297 return $user; 298 } 299 300 /** 301 * Check user's credentials. Deprecated. 302 * 303 * @since 1.5.0 304 * @deprecated 2.8.0 Use wp_xmlrpc_server::login() 305 * @see wp_xmlrpc_server::login() 306 * 307 * @param string $username User's username. 308 * @param string $password User's password. 309 * @return bool Whether authentication passed. 310 */ 311 public function login_pass_ok( $username, $password ) { 312 return (bool) $this->login( $username, $password ); 313 } 314 315 /** 316 * Escape string or array of strings for database. 317 * 318 * @since 1.5.2 319 * 320 * @param string|array $data Escape single string or array of strings. 321 * @return string|void Returns with string is passed, alters by-reference 322 * when array is passed. 323 */ 324 public function escape( &$data ) { 325 if ( ! is_array( $data ) ) { 326 return wp_slash( $data ); 327 } 328 329 foreach ( $data as &$v ) { 330 if ( is_array( $v ) ) { 331 $this->escape( $v ); 332 } elseif ( ! is_object( $v ) ) { 333 $v = wp_slash( $v ); 334 } 335 } 336 } 337 338 /** 339 * Retrieve custom fields for post. 340 * 341 * @since 2.5.0 342 * 343 * @param int $post_id Post ID. 344 * @return array Custom fields, if exist. 345 */ 346 public function get_custom_fields( $post_id ) { 347 $post_id = (int) $post_id; 348 349 $custom_fields = array(); 350 351 foreach ( (array) has_meta( $post_id ) as $meta ) { 352 // Don't expose protected fields. 353 if ( ! current_user_can( 'edit_post_meta', $post_id, $meta['meta_key'] ) ) { 354 continue; 355 } 356 357 $custom_fields[] = array( 358 'id' => $meta['meta_id'], 359 'key' => $meta['meta_key'], 360 'value' => $meta['meta_value'], 361 ); 362 } 363 364 return $custom_fields; 365 } 366 367 /** 368 * Set custom fields for post. 369 * 370 * @since 2.5.0 371 * 372 * @param int $post_id Post ID. 373 * @param array $fields Custom fields. 374 */ 375 public function set_custom_fields( $post_id, $fields ) { 376 $post_id = (int) $post_id; 377 378 foreach ( (array) $fields as $meta ) { 379 if ( isset( $meta['id'] ) ) { 380 $meta['id'] = (int) $meta['id']; 381 $pmeta = get_metadata_by_mid( 'post', $meta['id'] ); 382 383 if ( ! $pmeta || $pmeta->post_id != $post_id ) { 384 continue; 385 } 386 387 if ( isset( $meta['key'] ) ) { 388 $meta['key'] = wp_unslash( $meta['key'] ); 389 if ( $meta['key'] !== $pmeta->meta_key ) { 390 continue; 391 } 392 $meta['value'] = wp_unslash( $meta['value'] ); 393 if ( current_user_can( 'edit_post_meta', $post_id, $meta['key'] ) ) { 394 update_metadata_by_mid( 'post', $meta['id'], $meta['value'] ); 395 } 396 } elseif ( current_user_can( 'delete_post_meta', $post_id, $pmeta->meta_key ) ) { 397 delete_metadata_by_mid( 'post', $meta['id'] ); 398 } 399 } elseif ( current_user_can( 'add_post_meta', $post_id, wp_unslash( $meta['key'] ) ) ) { 400 add_post_meta( $post_id, $meta['key'], $meta['value'] ); 401 } 402 } 403 } 404 405 /** 406 * Retrieve custom fields for a term. 407 * 408 * @since 4.9.0 409 * 410 * @param int $term_id Term ID. 411 * @return array Array of custom fields, if they exist. 412 */ 413 public function get_term_custom_fields( $term_id ) { 414 $term_id = (int) $term_id; 415 416 $custom_fields = array(); 417 418 foreach ( (array) has_term_meta( $term_id ) as $meta ) { 419 420 if ( ! current_user_can( 'edit_term_meta', $term_id ) ) { 421 continue; 422 } 423 424 $custom_fields[] = array( 425 'id' => $meta['meta_id'], 426 'key' => $meta['meta_key'], 427 'value' => $meta['meta_value'], 428 ); 429 } 430 431 return $custom_fields; 432 } 433 434 /** 435 * Set custom fields for a term. 436 * 437 * @since 4.9.0 438 * 439 * @param int $term_id Term ID. 440 * @param array $fields Custom fields. 441 */ 442 public function set_term_custom_fields( $term_id, $fields ) { 443 $term_id = (int) $term_id; 444 445 foreach ( (array) $fields as $meta ) { 446 if ( isset( $meta['id'] ) ) { 447 $meta['id'] = (int) $meta['id']; 448 $pmeta = get_metadata_by_mid( 'term', $meta['id'] ); 449 if ( isset( $meta['key'] ) ) { 450 $meta['key'] = wp_unslash( $meta['key'] ); 451 if ( $meta['key'] !== $pmeta->meta_key ) { 452 continue; 453 } 454 $meta['value'] = wp_unslash( $meta['value'] ); 455 if ( current_user_can( 'edit_term_meta', $term_id ) ) { 456 update_metadata_by_mid( 'term', $meta['id'], $meta['value'] ); 457 } 458 } elseif ( current_user_can( 'delete_term_meta', $term_id ) ) { 459 delete_metadata_by_mid( 'term', $meta['id'] ); 460 } 461 } elseif ( current_user_can( 'add_term_meta', $term_id ) ) { 462 add_term_meta( $term_id, $meta['key'], $meta['value'] ); 463 } 464 } 465 } 466 467 /** 468 * Set up blog options property. 469 * 470 * Passes property through {@see 'xmlrpc_blog_options'} filter. 471 * 472 * @since 2.6.0 473 */ 474 public function initialise_blog_option_info() { 475 $this->blog_options = array( 476 // Read-only options. 477 'software_name' => array( 478 'desc' => __( 'Software Name' ), 479 'readonly' => true, 480 'value' => 'WordPress', 481 ), 482 'software_version' => array( 483 'desc' => __( 'Software Version' ), 484 'readonly' => true, 485 'value' => get_bloginfo( 'version' ), 486 ), 487 'blog_url' => array( 488 'desc' => __( 'WordPress Address (URL)' ), 489 'readonly' => true, 490 'option' => 'siteurl', 491 ), 492 'home_url' => array( 493 'desc' => __( 'Site Address (URL)' ), 494 'readonly' => true, 495 'option' => 'home', 496 ), 497 'login_url' => array( 498 'desc' => __( 'Login Address (URL)' ), 499 'readonly' => true, 500 'value' => wp_login_url(), 501 ), 502 'admin_url' => array( 503 'desc' => __( 'The URL to the admin area' ), 504 'readonly' => true, 505 'value' => get_admin_url(), 506 ), 507 'image_default_link_type' => array( 508 'desc' => __( 'Image default link type' ), 509 'readonly' => true, 510 'option' => 'image_default_link_type', 511 ), 512 'image_default_size' => array( 513 'desc' => __( 'Image default size' ), 514 'readonly' => true, 515 'option' => 'image_default_size', 516 ), 517 'image_default_align' => array( 518 'desc' => __( 'Image default align' ), 519 'readonly' => true, 520 'option' => 'image_default_align', 521 ), 522 'template' => array( 523 'desc' => __( 'Template' ), 524 'readonly' => true, 525 'option' => 'template', 526 ), 527 'stylesheet' => array( 528 'desc' => __( 'Stylesheet' ), 529 'readonly' => true, 530 'option' => 'stylesheet', 531 ), 532 'post_thumbnail' => array( 533 'desc' => __( 'Post Thumbnail' ), 534 'readonly' => true, 535 'value' => current_theme_supports( 'post-thumbnails' ), 536 ), 537 538 // Updatable options. 539 'time_zone' => array( 540 'desc' => __( 'Time Zone' ), 541 'readonly' => false, 542 'option' => 'gmt_offset', 543 ), 544 'blog_title' => array( 545 'desc' => __( 'Site Title' ), 546 'readonly' => false, 547 'option' => 'blogname', 548 ), 549 'blog_tagline' => array( 550 'desc' => __( 'Site Tagline' ), 551 'readonly' => false, 552 'option' => 'blogdescription', 553 ), 554 'date_format' => array( 555 'desc' => __( 'Date Format' ), 556 'readonly' => false, 557 'option' => 'date_format', 558 ), 559 'time_format' => array( 560 'desc' => __( 'Time Format' ), 561 'readonly' => false, 562 'option' => 'time_format', 563 ), 564 'users_can_register' => array( 565 'desc' => __( 'Allow new users to sign up' ), 566 'readonly' => false, 567 'option' => 'users_can_register', 568 ), 569 'thumbnail_size_w' => array( 570 'desc' => __( 'Thumbnail Width' ), 571 'readonly' => false, 572 'option' => 'thumbnail_size_w', 573 ), 574 'thumbnail_size_h' => array( 575 'desc' => __( 'Thumbnail Height' ), 576 'readonly' => false, 577 'option' => 'thumbnail_size_h', 578 ), 579 'thumbnail_crop' => array( 580 'desc' => __( 'Crop thumbnail to exact dimensions' ), 581 'readonly' => false, 582 'option' => 'thumbnail_crop', 583 ), 584 'medium_size_w' => array( 585 'desc' => __( 'Medium size image width' ), 586 'readonly' => false, 587 'option' => 'medium_size_w', 588 ), 589 'medium_size_h' => array( 590 'desc' => __( 'Medium size image height' ), 591 'readonly' => false, 592 'option' => 'medium_size_h', 593 ), 594 'medium_large_size_w' => array( 595 'desc' => __( 'Medium-Large size image width' ), 596 'readonly' => false, 597 'option' => 'medium_large_size_w', 598 ), 599 'medium_large_size_h' => array( 600 'desc' => __( 'Medium-Large size image height' ), 601 'readonly' => false, 602 'option' => 'medium_large_size_h', 603 ), 604 'large_size_w' => array( 605 'desc' => __( 'Large size image width' ), 606 'readonly' => false, 607 'option' => 'large_size_w', 608 ), 609 'large_size_h' => array( 610 'desc' => __( 'Large size image height' ), 611 'readonly' => false, 612 'option' => 'large_size_h', 613 ), 614 'default_comment_status' => array( 615 'desc' => __( 'Allow people to submit comments on new posts.' ), 616 'readonly' => false, 617 'option' => 'default_comment_status', 618 ), 619 'default_ping_status' => array( 620 'desc' => __( 'Allow link notifications from other blogs (pingbacks and trackbacks) on new posts.' ), 621 'readonly' => false, 622 'option' => 'default_ping_status', 623 ), 624 ); 625 626 /** 627 * Filters the XML-RPC blog options property. 628 * 629 * @since 2.6.0 630 * 631 * @param array $blog_options An array of XML-RPC blog options. 632 */ 633 $this->blog_options = apply_filters( 'xmlrpc_blog_options', $this->blog_options ); 634 } 635 636 /** 637 * Retrieve the blogs of the user. 638 * 639 * @since 2.6.0 640 * 641 * @param array $args { 642 * Method arguments. Note: arguments must be ordered as documented. 643 * 644 * @type string $username Username. 645 * @type string $password Password. 646 * } 647 * @return array|IXR_Error Array contains: 648 * - 'isAdmin' 649 * - 'isPrimary' - whether the blog is the user's primary blog 650 * - 'url' 651 * - 'blogid' 652 * - 'blogName' 653 * - 'xmlrpc' - url of xmlrpc endpoint 654 */ 655 public function wp_getUsersBlogs( $args ) { 656 if ( ! $this->minimum_args( $args, 2 ) ) { 657 return $this->error; 658 } 659 660 // If this isn't on WPMU then just use blogger_getUsersBlogs(). 661 if ( ! is_multisite() ) { 662 array_unshift( $args, 1 ); 663 return $this->blogger_getUsersBlogs( $args ); 664 } 665 666 $this->escape( $args ); 667 668 $username = $args[0]; 669 $password = $args[1]; 670 671 $user = $this->login( $username, $password ); 672 if ( ! $user ) { 673 return $this->error; 674 } 675 676 /** 677 * Fires after the XML-RPC user has been authenticated but before the rest of 678 * the method logic begins. 679 * 680 * All built-in XML-RPC methods use the action xmlrpc_call, with a parameter 681 * equal to the method's name, e.g., wp.getUsersBlogs, wp.newPost, etc. 682 * 683 * @since 2.5.0 684 * @since 5.7.0 Added the `$args` and `$server` parameters. 685 * 686 * @param string $name The method name. 687 * @param array|string $args The escaped arguments passed to the method. 688 * @param wp_xmlrpc_server $server The XML-RPC server instance. 689 */ 690 do_action( 'xmlrpc_call', 'wp.getUsersBlogs', $args, $this ); 691 692 $blogs = (array) get_blogs_of_user( $user->ID ); 693 $struct = array(); 694 $primary_blog_id = 0; 695 $active_blog = get_active_blog_for_user( $user->ID ); 696 if ( $active_blog ) { 697 $primary_blog_id = (int) $active_blog->blog_id; 698 } 699 700 foreach ( $blogs as $blog ) { 701 // Don't include blogs that aren't hosted at this site. 702 if ( get_current_network_id() != $blog->site_id ) { 703 continue; 704 } 705 706 $blog_id = $blog->userblog_id; 707 708 switch_to_blog( $blog_id ); 709 710 $is_admin = current_user_can( 'manage_options' ); 711 $is_primary = ( (int) $blog_id === $primary_blog_id ); 712 713 $struct[] = array( 714 'isAdmin' => $is_admin, 715 'isPrimary' => $is_primary, 716 'url' => home_url( '/' ), 717 'blogid' => (string) $blog_id, 718 'blogName' => get_option( 'blogname' ), 719 'xmlrpc' => site_url( 'xmlrpc.php', 'rpc' ), 720 ); 721 722 restore_current_blog(); 723 } 724 725 return $struct; 726 } 727 728 /** 729 * Checks if the method received at least the minimum number of arguments. 730 * 731 * @since 3.4.0 732 * 733 * @param array $args An array of arguments to check. 734 * @param int $count Minimum number of arguments. 735 * @return bool True if `$args` contains at least `$count` arguments, false otherwise. 736 */ 737 protected function minimum_args( $args, $count ) { 738 if ( ! is_array( $args ) || count( $args ) < $count ) { 739 $this->error = new IXR_Error( 400, __( 'Insufficient arguments passed to this XML-RPC method.' ) ); 740 return false; 741 } 742 743 return true; 744 } 745 746 /** 747 * Prepares taxonomy data for return in an XML-RPC object. 748 * 749 * @param WP_Taxonomy $taxonomy The unprepared taxonomy data. 750 * @param array $fields The subset of taxonomy fields to return. 751 * @return array The prepared taxonomy data. 752 */ 753 protected function _prepare_taxonomy( $taxonomy, $fields ) { 754 $_taxonomy = array( 755 'name' => $taxonomy->name, 756 'label' => $taxonomy->label, 757 'hierarchical' => (bool) $taxonomy->hierarchical, 758 'public' => (bool) $taxonomy->public, 759 'show_ui' => (bool) $taxonomy->show_ui, 760 '_builtin' => (bool) $taxonomy->_builtin, 761 ); 762 763 if ( in_array( 'labels', $fields, true ) ) { 764 $_taxonomy['labels'] = (array) $taxonomy->labels; 765 } 766 767 if ( in_array( 'cap', $fields, true ) ) { 768 $_taxonomy['cap'] = (array) $taxonomy->cap; 769 } 770 771 if ( in_array( 'menu', $fields, true ) ) { 772 $_taxonomy['show_in_menu'] = (bool) $taxonomy->show_in_menu; 773 } 774 775 if ( in_array( 'object_type', $fields, true ) ) { 776 $_taxonomy['object_type'] = array_unique( (array) $taxonomy->object_type ); 777 } 778 779 /** 780 * Filters XML-RPC-prepared data for the given taxonomy. 781 * 782 * @since 3.4.0 783 * 784 * @param array $_taxonomy An array of taxonomy data. 785 * @param WP_Taxonomy $taxonomy Taxonomy object. 786 * @param array $fields The subset of taxonomy fields to return. 787 */ 788 return apply_filters( 'xmlrpc_prepare_taxonomy', $_taxonomy, $taxonomy, $fields ); 789 } 790 791 /** 792 * Prepares term data for return in an XML-RPC object. 793 * 794 * @param array|object $term The unprepared term data. 795 * @return array The prepared term data. 796 */ 797 protected function _prepare_term( $term ) { 798 $_term = $term; 799 if ( ! is_array( $_term ) ) { 800 $_term = get_object_vars( $_term ); 801 } 802 803 // For integers which may be larger than XML-RPC supports ensure we return strings. 804 $_term['term_id'] = (string) $_term['term_id']; 805 $_term['term_group'] = (string) $_term['term_group']; 806 $_term['term_taxonomy_id'] = (string) $_term['term_taxonomy_id']; 807 $_term['parent'] = (string) $_term['parent']; 808 809 // Count we are happy to return as an integer because people really shouldn't use terms that much. 810 $_term['count'] = (int) $_term['count']; 811 812 // Get term meta. 813 $_term['custom_fields'] = $this->get_term_custom_fields( $_term['term_id'] ); 814 815 /** 816 * Filters XML-RPC-prepared data for the given term. 817 * 818 * @since 3.4.0 819 * 820 * @param array $_term An array of term data. 821 * @param array|object $term Term object or array. 822 */ 823 return apply_filters( 'xmlrpc_prepare_term', $_term, $term ); 824 } 825 826 /** 827 * Convert a WordPress date string to an IXR_Date object. 828 * 829 * @param string $date Date string to convert. 830 * @return IXR_Date IXR_Date object. 831 */ 832 protected function _convert_date( $date ) { 833 if ( '0000-00-00 00:00:00' === $date ) { 834 return new IXR_Date( '00000000T00:00:00Z' ); 835 } 836 return new IXR_Date( mysql2date( 'Ymd\TH:i:s', $date, false ) ); 837 } 838 839 /** 840 * Convert a WordPress GMT date string to an IXR_Date object. 841 * 842 * @param string $date_gmt WordPress GMT date string. 843 * @param string $date Date string. 844 * @return IXR_Date IXR_Date object. 845 */ 846 protected function _convert_date_gmt( $date_gmt, $date ) { 847 if ( '0000-00-00 00:00:00' !== $date && '0000-00-00 00:00:00' === $date_gmt ) { 848 return new IXR_Date( get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $date, false ), 'Ymd\TH:i:s' ) ); 849 } 850 return $this->_convert_date( $date_gmt ); 851 } 852 853 /** 854 * Prepares post data for return in an XML-RPC object. 855 * 856 * @param array $post The unprepared post data. 857 * @param array $fields The subset of post type fields to return. 858 * @return array The prepared post data. 859 */ 860 protected function _prepare_post( $post, $fields ) { 861 // Holds the data for this post. built up based on $fields. 862 $_post = array( 'post_id' => (string) $post['ID'] ); 863 864 // Prepare common post fields. 865 $post_fields = array( 866 'post_title' => $post['post_title'], 867 'post_date' => $this->_convert_date( $post['post_date'] ), 868 'post_date_gmt' => $this->_convert_date_gmt( $post['post_date_gmt'], $post['post_date'] ), 869 'post_modified' => $this->_convert_date( $post['post_modified'] ), 870 'post_modified_gmt' => $this->_convert_date_gmt( $post['post_modified_gmt'], $post['post_modified'] ), 871 'post_status' => $post['post_status'], 872 'post_type' => $post['post_type'], 873 'post_name' => $post['post_name'], 874 'post_author' => $post['post_author'], 875 'post_password' => $post['post_password'], 876 'post_excerpt' => $post['post_excerpt'], 877 'post_content' => $post['post_content'], 878 'post_parent' => (string) $post['post_parent'], 879 'post_mime_type' => $post['post_mime_type'], 880 'link' => get_permalink( $post['ID'] ), 881 'guid' => $post['guid'], 882 'menu_order' => (int) $post['menu_order'], 883 'comment_status' => $post['comment_status'], 884 'ping_status' => $post['ping_status'], 885 'sticky' => ( 'post' === $post['post_type'] && is_sticky( $post['ID'] ) ), 886 ); 887 888 // Thumbnail. 889 $post_fields['post_thumbnail'] = array(); 890 $thumbnail_id = get_post_thumbnail_id( $post['ID'] ); 891 if ( $thumbnail_id ) { 892 $thumbnail_size = current_theme_supports( 'post-thumbnail' ) ? 'post-thumbnail' : 'thumbnail'; 893 $post_fields['post_thumbnail'] = $this->_prepare_media_item( get_post( $thumbnail_id ), $thumbnail_size ); 894 } 895 896 // Consider future posts as published. 897 if ( 'future' === $post_fields['post_status'] ) { 898 $post_fields['post_status'] = 'publish'; 899 } 900 901 // Fill in blank post format. 902 $post_fields['post_format'] = get_post_format( $post['ID'] ); 903 if ( empty( $post_fields['post_format'] ) ) { 904 $post_fields['post_format'] = 'standard'; 905 } 906 907 // Merge requested $post_fields fields into $_post. 908 if ( in_array( 'post', $fields, true ) ) { 909 $_post = array_merge( $_post, $post_fields ); 910 } else { 911 $requested_fields = array_intersect_key( $post_fields, array_flip( $fields ) ); 912 $_post = array_merge( $_post, $requested_fields ); 913 } 914 915 $all_taxonomy_fields = in_array( 'taxonomies', $fields, true ); 916 917 if ( $all_taxonomy_fields || in_array( 'terms', $fields, true ) ) { 918 $post_type_taxonomies = get_object_taxonomies( $post['post_type'], 'names' ); 919 $terms = wp_get_object_terms( $post['ID'], $post_type_taxonomies ); 920 $_post['terms'] = array(); 921 foreach ( $terms as $term ) { 922 $_post['terms'][] = $this->_prepare_term( $term ); 923 } 924 } 925 926 if ( in_array( 'custom_fields', $fields, true ) ) { 927 $_post['custom_fields'] = $this->get_custom_fields( $post['ID'] ); 928 } 929 930 if ( in_array( 'enclosure', $fields, true ) ) { 931 $_post['enclosure'] = array(); 932 $enclosures = (array) get_post_meta( $post['ID'], 'enclosure' ); 933 if ( ! empty( $enclosures ) ) { 934 $encdata = explode( "\n", $enclosures[0] ); 935 $_post['enclosure']['url'] = trim( htmlspecialchars( $encdata[0] ) ); 936 $_post['enclosure']['length'] = (int) trim( $encdata[1] ); 937 $_post['enclosure']['type'] = trim( $encdata[2] ); 938 } 939 } 940 941 /** 942 * Filters XML-RPC-prepared date for the given post. 943 * 944 * @since 3.4.0 945 * 946 * @param array $_post An array of modified post data. 947 * @param array $post An array of post data. 948 * @param array $fields An array of post fields. 949 */ 950 return apply_filters( 'xmlrpc_prepare_post', $_post, $post, $fields ); 951 } 952 953 /** 954 * Prepares post data for return in an XML-RPC object. 955 * 956 * @since 3.4.0 957 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object. 958 * 959 * @param WP_Post_Type $post_type Post type object. 960 * @param array $fields The subset of post fields to return. 961 * @return array The prepared post type data. 962 */ 963 protected function _prepare_post_type( $post_type, $fields ) { 964 $_post_type = array( 965 'name' => $post_type->name, 966 'label' => $post_type->label, 967 'hierarchical' => (bool) $post_type->hierarchical, 968 'public' => (bool) $post_type->public, 969 'show_ui' => (bool) $post_type->show_ui, 970 '_builtin' => (bool) $post_type->_builtin, 971 'has_archive' => (bool) $post_type->has_archive, 972 'supports' => get_all_post_type_supports( $post_type->name ), 973 ); 974 975 if ( in_array( 'labels', $fields, true ) ) { 976 $_post_type['labels'] = (array) $post_type->labels; 977 } 978 979 if ( in_array( 'cap', $fields, true ) ) { 980 $_post_type['cap'] = (array) $post_type->cap; 981 $_post_type['map_meta_cap'] = (bool) $post_type->map_meta_cap; 982 } 983 984 if ( in_array( 'menu', $fields, true ) ) { 985 $_post_type['menu_position'] = (int) $post_type->menu_position; 986 $_post_type['menu_icon'] = $post_type->menu_icon; 987 $_post_type['show_in_menu'] = (bool) $post_type->show_in_menu; 988 } 989 990 if ( in_array( 'taxonomies', $fields, true ) ) { 991 $_post_type['taxonomies'] = get_object_taxonomies( $post_type->name, 'names' ); 992 } 993 994 /** 995 * Filters XML-RPC-prepared date for the given post type. 996 * 997 * @since 3.4.0 998 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object. 999 * 1000 * @param array $_post_type An array of post type data. 1001 * @param WP_Post_Type $post_type Post type object. 1002 */ 1003 return apply_filters( 'xmlrpc_prepare_post_type', $_post_type, $post_type ); 1004 } 1005 1006 /** 1007 * Prepares media item data for return in an XML-RPC object. 1008 * 1009 * @param WP_Post $media_item The unprepared media item data. 1010 * @param string $thumbnail_size The image size to use for the thumbnail URL. 1011 * @return array The prepared media item data. 1012 */ 1013 protected function _prepare_media_item( $media_item, $thumbnail_size = 'thumbnail' ) { 1014 $_media_item = array( 1015 'attachment_id' => (string) $media_item->ID, 1016 'date_created_gmt' => $this->_convert_date_gmt( $media_item->post_date_gmt, $media_item->post_date ), 1017 'parent' => $media_item->post_parent, 1018 'link' => wp_get_attachment_url( $media_item->ID ), 1019 'title' => $media_item->post_title, 1020 'caption' => $media_item->post_excerpt, 1021 'description' => $media_item->post_content, 1022 'metadata' => wp_get_attachment_metadata( $media_item->ID ), 1023 'type' => $media_item->post_mime_type, 1024 ); 1025 1026 $thumbnail_src = image_downsize( $media_item->ID, $thumbnail_size ); 1027 if ( $thumbnail_src ) { 1028 $_media_item['thumbnail'] = $thumbnail_src[0]; 1029 } else { 1030 $_media_item['thumbnail'] = $_media_item['link']; 1031 } 1032 1033 /** 1034 * Filters XML-RPC-prepared data for the given media item. 1035 * 1036 * @since 3.4.0 1037 * 1038 * @param array $_media_item An array of media item data. 1039 * @param WP_Post $media_item Media item object. 1040 * @param string $thumbnail_size Image size. 1041 */ 1042 return apply_filters( 'xmlrpc_prepare_media_item', $_media_item, $media_item, $thumbnail_size ); 1043 } 1044 1045 /** 1046 * Prepares page data for return in an XML-RPC object. 1047 * 1048 * @param WP_Post $page The unprepared page data. 1049 * @return array The prepared page data. 1050 */ 1051 protected function _prepare_page( $page ) { 1052 // Get all of the page content and link. 1053 $full_page = get_extended( $page->post_content ); 1054 $link = get_permalink( $page->ID ); 1055 1056 // Get info the page parent if there is one. 1057 $parent_title = ''; 1058 if ( ! empty( $page->post_parent ) ) { 1059 $parent = get_post( $page->post_parent ); 1060 $parent_title = $parent->post_title; 1061 } 1062 1063 // Determine comment and ping settings. 1064 $allow_comments = comments_open( $page->ID ) ? 1 : 0; 1065 $allow_pings = pings_open( $page->ID ) ? 1 : 0; 1066 1067 // Format page date. 1068 $page_date = $this->_convert_date( $page->post_date ); 1069 $page_date_gmt = $this->_convert_date_gmt( $page->post_date_gmt, $page->post_date ); 1070 1071 // Pull the categories info together. 1072 $categories = array(); 1073 if ( is_object_in_taxonomy( 'page', 'category' ) ) { 1074 foreach ( wp_get_post_categories( $page->ID ) as $cat_id ) { 1075 $categories[] = get_cat_name( $cat_id ); 1076 } 1077 } 1078 1079 // Get the author info. 1080 $author = get_userdata( $page->post_author ); 1081 1082 $page_template = get_page_template_slug( $page->ID ); 1083 if ( empty( $page_template ) ) { 1084 $page_template = 'default'; 1085 } 1086 1087 $_page = array( 1088 'dateCreated' => $page_date, 1089 'userid' => $page->post_author, 1090 'page_id' => $page->ID, 1091 'page_status' => $page->post_status, 1092 'description' => $full_page['main'], 1093 'title' => $page->post_title, 1094 'link' => $link, 1095 'permaLink' => $link, 1096 'categories' => $categories, 1097 'excerpt' => $page->post_excerpt, 1098 'text_more' => $full_page['extended'], 1099 'mt_allow_comments' => $allow_comments, 1100 'mt_allow_pings' => $allow_pings, 1101 'wp_slug' => $page->post_name, 1102 'wp_password' => $page->post_password, 1103 'wp_author' => $author->display_name, 1104 'wp_page_parent_id' => $page->post_parent, 1105 'wp_page_parent_title' => $parent_title, 1106 'wp_page_order' => $page->menu_order, 1107 'wp_author_id' => (string) $author->ID, 1108 'wp_author_display_name' => $author->display_name, 1109 'date_created_gmt' => $page_date_gmt, 1110 'custom_fields' => $this->get_custom_fields( $page->ID ), 1111 'wp_page_template' => $page_template, 1112 ); 1113 1114 /** 1115 * Filters XML-RPC-prepared data for the given page. 1116 * 1117 * @since 3.4.0 1118 * 1119 * @param array $_page An array of page data. 1120 * @param WP_Post $page Page object. 1121 */ 1122 return apply_filters( 'xmlrpc_prepare_page', $_page, $page ); 1123 } 1124 1125 /** 1126 * Prepares comment data for return in an XML-RPC object. 1127 * 1128 * @param WP_Comment $comment The unprepared comment data. 1129 * @return array The prepared comment data. 1130 */ 1131 protected function _prepare_comment( $comment ) { 1132 // Format page date. 1133 $comment_date_gmt = $this->_convert_date_gmt( $comment->comment_date_gmt, $comment->comment_date ); 1134 1135 if ( '0' == $comment->comment_approved ) { 1136 $comment_status = 'hold'; 1137 } elseif ( 'spam' === $comment->comment_approved ) { 1138 $comment_status = 'spam'; 1139 } elseif ( '1' == $comment->comment_approved ) { 1140 $comment_status = 'approve'; 1141 } else { 1142 $comment_status = $comment->comment_approved; 1143 } 1144 $_comment = array( 1145 'date_created_gmt' => $comment_date_gmt, 1146 'user_id' => $comment->user_id, 1147 'comment_id' => $comment->comment_ID, 1148 'parent' => $comment->comment_parent, 1149 'status' => $comment_status, 1150 'content' => $comment->comment_content, 1151 'link' => get_comment_link( $comment ), 1152 'post_id' => $comment->comment_post_ID, 1153 'post_title' => get_the_title( $comment->comment_post_ID ), 1154 'author' => $comment->comment_author, 1155 'author_url' => $comment->comment_author_url, 1156 'author_email' => $comment->comment_author_email, 1157 'author_ip' => $comment->comment_author_IP, 1158 'type' => $comment->comment_type, 1159 ); 1160 1161 /** 1162 * Filters XML-RPC-prepared data for the given comment. 1163 * 1164 * @since 3.4.0 1165 * 1166 * @param array $_comment An array of prepared comment data. 1167 * @param WP_Comment $comment Comment object. 1168 */ 1169 return apply_filters( 'xmlrpc_prepare_comment', $_comment, $comment ); 1170 } 1171 1172 /** 1173 * Prepares user data for return in an XML-RPC object. 1174 * 1175 * @param WP_User $user The unprepared user object. 1176 * @param array $fields The subset of user fields to return. 1177 * @return array The prepared user data. 1178 */ 1179 protected function _prepare_user( $user, $fields ) { 1180 $_user = array( 'user_id' => (string) $user->ID ); 1181 1182 $user_fields = array( 1183 'username' => $user->user_login, 1184 'first_name' => $user->user_firstname, 1185 'last_name' => $user->user_lastname, 1186 'registered' => $this->_convert_date( $user->user_registered ), 1187 'bio' => $user->user_description, 1188 'email' => $user->user_email, 1189 'nickname' => $user->nickname, 1190 'nicename' => $user->user_nicename, 1191 'url' => $user->user_url, 1192 'display_name' => $user->display_name, 1193 'roles' => $user->roles, 1194 ); 1195 1196 if ( in_array( 'all', $fields, true ) ) { 1197 $_user = array_merge( $_user, $user_fields ); 1198 } else { 1199 if ( in_array( 'basic', $fields, true ) ) { 1200 $basic_fields = array( 'username', 'email', 'registered', 'display_name', 'nicename' ); 1201 $fields = array_merge( $fields, $basic_fields ); 1202 } 1203 $requested_fields = array_intersect_key( $user_fields, array_flip( $fields ) ); 1204 $_user = array_merge( $_user, $requested_fields ); 1205 } 1206 1207 /** 1208 * Filters XML-RPC-prepared data for the given user. 1209 * 1210 * @since 3.5.0 1211 * 1212 * @param array $_user An array of user data. 1213 * @param WP_User $user User object. 1214 * @param array $fields An array of user fields. 1215 */ 1216 return apply_filters( 'xmlrpc_prepare_user', $_user, $user, $fields ); 1217 } 1218 1219 /** 1220 * Create a new post for any registered post type. 1221 * 1222 * @since 3.4.0 1223 * 1224 * @link https://en.wikipedia.org/wiki/RSS_enclosure for information on RSS enclosures. 1225 * 1226 * @param array $args { 1227 * Method arguments. Note: top-level arguments must be ordered as documented. 1228 * 1229 * @type int $blog_id Blog ID (unused). 1230 * @type string $username Username. 1231 * @type string $password Password. 1232 * @type array $content_struct { 1233 * Content struct for adding a new post. See wp_insert_post() for information on 1234 * additional post fields 1235 * 1236 * @type string $post_type Post type. Default 'post'. 1237 * @type string $post_status Post status. Default 'draft' 1238 * @type string $post_title Post title. 1239 * @type int $post_author Post author ID. 1240 * @type string $post_excerpt Post excerpt. 1241 * @type string $post_content Post content. 1242 * @type string $post_date_gmt Post date in GMT. 1243 * @type string $post_date Post date. 1244 * @type string $post_password Post password (20-character limit). 1245 * @type string $comment_status Post comment enabled status. Accepts 'open' or 'closed'. 1246 * @type string $ping_status Post ping status. Accepts 'open' or 'closed'. 1247 * @type bool $sticky Whether the post should be sticky. Automatically false if 1248 * `$post_status` is 'private'. 1249 * @type int $post_thumbnail ID of an image to use as the post thumbnail/featured image. 1250 * @type array $custom_fields Array of meta key/value pairs to add to the post. 1251 * @type array $terms Associative array with taxonomy names as keys and arrays 1252 * of term IDs as values. 1253 * @type array $terms_names Associative array with taxonomy names as keys and arrays 1254 * of term names as values. 1255 * @type array $enclosure { 1256 * Array of feed enclosure data to add to post meta. 1257 * 1258 * @type string $url URL for the feed enclosure. 1259 * @type int $length Size in bytes of the enclosure. 1260 * @type string $type Mime-type for the enclosure. 1261 * } 1262 * } 1263 * } 1264 * @return int|IXR_Error Post ID on success, IXR_Error instance otherwise. 1265 */ 1266 public function wp_newPost( $args ) { 1267 if ( ! $this->minimum_args( $args, 4 ) ) { 1268 return $this->error; 1269 } 1270 1271 $this->escape( $args ); 1272 1273 $username = $args[1]; 1274 $password = $args[2]; 1275 $content_struct = $args[3]; 1276 1277 $user = $this->login( $username, $password ); 1278 if ( ! $user ) { 1279 return $this->error; 1280 } 1281 1282 // Convert the date field back to IXR form. 1283 if ( isset( $content_struct['post_date'] ) && ! ( $content_struct['post_date'] instanceof IXR_Date ) ) { 1284 $content_struct['post_date'] = $this->_convert_date( $content_struct['post_date'] ); 1285 } 1286 1287 /* 1288 * Ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct, 1289 * since _insert_post() will ignore the non-GMT date if the GMT date is set. 1290 */ 1291 if ( isset( $content_struct['post_date_gmt'] ) && ! ( $content_struct['post_date_gmt'] instanceof IXR_Date ) ) { 1292 if ( '0000-00-00 00:00:00' === $content_struct['post_date_gmt'] || isset( $content_struct['post_date'] ) ) { 1293 unset( $content_struct['post_date_gmt'] ); 1294 } else { 1295 $content_struct['post_date_gmt'] = $this->_convert_date( $content_struct['post_date_gmt'] ); 1296 } 1297 } 1298 1299 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 1300 do_action( 'xmlrpc_call', 'wp.newPost', $args, $this ); 1301 1302 unset( $content_struct['ID'] ); 1303 1304 return $this->_insert_post( $user, $content_struct ); 1305 } 1306 1307 /** 1308 * Helper method for filtering out elements from an array. 1309 * 1310 * @since 3.4.0 1311 * 1312 * @param int $count Number to compare to one. 1313 * @return bool True if the number is greater than one, false otherwise. 1314 */ 1315 private function _is_greater_than_one( $count ) { 1316 return $count > 1; 1317 } 1318 1319 /** 1320 * Encapsulate the logic for sticking a post 1321 * and determining if the user has permission to do so 1322 * 1323 * @since 4.3.0 1324 * 1325 * @param array $post_data 1326 * @param bool $update 1327 * @return void|IXR_Error 1328 */ 1329 private function _toggle_sticky( $post_data, $update = false ) { 1330 $post_type = get_post_type_object( $post_data['post_type'] ); 1331 1332 // Private and password-protected posts cannot be stickied. 1333 if ( 'private' === $post_data['post_status'] || ! empty( $post_data['post_password'] ) ) { 1334 // Error if the client tried to stick the post, otherwise, silently unstick. 1335 if ( ! empty( $post_data['sticky'] ) ) { 1336 return new IXR_Error( 401, __( 'Sorry, you cannot stick a private post.' ) ); 1337 } 1338 1339 if ( $update ) { 1340 unstick_post( $post_data['ID'] ); 1341 } 1342 } elseif ( isset( $post_data['sticky'] ) ) { 1343 if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) { 1344 return new IXR_Error( 401, __( 'Sorry, you are not allowed to make posts sticky.' ) ); 1345 } 1346 1347 $sticky = wp_validate_boolean( $post_data['sticky'] ); 1348 if ( $sticky ) { 1349 stick_post( $post_data['ID'] ); 1350 } else { 1351 unstick_post( $post_data['ID'] ); 1352 } 1353 } 1354 } 1355 1356 /** 1357 * Helper method for wp_newPost() and wp_editPost(), containing shared logic. 1358 * 1359 * @since 3.4.0 1360 * 1361 * @see wp_insert_post() 1362 * 1363 * @param WP_User $user The post author if post_author isn't set in $content_struct. 1364 * @param array|IXR_Error $content_struct Post data to insert. 1365 * @return IXR_Error|string 1366 */ 1367 protected function _insert_post( $user, $content_struct ) { 1368 $defaults = array( 1369 'post_status' => 'draft', 1370 'post_type' => 'post', 1371 'post_author' => null, 1372 'post_password' => null, 1373 'post_excerpt' => null, 1374 'post_content' => null, 1375 'post_title' => null, 1376 'post_date' => null, 1377 'post_date_gmt' => null, 1378 'post_format' => null, 1379 'post_name' => null, 1380 'post_thumbnail' => null, 1381 'post_parent' => null, 1382 'ping_status' => null, 1383 'comment_status' => null, 1384 'custom_fields' => null, 1385 'terms_names' => null, 1386 'terms' => null, 1387 'sticky' => null, 1388 'enclosure' => null, 1389 'ID' => null, 1390 ); 1391 1392 $post_data = wp_parse_args( array_intersect_key( $content_struct, $defaults ), $defaults ); 1393 1394 $post_type = get_post_type_object( $post_data['post_type'] ); 1395 if ( ! $post_type ) { 1396 return new IXR_Error( 403, __( 'Invalid post type.' ) ); 1397 } 1398 1399 $update = ! empty( $post_data['ID'] ); 1400 1401 if ( $update ) { 1402 if ( ! get_post( $post_data['ID'] ) ) { 1403 return new IXR_Error( 401, __( 'Invalid post ID.' ) ); 1404 } 1405 if ( ! current_user_can( 'edit_post', $post_data['ID'] ) ) { 1406 return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) ); 1407 } 1408 if ( get_post_type( $post_data['ID'] ) !== $post_data['post_type'] ) { 1409 return new IXR_Error( 401, __( 'The post type may not be changed.' ) ); 1410 } 1411 } else { 1412 if ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( $post_type->cap->edit_posts ) ) { 1413 return new IXR_Error( 401, __( 'Sorry, you are not allowed to post on this site.' ) ); 1414 } 1415 } 1416 1417 switch ( $post_data['post_status'] ) { 1418 case 'draft': 1419 case 'pending': 1420 break; 1421 case 'private': 1422 if ( ! current_user_can( $post_type->cap->publish_posts ) ) { 1423 return new IXR_Error( 401, __( 'Sorry, you are not allowed to create private posts in this post type.' ) ); 1424 } 1425 break; 1426 case 'publish': 1427 case 'future': 1428 if ( ! current_user_can( $post_type->cap->publish_posts ) ) { 1429 return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts in this post type.' ) ); 1430 } 1431 break; 1432 default: 1433 if ( ! get_post_status_object( $post_data['post_status'] ) ) { 1434 $post_data['post_status'] = 'draft'; 1435 } 1436 break; 1437 } 1438 1439 if ( ! empty( $post_data['post_password'] ) && ! current_user_can( $post_type->cap->publish_posts ) ) { 1440 return new IXR_Error( 401, __( 'Sorry, you are not allowed to create password protected posts in this post type.' ) ); 1441 } 1442 1443 $post_data['post_author'] = absint( $post_data['post_author'] ); 1444 if ( ! empty( $post_data['post_author'] ) && $post_data['post_author'] != $user->ID ) { 1445 if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) { 1446 return new IXR_Error( 401, __( 'Sorry, you are not allowed to create posts as this user.' ) ); 1447 } 1448 1449 $author = get_userdata( $post_data['post_author'] ); 1450 1451 if ( ! $author ) { 1452 return new IXR_Error( 404, __( 'Invalid author ID.' ) ); 1453 } 1454 } else { 1455 $post_data['post_author'] = $user->ID; 1456 } 1457 1458 if ( isset( $post_data['comment_status'] ) && 'open' !== $post_data['comment_status'] && 'closed' !== $post_data['comment_status'] ) { 1459 unset( $post_data['comment_status'] ); 1460 } 1461 1462 if ( isset( $post_data['ping_status'] ) && 'open' !== $post_data['ping_status'] && 'closed' !== $post_data['ping_status'] ) { 1463 unset( $post_data['ping_status'] ); 1464 } 1465 1466 // Do some timestamp voodoo. 1467 if ( ! empty( $post_data['post_date_gmt'] ) ) { 1468 // We know this is supposed to be GMT, so we're going to slap that Z on there by force. 1469 $dateCreated = rtrim( $post_data['post_date_gmt']->getIso(), 'Z' ) . 'Z'; 1470 } elseif ( ! empty( $post_data['post_date'] ) ) { 1471 $dateCreated = $post_data['post_date']->getIso(); 1472 } 1473 1474 // Default to not flagging the post date to be edited unless it's intentional. 1475 $post_data['edit_date'] = false; 1476 1477 if ( ! empty( $dateCreated ) ) { 1478 $post_data['post_date'] = iso8601_to_datetime( $dateCreated ); 1479 $post_data['post_date_gmt'] = iso8601_to_datetime( $dateCreated, 'gmt' ); 1480 1481 // Flag the post date to be edited. 1482 $post_data['edit_date'] = true; 1483 } 1484 1485 if ( ! isset( $post_data['ID'] ) ) { 1486 $post_data['ID'] = get_default_post_to_edit( $post_data['post_type'], true )->ID; 1487 } 1488 $post_ID = $post_data['ID']; 1489 1490 if ( 'post' === $post_data['post_type'] ) { 1491 $error = $this->_toggle_sticky( $post_data, $update ); 1492 if ( $error ) { 1493 return $error; 1494 } 1495 } 1496 1497 if ( isset( $post_data['post_thumbnail'] ) ) { 1498 // Empty value deletes, non-empty value adds/updates. 1499 if ( ! $post_data['post_thumbnail'] ) { 1500 delete_post_thumbnail( $post_ID ); 1501 } elseif ( ! get_post( absint( $post_data['post_thumbnail'] ) ) ) { 1502 return new IXR_Error( 404, __( 'Invalid attachment ID.' ) ); 1503 } 1504 set_post_thumbnail( $post_ID, $post_data['post_thumbnail'] ); 1505 unset( $content_struct['post_thumbnail'] ); 1506 } 1507 1508 if ( isset( $post_data['custom_fields'] ) ) { 1509 $this->set_custom_fields( $post_ID, $post_data['custom_fields'] ); 1510 } 1511 1512 if ( isset( $post_data['terms'] ) || isset( $post_data['terms_names'] ) ) { 1513 $post_type_taxonomies = get_object_taxonomies( $post_data['post_type'], 'objects' ); 1514 1515 // Accumulate term IDs from terms and terms_names. 1516 $terms = array(); 1517 1518 // First validate the terms specified by ID. 1519 if ( isset( $post_data['terms'] ) && is_array( $post_data['terms'] ) ) { 1520 $taxonomies = array_keys( $post_data['terms'] ); 1521 1522 // Validating term IDs. 1523 foreach ( $taxonomies as $taxonomy ) { 1524 if ( ! array_key_exists( $taxonomy, $post_type_taxonomies ) ) { 1525 return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) ); 1526 } 1527 1528 if ( ! current_user_can( $post_type_taxonomies[ $taxonomy ]->cap->assign_terms ) ) { 1529 return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) ); 1530 } 1531 1532 $term_ids = $post_data['terms'][ $taxonomy ]; 1533 $terms[ $taxonomy ] = array(); 1534 foreach ( $term_ids as $term_id ) { 1535 $term = get_term_by( 'id', $term_id, $taxonomy ); 1536 1537 if ( ! $term ) { 1538 return new IXR_Error( 403, __( 'Invalid term ID.' ) ); 1539 } 1540 1541 $terms[ $taxonomy ][] = (int) $term_id; 1542 } 1543 } 1544 } 1545 1546 // Now validate terms specified by name. 1547 if ( isset( $post_data['terms_names'] ) && is_array( $post_data['terms_names'] ) ) { 1548 $taxonomies = array_keys( $post_data['terms_names'] ); 1549 1550 foreach ( $taxonomies as $taxonomy ) { 1551 if ( ! array_key_exists( $taxonomy, $post_type_taxonomies ) ) { 1552 return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) ); 1553 } 1554 1555 if ( ! current_user_can( $post_type_taxonomies[ $taxonomy ]->cap->assign_terms ) ) { 1556 return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) ); 1557 } 1558 1559 /* 1560 * For hierarchical taxonomies, we can't assign a term when multiple terms 1561 * in the hierarchy share the same name. 1562 */ 1563 $ambiguous_terms = array(); 1564 if ( is_taxonomy_hierarchical( $taxonomy ) ) { 1565 $tax_term_names = get_terms( 1566 array( 1567 'taxonomy' => $taxonomy, 1568 'fields' => 'names', 1569 'hide_empty' => false, 1570 ) 1571 ); 1572 1573 // Count the number of terms with the same name. 1574 $tax_term_names_count = array_count_values( $tax_term_names ); 1575 1576 // Filter out non-ambiguous term names. 1577 $ambiguous_tax_term_counts = array_filter( $tax_term_names_count, array( $this, '_is_greater_than_one' ) ); 1578 1579 $ambiguous_terms = array_keys( $ambiguous_tax_term_counts ); 1580 } 1581 1582 $term_names = $post_data['terms_names'][ $taxonomy ]; 1583 foreach ( $term_names as $term_name ) { 1584 if ( in_array( $term_name, $ambiguous_terms, true ) ) { 1585 return new IXR_Error( 401, __( 'Ambiguous term name used in a hierarchical taxonomy. Please use term ID instead.' ) ); 1586 } 1587 1588 $term = get_term_by( 'name', $term_name, $taxonomy ); 1589 1590 if ( ! $term ) { 1591 // Term doesn't exist, so check that the user is allowed to create new terms. 1592 if ( ! current_user_can( $post_type_taxonomies[ $taxonomy ]->cap->edit_terms ) ) { 1593 return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a term to one of the given taxonomies.' ) ); 1594 } 1595 1596 // Create the new term. 1597 $term_info = wp_insert_term( $term_name, $taxonomy ); 1598 if ( is_wp_error( $term_info ) ) { 1599 return new IXR_Error( 500, $term_info->get_error_message() ); 1600 } 1601 1602 $terms[ $taxonomy ][] = (int) $term_info['term_id']; 1603 } else { 1604 $terms[ $taxonomy ][] = (int) $term->term_id; 1605 } 1606 } 1607 } 1608 } 1609 1610 $post_data['tax_input'] = $terms; 1611 unset( $post_data['terms'], $post_data['terms_names'] ); 1612 } 1613 1614 if ( isset( $post_data['post_format'] ) ) { 1615 $format = set_post_format( $post_ID, $post_data['post_format'] ); 1616 1617 if ( is_wp_error( $format ) ) { 1618 return new IXR_Error( 500, $format->get_error_message() ); 1619 } 1620 1621 unset( $post_data['post_format'] ); 1622 } 1623 1624 // Handle enclosures. 1625 $enclosure = isset( $post_data['enclosure'] ) ? $post_data['enclosure'] : null; 1626 $this->add_enclosure_if_new( $post_ID, $enclosure ); 1627 1628 $this->attach_uploads( $post_ID, $post_data['post_content'] ); 1629 1630 /** 1631 * Filters post data array to be inserted via XML-RPC. 1632 * 1633 * @since 3.4.0 1634 * 1635 * @param array $post_data Parsed array of post data. 1636 * @param array $content_struct Post data array. 1637 */ 1638 $post_data = apply_filters( 'xmlrpc_wp_insert_post_data', $post_data, $content_struct ); 1639 1640 $post_ID = $update ? wp_update_post( $post_data, true ) : wp_insert_post( $post_data, true ); 1641 if ( is_wp_error( $post_ID ) ) { 1642 return new IXR_Error( 500, $post_ID->get_error_message() ); 1643 } 1644 1645 if ( ! $post_ID ) { 1646 if ( $update ) { 1647 return new IXR_Error( 401, __( 'Sorry, the post could not be updated.' ) ); 1648 } else { 1649 return new IXR_Error( 401, __( 'Sorry, the post could not be created.' ) ); 1650 } 1651 } 1652 1653 return (string) $post_ID; 1654 } 1655 1656 /** 1657 * Edit a post for any registered post type. 1658 * 1659 * The $content_struct parameter only needs to contain fields that 1660 * should be changed. All other fields will retain their existing values. 1661 * 1662 * @since 3.4.0 1663 * 1664 * @param array $args { 1665 * Method arguments. Note: arguments must be ordered as documented. 1666 * 1667 * @type int $blog_id Blog ID (unused). 1668 * @type string $username Username. 1669 * @type string $password Password. 1670 * @type int $post_id Post ID. 1671 * @type array $content_struct Extra content arguments. 1672 * } 1673 * @return true|IXR_Error True on success, IXR_Error on failure. 1674 */ 1675 public function wp_editPost( $args ) { 1676 if ( ! $this->minimum_args( $args, 5 ) ) { 1677 return $this->error; 1678 } 1679 1680 $this->escape( $args ); 1681 1682 $username = $args[1]; 1683 $password = $args[2]; 1684 $post_id = (int) $args[3]; 1685 $content_struct = $args[4]; 1686 1687 $user = $this->login( $username, $password ); 1688 if ( ! $user ) { 1689 return $this->error; 1690 } 1691 1692 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 1693 do_action( 'xmlrpc_call', 'wp.editPost', $args, $this ); 1694 1695 $post = get_post( $post_id, ARRAY_A ); 1696 1697 if ( empty( $post['ID'] ) ) { 1698 return new IXR_Error( 404, __( 'Invalid post ID.' ) ); 1699 } 1700 1701 if ( isset( $content_struct['if_not_modified_since'] ) ) { 1702 // If the post has been modified since the date provided, return an error. 1703 if ( mysql2date( 'U', $post['post_modified_gmt'] ) > $content_struct['if_not_modified_since']->getTimestamp() ) { 1704 return new IXR_Error( 409, __( 'There is a revision of this post that is more recent.' ) ); 1705 } 1706 } 1707 1708 // Convert the date field back to IXR form. 1709 $post['post_date'] = $this->_convert_date( $post['post_date'] ); 1710 1711 /* 1712 * Ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct, 1713 * since _insert_post() will ignore the non-GMT date if the GMT date is set. 1714 */ 1715 if ( '0000-00-00 00:00:00' === $post['post_date_gmt'] || isset( $content_struct['post_date'] ) ) { 1716 unset( $post['post_date_gmt'] ); 1717 } else { 1718 $post['post_date_gmt'] = $this->_convert_date( $post['post_date_gmt'] ); 1719 } 1720 1721 /* 1722 * If the API client did not provide 'post_date', then we must not perpetuate the value that 1723 * was stored in the database, or it will appear to be an intentional edit. Conveying it here 1724 * as if it was coming from the API client will cause an otherwise zeroed out 'post_date_gmt' 1725 * to get set with the value that was originally stored in the database when the draft was created. 1726 */ 1727 if ( ! isset( $content_struct['post_date'] ) ) { 1728 unset( $post['post_date'] ); 1729 } 1730 1731 $this->escape( $post ); 1732 $merged_content_struct = array_merge( $post, $content_struct ); 1733 1734 $retval = $this->_insert_post( $user, $merged_content_struct ); 1735 if ( $retval instanceof IXR_Error ) { 1736 return $retval; 1737 } 1738 1739 return true; 1740 } 1741 1742 /** 1743 * Delete a post for any registered post type. 1744 * 1745 * @since 3.4.0 1746 * 1747 * @see wp_delete_post() 1748 * 1749 * @param array $args { 1750 * Method arguments. Note: arguments must be ordered as documented. 1751 * 1752 * @type int $blog_id Blog ID (unused). 1753 * @type string $username Username. 1754 * @type string $password Password. 1755 * @type int $post_id Post ID. 1756 * } 1757 * @return true|IXR_Error True on success, IXR_Error instance on failure. 1758 */ 1759 public function wp_deletePost( $args ) { 1760 if ( ! $this->minimum_args( $args, 4 ) ) { 1761 return $this->error; 1762 } 1763 1764 $this->escape( $args ); 1765 1766 $username = $args[1]; 1767 $password = $args[2]; 1768 $post_id = (int) $args[3]; 1769 1770 $user = $this->login( $username, $password ); 1771 if ( ! $user ) { 1772 return $this->error; 1773 } 1774 1775 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 1776 do_action( 'xmlrpc_call', 'wp.deletePost', $args, $this ); 1777 1778 $post = get_post( $post_id, ARRAY_A ); 1779 if ( empty( $post['ID'] ) ) { 1780 return new IXR_Error( 404, __( 'Invalid post ID.' ) ); 1781 } 1782 1783 if ( ! current_user_can( 'delete_post', $post_id ) ) { 1784 return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this post.' ) ); 1785 } 1786 1787 $result = wp_delete_post( $post_id ); 1788 1789 if ( ! $result ) { 1790 return new IXR_Error( 500, __( 'Sorry, the post could not be deleted.' ) ); 1791 } 1792 1793 return true; 1794 } 1795 1796 /** 1797 * Retrieve a post. 1798 * 1799 * @since 3.4.0 1800 * 1801 * The optional $fields parameter specifies what fields will be included 1802 * in the response array. This should be a list of field names. 'post_id' will 1803 * always be included in the response regardless of the value of $fields. 1804 * 1805 * Instead of, or in addition to, individual field names, conceptual group 1806 * names can be used to specify multiple fields. The available conceptual 1807 * groups are 'post' (all basic fields), 'taxonomies', 'custom_fields', 1808 * and 'enclosure'. 1809 * 1810 * @see get_post() 1811 * 1812 * @param array $args { 1813 * Method arguments. Note: arguments must be ordered as documented. 1814 * 1815 * @type int $blog_id Blog ID (unused). 1816 * @type string $username Username. 1817 * @type string $password Password. 1818 * @type int $post_id Post ID. 1819 * @type array $fields The subset of post type fields to return. 1820 * } 1821 * @return array|IXR_Error Array contains (based on $fields parameter): 1822 * - 'post_id' 1823 * - 'post_title' 1824 * - 'post_date' 1825 * - 'post_date_gmt' 1826 * - 'post_modified' 1827 * - 'post_modified_gmt' 1828 * - 'post_status' 1829 * - 'post_type' 1830 * - 'post_name' 1831 * - 'post_author' 1832 * - 'post_password' 1833 * - 'post_excerpt' 1834 * - 'post_content' 1835 * - 'link' 1836 * - 'comment_status' 1837 * - 'ping_status' 1838 * - 'sticky' 1839 * - 'custom_fields' 1840 * - 'terms' 1841 * - 'categories' 1842 * - 'tags' 1843 * - 'enclosure' 1844 */ 1845 public function wp_getPost( $args ) { 1846 if ( ! $this->minimum_args( $args, 4 ) ) { 1847 return $this->error; 1848 } 1849 1850 $this->escape( $args ); 1851 1852 $username = $args[1]; 1853 $password = $args[2]; 1854 $post_id = (int) $args[3]; 1855 1856 if ( isset( $args[4] ) ) { 1857 $fields = $args[4]; 1858 } else { 1859 /** 1860 * Filters the list of post query fields used by the given XML-RPC method. 1861 * 1862 * @since 3.4.0 1863 * 1864 * @param array $fields Array of post fields. Default array contains 'post', 'terms', and 'custom_fields'. 1865 * @param string $method Method name. 1866 */ 1867 $fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPost' ); 1868 } 1869 1870 $user = $this->login( $username, $password ); 1871 if ( ! $user ) { 1872 return $this->error; 1873 } 1874 1875 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 1876 do_action( 'xmlrpc_call', 'wp.getPost', $args, $this ); 1877 1878 $post = get_post( $post_id, ARRAY_A ); 1879 1880 if ( empty( $post['ID'] ) ) { 1881 return new IXR_Error( 404, __( 'Invalid post ID.' ) ); 1882 } 1883 1884 if ( ! current_user_can( 'edit_post', $post_id ) ) { 1885 return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) ); 1886 } 1887 1888 return $this->_prepare_post( $post, $fields ); 1889 } 1890 1891 /** 1892 * Retrieve posts. 1893 * 1894 * @since 3.4.0 1895 * 1896 * @see wp_get_recent_posts() 1897 * @see wp_getPost() for more on `$fields` 1898 * @see get_posts() for more on `$filter` values 1899 * 1900 * @param array $args { 1901 * Method arguments. Note: arguments must be ordered as documented. 1902 * 1903 * @type int $blog_id Blog ID (unused). 1904 * @type string $username Username. 1905 * @type string $password Password. 1906 * @type array $filter Optional. Modifies the query used to retrieve posts. Accepts 'post_type', 1907 * 'post_status', 'number', 'offset', 'orderby', 's', and 'order'. 1908 * Default empty array. 1909 * @type array $fields Optional. The subset of post type fields to return in the response array. 1910 * } 1911 * @return array|IXR_Error Array contains a collection of posts. 1912 */ 1913 public function wp_getPosts( $args ) { 1914 if ( ! $this->minimum_args( $args, 3 ) ) { 1915 return $this->error; 1916 } 1917 1918 $this->escape( $args ); 1919 1920 $username = $args[1]; 1921 $password = $args[2]; 1922 $filter = isset( $args[3] ) ? $args[3] : array(); 1923 1924 if ( isset( $args[4] ) ) { 1925 $fields = $args[4]; 1926 } else { 1927 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 1928 $fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPosts' ); 1929 } 1930 1931 $user = $this->login( $username, $password ); 1932 if ( ! $user ) { 1933 return $this->error; 1934 } 1935 1936 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 1937 do_action( 'xmlrpc_call', 'wp.getPosts', $args, $this ); 1938 1939 $query = array(); 1940 1941 if ( isset( $filter['post_type'] ) ) { 1942 $post_type = get_post_type_object( $filter['post_type'] ); 1943 if ( ! ( (bool) $post_type ) ) { 1944 return new IXR_Error( 403, __( 'Invalid post type.' ) ); 1945 } 1946 } else { 1947 $post_type = get_post_type_object( 'post' ); 1948 } 1949 1950 if ( ! current_user_can( $post_type->cap->edit_posts ) ) { 1951 return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type.' ) ); 1952 } 1953 1954 $query['post_type'] = $post_type->name; 1955 1956 if ( isset( $filter['post_status'] ) ) { 1957 $query['post_status'] = $filter['post_status']; 1958 } 1959 1960 if ( isset( $filter['number'] ) ) { 1961 $query['numberposts'] = absint( $filter['number'] ); 1962 } 1963 1964 if ( isset( $filter['offset'] ) ) { 1965 $query['offset'] = absint( $filter['offset'] ); 1966 } 1967 1968 if ( isset( $filter['orderby'] ) ) { 1969 $query['orderby'] = $filter['orderby']; 1970 1971 if ( isset( $filter['order'] ) ) { 1972 $query['order'] = $filter['order']; 1973 } 1974 } 1975 1976 if ( isset( $filter['s'] ) ) { 1977 $query['s'] = $filter['s']; 1978 } 1979 1980 $posts_list = wp_get_recent_posts( $query ); 1981 1982 if ( ! $posts_list ) { 1983 return array(); 1984 } 1985 1986 // Holds all the posts data. 1987 $struct = array(); 1988 1989 foreach ( $posts_list as $post ) { 1990 if ( ! current_user_can( 'edit_post', $post['ID'] ) ) { 1991 continue; 1992 } 1993 1994 $struct[] = $this->_prepare_post( $post, $fields ); 1995 } 1996 1997 return $struct; 1998 } 1999 2000 /** 2001 * Create a new term. 2002 * 2003 * @since 3.4.0 2004 * 2005 * @see wp_insert_term() 2006 * 2007 * @param array $args { 2008 * Method arguments. Note: arguments must be ordered as documented. 2009 * 2010 * @type int $blog_id Blog ID (unused). 2011 * @type string $username Username. 2012 * @type string $password Password. 2013 * @type array $content_struct Content struct for adding a new term. The struct must contain 2014 * the term 'name' and 'taxonomy'. Optional accepted values include 2015 * 'parent', 'description', and 'slug'. 2016 * } 2017 * @return int|IXR_Error The term ID on success, or an IXR_Error object on failure. 2018 */ 2019 public function wp_newTerm( $args ) { 2020 if ( ! $this->minimum_args( $args, 4 ) ) { 2021 return $this->error; 2022 } 2023 2024 $this->escape( $args ); 2025 2026 $username = $args[1]; 2027 $password = $args[2]; 2028 $content_struct = $args[3]; 2029 2030 $user = $this->login( $username, $password ); 2031 if ( ! $user ) { 2032 return $this->error; 2033 } 2034 2035 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 2036 do_action( 'xmlrpc_call', 'wp.newTerm', $args, $this ); 2037 2038 if ( ! taxonomy_exists( $content_struct['taxonomy'] ) ) { 2039 return new IXR_Error( 403, __( 'Invalid taxonomy.' ) ); 2040 } 2041 2042 $taxonomy = get_taxonomy( $content_struct['taxonomy'] ); 2043 2044 if ( ! current_user_can( $taxonomy->cap->edit_terms ) ) { 2045 return new IXR_Error( 401, __( 'Sorry, you are not allowed to create terms in this taxonomy.' ) ); 2046 } 2047 2048 $taxonomy = (array) $taxonomy; 2049 2050 // Hold the data of the term. 2051 $term_data = array(); 2052 2053 $term_data['name'] = trim( $content_struct['name'] ); 2054 if ( empty( $term_data['name'] ) ) { 2055 return new IXR_Error( 403, __( 'The term name cannot be empty.' ) ); 2056 } 2057 2058 if ( isset( $content_struct['parent'] ) ) { 2059 if ( ! $taxonomy['hierarchical'] ) { 2060 return new IXR_Error( 403, __( 'This taxonomy is not hierarchical.' ) ); 2061 } 2062 2063 $parent_term_id = (int) $content_struct['parent']; 2064 $parent_term = get_term( $parent_term_id, $taxonomy['name'] ); 2065 2066 if ( is_wp_error( $parent_term ) ) { 2067 return new IXR_Error( 500, $parent_term->get_error_message() ); 2068 } 2069 2070 if ( ! $parent_term ) { 2071 return new IXR_Error( 403, __( 'Parent term does not exist.' ) ); 2072 } 2073 2074 $term_data['parent'] = $content_struct['parent']; 2075 } 2076 2077 if ( isset( $content_struct['description'] ) ) { 2078 $term_data['description'] = $content_struct['description']; 2079 } 2080 2081 if ( isset( $content_struct['slug'] ) ) { 2082 $term_data['slug'] = $content_struct['slug']; 2083 } 2084 2085 $term = wp_insert_term( $term_data['name'], $taxonomy['name'], $term_data ); 2086 2087 if ( is_wp_error( $term ) ) { 2088 return new IXR_Error( 500, $term->get_error_message() ); 2089 } 2090 2091 if ( ! $term ) { 2092 return new IXR_Error( 500, __( 'Sorry, the term could not be created.' ) ); 2093 } 2094 2095 // Add term meta. 2096 if ( isset( $content_struct['custom_fields'] ) ) { 2097 $this->set_term_custom_fields( $term['term_id'], $content_struct['custom_fields'] ); 2098 } 2099 2100 return (string) $term['term_id']; 2101 } 2102 2103 /** 2104 * Edit a term. 2105 * 2106 * @since 3.4.0 2107 * 2108 * @see wp_update_term() 2109 * 2110 * @param array $args { 2111 * Method arguments. Note: arguments must be ordered as documented. 2112 * 2113 * @type int $blog_id Blog ID (unused). 2114 * @type string $username Username. 2115 * @type string $password Password. 2116 * @type int $term_id Term ID. 2117 * @type array $content_struct Content struct for editing a term. The struct must contain the 2118 * term ''taxonomy'. Optional accepted values include 'name', 'parent', 2119 * 'description', and 'slug'. 2120 * } 2121 * @return true|IXR_Error True on success, IXR_Error instance on failure. 2122 */ 2123 public function wp_editTerm( $args ) { 2124 if ( ! $this->minimum_args( $args, 5 ) ) { 2125 return $this->error; 2126 } 2127 2128 $this->escape( $args ); 2129 2130 $username = $args[1]; 2131 $password = $args[2]; 2132 $term_id = (int) $args[3]; 2133 $content_struct = $args[4]; 2134 2135 $user = $this->login( $username, $password ); 2136 if ( ! $user ) { 2137 return $this->error; 2138 } 2139 2140 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 2141 do_action( 'xmlrpc_call', 'wp.editTerm', $args, $this ); 2142 2143 if ( ! taxonomy_exists( $content_struct['taxonomy'] ) ) { 2144 return new IXR_Error( 403, __( 'Invalid taxonomy.' ) ); 2145 } 2146 2147 $taxonomy = get_taxonomy( $content_struct['taxonomy'] ); 2148 2149 $taxonomy = (array) $taxonomy; 2150 2151 // Hold the data of the term. 2152 $term_data = array(); 2153 2154 $term = get_term( $term_id, $content_struct['taxonomy'] ); 2155 2156 if ( is_wp_error( $term ) ) { 2157 return new IXR_Error( 500, $term->get_error_message() ); 2158 } 2159 2160 if ( ! $term ) { 2161 return new IXR_Error( 404, __( 'Invalid term ID.' ) ); 2162 } 2163 2164 if ( ! current_user_can( 'edit_term', $term_id ) ) { 2165 return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this term.' ) ); 2166 } 2167 2168 if ( isset( $content_struct['name'] ) ) { 2169 $term_data['name'] = trim( $content_struct['name'] ); 2170 2171 if ( empty( $term_data['name'] ) ) { 2172 return new IXR_Error( 403, __( 'The term name cannot be empty.' ) ); 2173 } 2174 } 2175 2176 if ( ! empty( $content_struct['parent'] ) ) { 2177 if ( ! $taxonomy['hierarchical'] ) { 2178 return new IXR_Error( 403, __( 'Cannot set parent term, taxonomy is not hierarchical.' ) ); 2179 } 2180 2181 $parent_term_id = (int) $content_struct['parent']; 2182 $parent_term = get_term( $parent_term_id, $taxonomy['name'] ); 2183 2184 if ( is_wp_error( $parent_term ) ) { 2185 return new IXR_Error( 500, $parent_term->get_error_message() ); 2186 } 2187 2188 if ( ! $parent_term ) { 2189 return new IXR_Error( 403, __( 'Parent term does not exist.' ) ); 2190 } 2191 2192 $term_data['parent'] = $content_struct['parent']; 2193 } 2194 2195 if ( isset( $content_struct['description'] ) ) { 2196 $term_data['description'] = $content_struct['description']; 2197 } 2198 2199 if ( isset( $content_struct['slug'] ) ) { 2200 $term_data['slug'] = $content_struct['slug']; 2201 } 2202 2203 $term = wp_update_term( $term_id, $taxonomy['name'], $term_data ); 2204 2205 if ( is_wp_error( $term ) ) { 2206 return new IXR_Error( 500, $term->get_error_message() ); 2207 } 2208 2209 if ( ! $term ) { 2210 return new IXR_Error( 500, __( 'Sorry, editing the term failed.' ) ); 2211 } 2212 2213 // Update term meta. 2214 if ( isset( $content_struct['custom_fields'] ) ) { 2215 $this->set_term_custom_fields( $term_id, $content_struct['custom_fields'] ); 2216 } 2217 2218 return true; 2219 } 2220 2221 /** 2222 * Delete a term. 2223 * 2224 * @since 3.4.0 2225 * 2226 * @see wp_delete_term() 2227 * 2228 * @param array $args { 2229 * Method arguments. Note: arguments must be ordered as documented. 2230 * 2231 * @type int $blog_id Blog ID (unused). 2232 * @type string $username Username. 2233 * @type string $password Password. 2234 * @type string $taxnomy_name Taxonomy name. 2235 * @type int $term_id Term ID. 2236 * } 2237 * @return true|IXR_Error True on success, IXR_Error instance on failure. 2238 */ 2239 public function wp_deleteTerm( $args ) { 2240 if ( ! $this->minimum_args( $args, 5 ) ) { 2241 return $this->error; 2242 } 2243 2244 $this->escape( $args ); 2245 2246 $username = $args[1]; 2247 $password = $args[2]; 2248 $taxonomy = $args[3]; 2249 $term_id = (int) $args[4]; 2250 2251 $user = $this->login( $username, $password ); 2252 if ( ! $user ) { 2253 return $this->error; 2254 } 2255 2256 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 2257 do_action( 'xmlrpc_call', 'wp.deleteTerm', $args, $this ); 2258 2259 if ( ! taxonomy_exists( $taxonomy ) ) { 2260 return new IXR_Error( 403, __( 'Invalid taxonomy.' ) ); 2261 } 2262 2263 $taxonomy = get_taxonomy( $taxonomy ); 2264 $term = get_term( $term_id, $taxonomy->name ); 2265 2266 if ( is_wp_error( $term ) ) { 2267 return new IXR_Error( 500, $term->get_error_message() ); 2268 } 2269 2270 if ( ! $term ) { 2271 return new IXR_Error( 404, __( 'Invalid term ID.' ) ); 2272 } 2273 2274 if ( ! current_user_can( 'delete_term', $term_id ) ) { 2275 return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this term.' ) ); 2276 } 2277 2278 $result = wp_delete_term( $term_id, $taxonomy->name ); 2279 2280 if ( is_wp_error( $result ) ) { 2281 return new IXR_Error( 500, $term->get_error_message() ); 2282 } 2283 2284 if ( ! $result ) { 2285 return new IXR_Error( 500, __( 'Sorry, deleting the term failed.' ) ); 2286 } 2287 2288 return $result; 2289 } 2290 2291 /** 2292 * Retrieve a term. 2293 * 2294 * @since 3.4.0 2295 * 2296 * @see get_term() 2297 * 2298 * @param array $args { 2299 * Method arguments. Note: arguments must be ordered as documented. 2300 * 2301 * @type int $blog_id Blog ID (unused). 2302 * @type string $username Username. 2303 * @type string $password Password. 2304 * @type string $taxnomy Taxonomy name. 2305 * @type string $term_id Term ID. 2306 * } 2307 * @return array|IXR_Error IXR_Error on failure, array on success, containing: 2308 * - 'term_id' 2309 * - 'name' 2310 * - 'slug' 2311 * - 'term_group' 2312 * - 'term_taxonomy_id' 2313 * - 'taxonomy' 2314 * - 'description' 2315 * - 'parent' 2316 * - 'count' 2317 */ 2318 public function wp_getTerm( $args ) { 2319 if ( ! $this->minimum_args( $args, 5 ) ) { 2320 return $this->error; 2321 } 2322 2323 $this->escape( $args ); 2324 2325 $username = $args[1]; 2326 $password = $args[2]; 2327 $taxonomy = $args[3]; 2328 $term_id = (int) $args[4]; 2329 2330 $user = $this->login( $username, $password ); 2331 if ( ! $user ) { 2332 return $this->error; 2333 } 2334 2335 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 2336 do_action( 'xmlrpc_call', 'wp.getTerm', $args, $this ); 2337 2338 if ( ! taxonomy_exists( $taxonomy ) ) { 2339 return new IXR_Error( 403, __( 'Invalid taxonomy.' ) ); 2340 } 2341 2342 $taxonomy = get_taxonomy( $taxonomy ); 2343 2344 $term = get_term( $term_id, $taxonomy->name, ARRAY_A ); 2345 2346 if ( is_wp_error( $term ) ) { 2347 return new IXR_Error( 500, $term->get_error_message() ); 2348 } 2349 2350 if ( ! $term ) { 2351 return new IXR_Error( 404, __( 'Invalid term ID.' ) ); 2352 } 2353 2354 if ( ! current_user_can( 'assign_term', $term_id ) ) { 2355 return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign this term.' ) ); 2356 } 2357 2358 return $this->_prepare_term( $term ); 2359 } 2360 2361 /** 2362 * Retrieve all terms for a taxonomy. 2363 * 2364 * @since 3.4.0 2365 * 2366 * The optional $filter parameter modifies the query used to retrieve terms. 2367 * Accepted keys are 'number', 'offset', 'orderby', 'order', 'hide_empty', and 'search'. 2368 * 2369 * @see get_terms() 2370 * 2371 * @param array $args { 2372 * Method arguments. Note: arguments must be ordered as documented. 2373 * 2374 * @type int $blog_id Blog ID (unused). 2375 * @type string $username Username. 2376 * @type string $password Password. 2377 * @type string $taxnomy Taxonomy name. 2378 * @type array $filter Optional. Modifies the query used to retrieve posts. Accepts 'number', 2379 * 'offset', 'orderby', 'order', 'hide_empty', and 'search'. Default empty array. 2380 * } 2381 * @return array|IXR_Error An associative array of terms data on success, IXR_Error instance otherwise. 2382 */ 2383 public function wp_getTerms( $args ) { 2384 if ( ! $this->minimum_args( $args, 4 ) ) { 2385 return $this->error; 2386 } 2387 2388 $this->escape( $args ); 2389 2390 $username = $args[1]; 2391 $password = $args[2]; 2392 $taxonomy = $args[3]; 2393 $filter = isset( $args[4] ) ? $args[4] : array(); 2394 2395 $user = $this->login( $username, $password ); 2396 if ( ! $user ) { 2397 return $this->error; 2398 } 2399 2400 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 2401 do_action( 'xmlrpc_call', 'wp.getTerms', $args, $this ); 2402 2403 if ( ! taxonomy_exists( $taxonomy ) ) { 2404 return new IXR_Error( 403, __( 'Invalid taxonomy.' ) ); 2405 } 2406 2407 $taxonomy = get_taxonomy( $taxonomy ); 2408 2409 if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) { 2410 return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) ); 2411 } 2412 2413 $query = array( 'taxonomy' => $taxonomy->name ); 2414 2415 if ( isset( $filter['number'] ) ) { 2416 $query['number'] = absint( $filter['number'] ); 2417 } 2418 2419 if ( isset( $filter['offset'] ) ) { 2420 $query['offset'] = absint( $filter['offset'] ); 2421 } 2422 2423 if ( isset( $filter['orderby'] ) ) { 2424 $query['orderby'] = $filter['orderby']; 2425 2426 if ( isset( $filter['order'] ) ) { 2427 $query['order'] = $filter['order']; 2428 } 2429 } 2430 2431 if ( isset( $filter['hide_empty'] ) ) { 2432 $query['hide_empty'] = $filter['hide_empty']; 2433 } else { 2434 $query['get'] = 'all'; 2435 } 2436 2437 if ( isset( $filter['search'] ) ) { 2438 $query['search'] = $filter['search']; 2439 } 2440 2441 $terms = get_terms( $query ); 2442 2443 if ( is_wp_error( $terms ) ) { 2444 return new IXR_Error( 500, $terms->get_error_message() ); 2445 } 2446 2447 $struct = array(); 2448 2449 foreach ( $terms as $term ) { 2450 $struct[] = $this->_prepare_term( $term ); 2451 } 2452 2453 return $struct; 2454 } 2455 2456 /** 2457 * Retrieve a taxonomy. 2458 * 2459 * @since 3.4.0 2460 * 2461 * @see get_taxonomy() 2462 * 2463 * @param array $args { 2464 * Method arguments. Note: arguments must be ordered as documented. 2465 * 2466 * @type int $blog_id Blog ID (unused). 2467 * @type string $username Username. 2468 * @type string $password Password. 2469 * @type string $taxnomy Taxonomy name. 2470 * @type array $fields Optional. Array of taxonomy fields to limit to in the return. 2471 * Accepts 'labels', 'cap', 'menu', and 'object_type'. 2472 * Default empty array. 2473 * } 2474 * @return array|IXR_Error An array of taxonomy data on success, IXR_Error instance otherwise. 2475 */ 2476 public function wp_getTaxonomy( $args ) { 2477 if ( ! $this->minimum_args( $args, 4 ) ) { 2478 return $this->error; 2479 } 2480 2481 $this->escape( $args ); 2482 2483 $username = $args[1]; 2484 $password = $args[2]; 2485 $taxonomy = $args[3]; 2486 2487 if ( isset( $args[4] ) ) { 2488 $fields = $args[4]; 2489 } else { 2490 /** 2491 * Filters the taxonomy query fields used by the given XML-RPC method. 2492 * 2493 * @since 3.4.0 2494 * 2495 * @param array $fields An array of taxonomy fields to retrieve. 2496 * @param string $method The method name. 2497 */ 2498 $fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomy' ); 2499 } 2500 2501 $user = $this->login( $username, $password ); 2502 if ( ! $user ) { 2503 return $this->error; 2504 } 2505 2506 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 2507 do_action( 'xmlrpc_call', 'wp.getTaxonomy', $args, $this ); 2508 2509 if ( ! taxonomy_exists( $taxonomy ) ) { 2510 return new IXR_Error( 403, __( 'Invalid taxonomy.' ) ); 2511 } 2512 2513 $taxonomy = get_taxonomy( $taxonomy ); 2514 2515 if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) { 2516 return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) ); 2517 } 2518 2519 return $this->_prepare_taxonomy( $taxonomy, $fields ); 2520 } 2521 2522 /** 2523 * Retrieve all taxonomies. 2524 * 2525 * @since 3.4.0 2526 * 2527 * @see get_taxonomies() 2528 * 2529 * @param array $args { 2530 * Method arguments. Note: arguments must be ordered as documented. 2531 * 2532 * @type int $blog_id Blog ID (unused). 2533 * @type string $username Username. 2534 * @type string $password Password. 2535 * @type array $filter Optional. An array of arguments for retrieving taxonomies. 2536 * @type array $fields Optional. The subset of taxonomy fields to return. 2537 * } 2538 * @return array|IXR_Error An associative array of taxonomy data with returned fields determined 2539 * by `$fields`, or an IXR_Error instance on failure. 2540 */ 2541 public function wp_getTaxonomies( $args ) { 2542 if ( ! $this->minimum_args( $args, 3 ) ) { 2543 return $this->error; 2544 } 2545 2546 $this->escape( $args ); 2547 2548 $username = $args[1]; 2549 $password = $args[2]; 2550 $filter = isset( $args[3] ) ? $args[3] : array( 'public' => true ); 2551 2552 if ( isset( $args[4] ) ) { 2553 $fields = $args[4]; 2554 } else { 2555 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 2556 $fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomies' ); 2557 } 2558 2559 $user = $this->login( $username, $password ); 2560 if ( ! $user ) { 2561 return $this->error; 2562 } 2563 2564 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 2565 do_action( 'xmlrpc_call', 'wp.getTaxonomies', $args, $this ); 2566 2567 $taxonomies = get_taxonomies( $filter, 'objects' ); 2568 2569 // Holds all the taxonomy data. 2570 $struct = array(); 2571 2572 foreach ( $taxonomies as $taxonomy ) { 2573 // Capability check for post types. 2574 if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) { 2575 continue; 2576 } 2577 2578 $struct[] = $this->_prepare_taxonomy( $taxonomy, $fields ); 2579 } 2580 2581 return $struct; 2582 } 2583 2584 /** 2585 * Retrieve a user. 2586 * 2587 * The optional $fields parameter specifies what fields will be included 2588 * in the response array. This should be a list of field names. 'user_id' will 2589 * always be included in the response regardless of the value of $fields. 2590 * 2591 * Instead of, or in addition to, individual field names, conceptual group 2592 * names can be used to specify multiple fields. The available conceptual 2593 * groups are 'basic' and 'all'. 2594 * 2595 * @uses get_userdata() 2596 * 2597 * @param array $args { 2598 * Method arguments. Note: arguments must be ordered as documented. 2599 * 2600 * @type int $blog_id (unused) 2601 * @type string $username 2602 * @type string $password 2603 * @type int $user_id 2604 * @type array $fields (optional) 2605 * } 2606 * @return array|IXR_Error Array contains (based on $fields parameter): 2607 * - 'user_id' 2608 * - 'username' 2609 * - 'first_name' 2610 * - 'last_name' 2611 * - 'registered' 2612 * - 'bio' 2613 * - 'email' 2614 * - 'nickname' 2615 * - 'nicename' 2616 * - 'url' 2617 * - 'display_name' 2618 * - 'roles' 2619 */ 2620 public function wp_getUser( $args ) { 2621 if ( ! $this->minimum_args( $args, 4 ) ) { 2622 return $this->error; 2623 } 2624 2625 $this->escape( $args ); 2626 2627 $username = $args[1]; 2628 $password = $args[2]; 2629 $user_id = (int) $args[3]; 2630 2631 if ( isset( $args[4] ) ) { 2632 $fields = $args[4]; 2633 } else { 2634 /** 2635 * Filters the default user query fields used by the given XML-RPC method. 2636 * 2637 * @since 3.5.0 2638 * 2639 * @param array $fields User query fields for given method. Default 'all'. 2640 * @param string $method The method name. 2641 */ 2642 $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUser' ); 2643 } 2644 2645 $user = $this->login( $username, $password ); 2646 if ( ! $user ) { 2647 return $this->error; 2648 } 2649 2650 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 2651 do_action( 'xmlrpc_call', 'wp.getUser', $args, $this ); 2652 2653 if ( ! current_user_can( 'edit_user', $user_id ) ) { 2654 return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this user.' ) ); 2655 } 2656 2657 $user_data = get_userdata( $user_id ); 2658 2659 if ( ! $user_data ) { 2660 return new IXR_Error( 404, __( 'Invalid user ID.' ) ); 2661 } 2662 2663 return $this->_prepare_user( $user_data, $fields ); 2664 } 2665 2666 /** 2667 * Retrieve users. 2668 * 2669 * The optional $filter parameter modifies the query used to retrieve users. 2670 * Accepted keys are 'number' (default: 50), 'offset' (default: 0), 'role', 2671 * 'who', 'orderby', and 'order'. 2672 * 2673 * The optional $fields parameter specifies what fields will be included 2674 * in the response array. 2675 * 2676 * @uses get_users() 2677 * @see wp_getUser() for more on $fields and return values 2678 * 2679 * @param array $args { 2680 * Method arguments. Note: arguments must be ordered as documented. 2681 * 2682 * @type int $blog_id (unused) 2683 * @type string $username 2684 * @type string $password 2685 * @type array $filter (optional) 2686 * @type array $fields (optional) 2687 * } 2688 * @return array|IXR_Error users data 2689 */ 2690 public function wp_getUsers( $args ) { 2691 if ( ! $this->minimum_args( $args, 3 ) ) { 2692 return $this->error; 2693 } 2694 2695 $this->escape( $args ); 2696 2697 $username = $args[1]; 2698 $password = $args[2]; 2699 $filter = isset( $args[3] ) ? $args[3] : array(); 2700 2701 if ( isset( $args[4] ) ) { 2702 $fields = $args[4]; 2703 } else { 2704 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 2705 $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUsers' ); 2706 } 2707 2708 $user = $this->login( $username, $password ); 2709 if ( ! $user ) { 2710 return $this->error; 2711 } 2712 2713 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 2714 do_action( 'xmlrpc_call', 'wp.getUsers', $args, $this ); 2715 2716 if ( ! current_user_can( 'list_users' ) ) { 2717 return new IXR_Error( 401, __( 'Sorry, you are not allowed to list users.' ) ); 2718 } 2719 2720 $query = array( 'fields' => 'all_with_meta' ); 2721 2722 $query['number'] = ( isset( $filter['number'] ) ) ? absint( $filter['number'] ) : 50; 2723 $query['offset'] = ( isset( $filter['offset'] ) ) ? absint( $filter['offset'] ) : 0; 2724 2725 if ( isset( $filter['orderby'] ) ) { 2726 $query['orderby'] = $filter['orderby']; 2727 2728 if ( isset( $filter['order'] ) ) { 2729 $query['order'] = $filter['order']; 2730 } 2731 } 2732 2733 if ( isset( $filter['role'] ) ) { 2734 if ( get_role( $filter['role'] ) === null ) { 2735 return new IXR_Error( 403, __( 'Invalid role.' ) ); 2736 } 2737 2738 $query['role'] = $filter['role']; 2739 } 2740 2741 if ( isset( $filter['who'] ) ) { 2742 $query['who'] = $filter['who']; 2743 } 2744 2745 $users = get_users( $query ); 2746 2747 $_users = array(); 2748 foreach ( $users as $user_data ) { 2749 if ( current_user_can( 'edit_user', $user_data->ID ) ) { 2750 $_users[] = $this->_prepare_user( $user_data, $fields ); 2751 } 2752 } 2753 return $_users; 2754 } 2755 2756 /** 2757 * Retrieve information about the requesting user. 2758 * 2759 * @uses get_userdata() 2760 * 2761 * @param array $args { 2762 * Method arguments. Note: arguments must be ordered as documented. 2763 * 2764 * @type int $blog_id (unused) 2765 * @type string $username 2766 * @type string $password 2767 * @type array $fields (optional) 2768 * } 2769 * @return array|IXR_Error (@see wp_getUser) 2770 */ 2771 public function wp_getProfile( $args ) { 2772 if ( ! $this->minimum_args( $args, 3 ) ) { 2773 return $this->error; 2774 } 2775 2776 $this->escape( $args ); 2777 2778 $username = $args[1]; 2779 $password = $args[2]; 2780 2781 if ( isset( $args[3] ) ) { 2782 $fields = $args[3]; 2783 } else { 2784 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 2785 $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getProfile' ); 2786 } 2787 2788 $user = $this->login( $username, $password ); 2789 if ( ! $user ) { 2790 return $this->error; 2791 } 2792 2793 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 2794 do_action( 'xmlrpc_call', 'wp.getProfile', $args, $this ); 2795 2796 if ( ! current_user_can( 'edit_user', $user->ID ) ) { 2797 return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit your profile.' ) ); 2798 } 2799 2800 $user_data = get_userdata( $user->ID ); 2801 2802 return $this->_prepare_user( $user_data, $fields ); 2803 } 2804 2805 /** 2806 * Edit user's profile. 2807 * 2808 * @uses wp_update_user() 2809 * 2810 * @param array $args { 2811 * Method arguments. Note: arguments must be ordered as documented. 2812 * 2813 * @type int $blog_id (unused) 2814 * @type string $username 2815 * @type string $password 2816 * @type array $content_struct It can optionally contain: 2817 * - 'first_name' 2818 * - 'last_name' 2819 * - 'website' 2820 * - 'display_name' 2821 * - 'nickname' 2822 * - 'nicename' 2823 * - 'bio' 2824 * } 2825 * @return true|IXR_Error True, on success. 2826 */ 2827 public function wp_editProfile( $args ) { 2828 if ( ! $this->minimum_args( $args, 4 ) ) { 2829 return $this->error; 2830 } 2831 2832 $this->escape( $args ); 2833 2834 $username = $args[1]; 2835 $password = $args[2]; 2836 $content_struct = $args[3]; 2837 2838 $user = $this->login( $username, $password ); 2839 if ( ! $user ) { 2840 return $this->error; 2841 } 2842 2843 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 2844 do_action( 'xmlrpc_call', 'wp.editProfile', $args, $this ); 2845 2846 if ( ! current_user_can( 'edit_user', $user->ID ) ) { 2847 return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit your profile.' ) ); 2848 } 2849 2850 // Holds data of the user. 2851 $user_data = array(); 2852 $user_data['ID'] = $user->ID; 2853 2854 // Only set the user details if they were given. 2855 if ( isset( $content_struct['first_name'] ) ) { 2856 $user_data['first_name'] = $content_struct['first_name']; 2857 } 2858 2859 if ( isset( $content_struct['last_name'] ) ) { 2860 $user_data['last_name'] = $content_struct['last_name']; 2861 } 2862 2863 if ( isset( $content_struct['url'] ) ) { 2864 $user_data['user_url'] = $content_struct['url']; 2865 } 2866 2867 if ( isset( $content_struct['display_name'] ) ) { 2868 $user_data['display_name'] = $content_struct['display_name']; 2869 } 2870 2871 if ( isset( $content_struct['nickname'] ) ) { 2872 $user_data['nickname'] = $content_struct['nickname']; 2873 } 2874 2875 if ( isset( $content_struct['nicename'] ) ) { 2876 $user_data['user_nicename'] = $content_struct['nicename']; 2877 } 2878 2879 if ( isset( $content_struct['bio'] ) ) { 2880 $user_data['description'] = $content_struct['bio']; 2881 } 2882 2883 $result = wp_update_user( $user_data ); 2884 2885 if ( is_wp_error( $result ) ) { 2886 return new IXR_Error( 500, $result->get_error_message() ); 2887 } 2888 2889 if ( ! $result ) { 2890 return new IXR_Error( 500, __( 'Sorry, the user could not be updated.' ) ); 2891 } 2892 2893 return true; 2894 } 2895 2896 /** 2897 * Retrieve page. 2898 * 2899 * @since 2.2.0 2900 * 2901 * @param array $args { 2902 * Method arguments. Note: arguments must be ordered as documented. 2903 * 2904 * @type int $blog_id (unused) 2905 * @type int $page_id 2906 * @type string $username 2907 * @type string $password 2908 * } 2909 * @return array|IXR_Error 2910 */ 2911 public function wp_getPage( $args ) { 2912 $this->escape( $args ); 2913 2914 $page_id = (int) $args[1]; 2915 $username = $args[2]; 2916 $password = $args[3]; 2917 2918 $user = $this->login( $username, $password ); 2919 if ( ! $user ) { 2920 return $this->error; 2921 } 2922 2923 $page = get_post( $page_id ); 2924 if ( ! $page ) { 2925 return new IXR_Error( 404, __( 'Invalid post ID.' ) ); 2926 } 2927 2928 if ( ! current_user_can( 'edit_page', $page_id ) ) { 2929 return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this page.' ) ); 2930 } 2931 2932 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 2933 do_action( 'xmlrpc_call', 'wp.getPage', $args, $this ); 2934 2935 // If we found the page then format the data. 2936 if ( $page->ID && ( 'page' === $page->post_type ) ) { 2937 return $this->_prepare_page( $page ); 2938 } else { 2939 // If the page doesn't exist, indicate that. 2940 return new IXR_Error( 404, __( 'Sorry, no such page.' ) ); 2941 } 2942 } 2943 2944 /** 2945 * Retrieve Pages. 2946 * 2947 * @since 2.2.0 2948 * 2949 * @param array $args { 2950 * Method arguments. Note: arguments must be ordered as documented. 2951 * 2952 * @type int $blog_id (unused) 2953 * @type string $username 2954 * @type string $password 2955 * @type int $num_pages 2956 * } 2957 * @return array|IXR_Error 2958 */ 2959 public function wp_getPages( $args ) { 2960 $this->escape( $args ); 2961 2962 $username = $args[1]; 2963 $password = $args[2]; 2964 $num_pages = isset( $args[3] ) ? (int) $args[3] : 10; 2965 2966 $user = $this->login( $username, $password ); 2967 if ( ! $user ) { 2968 return $this->error; 2969 } 2970 2971 if ( ! current_user_can( 'edit_pages' ) ) { 2972 return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit pages.' ) ); 2973 } 2974 2975 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 2976 do_action( 'xmlrpc_call', 'wp.getPages', $args, $this ); 2977 2978 $pages = get_posts( 2979 array( 2980 'post_type' => 'page', 2981 'post_status' => 'any', 2982 'numberposts' => $num_pages, 2983 ) 2984 ); 2985 $num_pages = count( $pages ); 2986 2987 // If we have pages, put together their info. 2988 if ( $num_pages >= 1 ) { 2989 $pages_struct = array(); 2990 2991 foreach ( $pages as $page ) { 2992 if ( current_user_can( 'edit_page', $page->ID ) ) { 2993 $pages_struct[] = $this->_prepare_page( $page ); 2994 } 2995 } 2996 2997 return $pages_struct; 2998 } 2999 3000 return array(); 3001 } 3002 3003 /** 3004 * Create new page. 3005 * 3006 * @since 2.2.0 3007 * 3008 * @see wp_xmlrpc_server::mw_newPost() 3009 * 3010 * @param array $args { 3011 * Method arguments. Note: arguments must be ordered as documented. 3012 * 3013 * @type int $blog_id (unused) 3014 * @type string $username 3015 * @type string $password 3016 * @type array $content_struct 3017 * } 3018 * @return int|IXR_Error 3019 */ 3020 public function wp_newPage( $args ) { 3021 // Items not escaped here will be escaped in wp_newPost(). 3022 $username = $this->escape( $args[1] ); 3023 $password = $this->escape( $args[2] ); 3024 3025 $user = $this->login( $username, $password ); 3026 if ( ! $user ) { 3027 return $this->error; 3028 } 3029 3030 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 3031 do_action( 'xmlrpc_call', 'wp.newPage', $args, $this ); 3032 3033 // Mark this as content for a page. 3034 $args[3]['post_type'] = 'page'; 3035 3036 // Let mw_newPost() do all of the heavy lifting. 3037 return $this->mw_newPost( $args ); 3038 } 3039 3040 /** 3041 * Delete page. 3042 * 3043 * @since 2.2.0 3044 * 3045 * @param array $args { 3046 * Method arguments. Note: arguments must be ordered as documented. 3047 * 3048 * @type int $blog_id (unused) 3049 * @type string $username 3050 * @type string $password 3051 * @type int $page_id 3052 * } 3053 * @return true|IXR_Error True, if success. 3054 */ 3055 public function wp_deletePage( $args ) { 3056 $this->escape( $args ); 3057 3058 $username = $args[1]; 3059 $password = $args[2]; 3060 $page_id = (int) $args[3]; 3061 3062 $user = $this->login( $username, $password ); 3063 if ( ! $user ) { 3064 return $this->error; 3065 } 3066 3067 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 3068 do_action( 'xmlrpc_call', 'wp.deletePage', $args, $this ); 3069 3070 // Get the current page based on the 'page_id' and 3071 // make sure it is a page and not a post. 3072 $actual_page = get_post( $page_id, ARRAY_A ); 3073 if ( ! $actual_page || ( 'page' !== $actual_page['post_type'] ) ) { 3074 return new IXR_Error( 404, __( 'Sorry, no such page.' ) ); 3075 } 3076 3077 // Make sure the user can delete pages. 3078 if ( ! current_user_can( 'delete_page', $page_id ) ) { 3079 return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this page.' ) ); 3080 } 3081 3082 // Attempt to delete the page. 3083 $result = wp_delete_post( $page_id ); 3084 if ( ! $result ) { 3085 return new IXR_Error( 500, __( 'Failed to delete the page.' ) ); 3086 } 3087 3088 /** 3089 * Fires after a page has been successfully deleted via XML-RPC. 3090 * 3091 * @since 3.4.0 3092 * 3093 * @param int $page_id ID of the deleted page. 3094 * @param array $args An array of arguments to delete the page. 3095 */ 3096 do_action( 'xmlrpc_call_success_wp_deletePage', $page_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase 3097 3098 return true; 3099 } 3100 3101 /** 3102 * Edit page. 3103 * 3104 * @since 2.2.0 3105 * 3106 * @param array $args { 3107 * Method arguments. Note: arguments must be ordered as documented. 3108 * 3109 * @type int $blog_id (unused) 3110 * @type int $page_id 3111 * @type string $username 3112 * @type string $password 3113 * @type string $content 3114 * @type string $publish 3115 * } 3116 * @return array|IXR_Error 3117 */ 3118 public function wp_editPage( $args ) { 3119 // Items will be escaped in mw_editPost(). 3120 $page_id = (int) $args[1]; 3121 $username = $args[2]; 3122 $password = $args[3]; 3123 $content = $args[4]; 3124 $publish = $args[5]; 3125 3126 $escaped_username = $this->escape( $username ); 3127 $escaped_password = $this->escape( $password ); 3128 3129 $user = $this->login( $escaped_username, $escaped_password ); 3130 if ( ! $user ) { 3131 return $this->error; 3132 } 3133 3134 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 3135 do_action( 'xmlrpc_call', 'wp.editPage', $args, $this ); 3136 3137 // Get the page data and make sure it is a page. 3138 $actual_page = get_post( $page_id, ARRAY_A ); 3139 if ( ! $actual_page || ( 'page' !== $actual_page['post_type'] ) ) { 3140 return new IXR_Error( 404, __( 'Sorry, no such page.' ) ); 3141 } 3142 3143 // Make sure the user is allowed to edit pages. 3144 if ( ! current_user_can( 'edit_page', $page_id ) ) { 3145 return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this page.' ) ); 3146 } 3147 3148 // Mark this as content for a page. 3149 $content['post_type'] = 'page'; 3150 3151 // Arrange args in the way mw_editPost() understands. 3152 $args = array( 3153 $page_id, 3154 $username, 3155 $password, 3156 $content, 3157 $publish, 3158 ); 3159 3160 // Let mw_editPost() do all of the heavy lifting. 3161 return $this->mw_editPost( $args ); 3162 } 3163 3164 /** 3165 * Retrieve page list. 3166 * 3167 * @since 2.2.0 3168 * 3169 * @global wpdb $wpdb WordPress database abstraction object. 3170 * 3171 * @param array $args { 3172 * Method arguments. Note: arguments must be ordered as documented. 3173 * 3174 * @type int $blog_id (unused) 3175 * @type string $username 3176 * @type string $password 3177 * } 3178 * @return array|IXR_Error 3179 */ 3180 public function wp_getPageList( $args ) { 3181 global $wpdb; 3182 3183 $this->escape( $args ); 3184 3185 $username = $args[1]; 3186 $password = $args[2]; 3187 3188 $user = $this->login( $username, $password ); 3189 if ( ! $user ) { 3190 return $this->error; 3191 } 3192 3193 if ( ! current_user_can( 'edit_pages' ) ) { 3194 return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit pages.' ) ); 3195 } 3196 3197 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 3198 do_action( 'xmlrpc_call', 'wp.getPageList', $args, $this ); 3199 3200 // Get list of page IDs and titles. 3201 $page_list = $wpdb->get_results( 3202 " 3203 SELECT ID page_id, 3204 post_title page_title, 3205 post_parent page_parent_id, 3206 post_date_gmt, 3207 post_date, 3208 post_status 3209 FROM {$wpdb->posts} 3210 WHERE post_type = 'page' 3211 ORDER BY ID 3212 " 3213 ); 3214 3215 // The date needs to be formatted properly. 3216 $num_pages = count( $page_list ); 3217 for ( $i = 0; $i < $num_pages; $i++ ) { 3218 $page_list[ $i ]->dateCreated = $this->_convert_date( $page_list[ $i ]->post_date ); 3219 $page_list[ $i ]->date_created_gmt = $this->_convert_date_gmt( $page_list[ $i ]->post_date_gmt, $page_list[ $i ]->post_date ); 3220 3221 unset( $page_list[ $i ]->post_date_gmt ); 3222 unset( $page_list[ $i ]->post_date ); 3223 unset( $page_list[ $i ]->post_status ); 3224 } 3225 3226 return $page_list; 3227 } 3228 3229 /** 3230 * Retrieve authors list. 3231 * 3232 * @since 2.2.0 3233 * 3234 * @param array $args { 3235 * Method arguments. Note: arguments must be ordered as documented. 3236 * 3237 * @type int $blog_id (unused) 3238 * @type string $username 3239 * @type string $password 3240 * } 3241 * @return array|IXR_Error 3242 */ 3243 public function wp_getAuthors( $args ) { 3244 $this->escape( $args ); 3245 3246 $username = $args[1]; 3247 $password = $args[2]; 3248 3249 $user = $this->login( $username, $password ); 3250 if ( ! $user ) { 3251 return $this->error; 3252 } 3253 3254 if ( ! current_user_can( 'edit_posts' ) ) { 3255 return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) ); 3256 } 3257 3258 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 3259 do_action( 'xmlrpc_call', 'wp.getAuthors', $args, $this ); 3260 3261 $authors = array(); 3262 foreach ( get_users( array( 'fields' => array( 'ID', 'user_login', 'display_name' ) ) ) as $user ) { 3263 $authors[] = array( 3264 'user_id' => $user->ID, 3265 'user_login' => $user->user_login, 3266 'display_name' => $user->display_name, 3267 ); 3268 } 3269 3270 return $authors; 3271 } 3272 3273 /** 3274 * Get list of all tags 3275 * 3276 * @since 2.7.0 3277 * 3278 * @param array $args { 3279 * Method arguments. Note: arguments must be ordered as documented. 3280 * 3281 * @type int $blog_id (unused) 3282 * @type string $username 3283 * @type string $password 3284 * } 3285 * @return array|IXR_Error 3286 */ 3287 public function wp_getTags( $args ) { 3288 $this->escape( $args ); 3289 3290 $username = $args[1]; 3291 $password = $args[2]; 3292 3293 $user = $this->login( $username, $password ); 3294 if ( ! $user ) { 3295 return $this->error; 3296 } 3297 3298 if ( ! current_user_can( 'edit_posts' ) ) { 3299 return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view tags.' ) ); 3300 } 3301 3302 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 3303 do_action( 'xmlrpc_call', 'wp.getKeywords', $args, $this ); 3304 3305 $tags = array(); 3306 3307 $all_tags = get_tags(); 3308 if ( $all_tags ) { 3309 foreach ( (array) $all_tags as $tag ) { 3310 $struct = array(); 3311 $struct['tag_id'] = $tag->term_id; 3312 $struct['name'] = $tag->name; 3313 $struct['count'] = $tag->count; 3314 $struct['slug'] = $tag->slug; 3315 $struct['html_url'] = esc_html( get_tag_link( $tag->term_id ) ); 3316 $struct['rss_url'] = esc_html( get_tag_feed_link( $tag->term_id ) ); 3317 3318 $tags[] = $struct; 3319 } 3320 } 3321 3322 return $tags; 3323 } 3324 3325 /** 3326 * Create new category. 3327 * 3328 * @since 2.2.0 3329 * 3330 * @param array $args { 3331 * Method arguments. Note: arguments must be ordered as documented. 3332 * 3333 * @type int $blog_id (unused) 3334 * @type string $username 3335 * @type string $password 3336 * @type array $category 3337 * } 3338 * @return int|IXR_Error Category ID. 3339 */ 3340 public function wp_newCategory( $args ) { 3341 $this->escape( $args ); 3342 3343 $username = $args[1]; 3344 $password = $args[2]; 3345 $category = $args[3]; 3346 3347 $user = $this->login( $username, $password ); 3348 if ( ! $user ) { 3349 return $this->error; 3350 } 3351 3352 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 3353 do_action( 'xmlrpc_call', 'wp.newCategory', $args, $this ); 3354 3355 // Make sure the user is allowed to add a category. 3356 if ( ! current_user_can( 'manage_categories' ) ) { 3357 return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a category.' ) ); 3358 } 3359 3360 // If no slug was provided, make it empty 3361 // so that WordPress will generate one. 3362 if ( empty( $category['slug'] ) ) { 3363 $category['slug'] = ''; 3364 } 3365 3366 // If no parent_id was provided, make it empty 3367 // so that it will be a top-level page (no parent). 3368 if ( ! isset( $category['parent_id'] ) ) { 3369 $category['parent_id'] = ''; 3370 } 3371 3372 // If no description was provided, make it empty. 3373 if ( empty( $category['description'] ) ) { 3374 $category['description'] = ''; 3375 } 3376 3377 $new_category = array( 3378 'cat_name' => $category['name'], 3379 'category_nicename' => $category['slug'], 3380 'category_parent' => $category['parent_id'], 3381 'category_description' => $category['description'], 3382 ); 3383 3384 $cat_id = wp_insert_category( $new_category, true ); 3385 if ( is_wp_error( $cat_id ) ) { 3386 if ( 'term_exists' === $cat_id->get_error_code() ) { 3387 return (int) $cat_id->get_error_data(); 3388 } else { 3389 return new IXR_Error( 500, __( 'Sorry, the category could not be created.' ) ); 3390 } 3391 } elseif ( ! $cat_id ) { 3392 return new IXR_Error( 500, __( 'Sorry, the category could not be created.' ) ); 3393 } 3394 3395 /** 3396 * Fires after a new category has been successfully created via XML-RPC. 3397 * 3398 * @since 3.4.0 3399 * 3400 * @param int $cat_id ID of the new category. 3401 * @param array $args An array of new category arguments. 3402 */ 3403 do_action( 'xmlrpc_call_success_wp_newCategory', $cat_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase 3404 3405 return $cat_id; 3406 } 3407 3408 /** 3409 * Remove category. 3410 * 3411 * @since 2.5.0 3412 * 3413 * @param array $args { 3414 * Method arguments. Note: arguments must be ordered as documented. 3415 * 3416 * @type int $blog_id (unused) 3417 * @type string $username 3418 * @type string $password 3419 * @type int $category_id 3420 * } 3421 * @return bool|IXR_Error See wp_delete_term() for return info. 3422 */ 3423 public function wp_deleteCategory( $args ) { 3424 $this->escape( $args ); 3425 3426 $username = $args[1]; 3427 $password = $args[2]; 3428 $category_id = (int) $args[3]; 3429 3430 $user = $this->login( $username, $password ); 3431 if ( ! $user ) { 3432 return $this->error; 3433 } 3434 3435 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 3436 do_action( 'xmlrpc_call', 'wp.deleteCategory', $args, $this ); 3437 3438 if ( ! current_user_can( 'delete_term', $category_id ) ) { 3439 return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this category.' ) ); 3440 } 3441 3442 $status = wp_delete_term( $category_id, 'category' ); 3443 3444 if ( true == $status ) { 3445 /** 3446 * Fires after a category has been successfully deleted via XML-RPC. 3447 * 3448 * @since 3.4.0 3449 * 3450 * @param int $category_id ID of the deleted category. 3451 * @param array $args An array of arguments to delete the category. 3452 */ 3453 do_action( 'xmlrpc_call_success_wp_deleteCategory', $category_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase 3454 } 3455 3456 return $status; 3457 } 3458 3459 /** 3460 * Retrieve category list. 3461 * 3462 * @since 2.2.0 3463 * 3464 * @param array $args { 3465 * Method arguments. Note: arguments must be ordered as documented. 3466 * 3467 * @type int $blog_id (unused) 3468 * @type string $username 3469 * @type string $password 3470 * @type array $category 3471 * @type int $max_results 3472 * } 3473 * @return array|IXR_Error 3474 */ 3475 public function wp_suggestCategories( $args ) { 3476 $this->escape( $args ); 3477 3478 $username = $args[1]; 3479 $password = $args[2]; 3480 $category = $args[3]; 3481 $max_results = (int) $args[4]; 3482 3483 $user = $this->login( $username, $password ); 3484 if ( ! $user ) { 3485 return $this->error; 3486 } 3487 3488 if ( ! current_user_can( 'edit_posts' ) ) { 3489 return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) ); 3490 } 3491 3492 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 3493 do_action( 'xmlrpc_call', 'wp.suggestCategories', $args, $this ); 3494 3495 $category_suggestions = array(); 3496 $args = array( 3497 'get' => 'all', 3498 'number' => $max_results, 3499 'name__like' => $category, 3500 ); 3501 foreach ( (array) get_categories( $args ) as $cat ) { 3502 $category_suggestions[] = array( 3503 'category_id' => $cat->term_id, 3504 'category_name' => $cat->name, 3505 ); 3506 } 3507 3508 return $category_suggestions; 3509 } 3510 3511 /** 3512 * Retrieve comment. 3513 * 3514 * @since 2.7.0 3515 * 3516 * @param array $args { 3517 * Method arguments. Note: arguments must be ordered as documented. 3518 * 3519 * @type int $blog_id (unused) 3520 * @type string $username 3521 * @type string $password 3522 * @type int $comment_id 3523 * } 3524 * @return array|IXR_Error 3525 */ 3526 public function wp_getComment( $args ) { 3527 $this->escape( $args ); 3528 3529 $username = $args[1]; 3530 $password = $args[2]; 3531 $comment_id = (int) $args[3]; 3532 3533 $user = $this->login( $username, $password ); 3534 if ( ! $user ) { 3535 return $this->error; 3536 } 3537 3538 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 3539 do_action( 'xmlrpc_call', 'wp.getComment', $args, $this ); 3540 3541 $comment = get_comment( $comment_id ); 3542 if ( ! $comment ) { 3543 return new IXR_Error( 404, __( 'Invalid comment ID.' ) ); 3544 } 3545 3546 if ( ! current_user_can( 'edit_comment', $comment_id ) ) { 3547 return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) ); 3548 } 3549 3550 return $this->_prepare_comment( $comment ); 3551 } 3552 3553 /** 3554 * Retrieve comments. 3555 * 3556 * Besides the common blog_id (unused), username, and password arguments, it takes a filter 3557 * array as last argument. 3558 * 3559 * Accepted 'filter' keys are 'status', 'post_id', 'offset', and 'number'. 3560 * 3561 * The defaults are as follows: 3562 * - 'status' - Default is ''. Filter by status (e.g., 'approve', 'hold') 3563 * - 'post_id' - Default is ''. The post where the comment is posted. Empty string shows all comments. 3564 * - 'number' - Default is 10. Total number of media items to retrieve. 3565 * - 'offset' - Default is 0. See WP_Query::query() for more. 3566 * 3567 * @since 2.7.0 3568 * 3569 * @param array $args { 3570 * Method arguments. Note: arguments must be ordered as documented. 3571 * 3572 * @type int $blog_id (unused) 3573 * @type string $username 3574 * @type string $password 3575 * @type array $struct 3576 * } 3577 * @return array|IXR_Error Contains a collection of comments. See wp_xmlrpc_server::wp_getComment() for a description of each item contents 3578 */ 3579 public function wp_getComments( $args ) { 3580 $this->escape( $args ); 3581 3582 $username = $args[1]; 3583 $password = $args[2]; 3584 $struct = isset( $args[3] ) ? $args[3] : array(); 3585 3586 $user = $this->login( $username, $password ); 3587 if ( ! $user ) { 3588 return $this->error; 3589 } 3590 3591 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 3592 do_action( 'xmlrpc_call', 'wp.getComments', $args, $this ); 3593 3594 if ( isset( $struct['status'] ) ) { 3595 $status = $struct['status']; 3596 } else { 3597 $status = ''; 3598 } 3599 3600 if ( ! current_user_can( 'moderate_comments' ) && 'approve' !== $status ) { 3601 return new IXR_Error( 401, __( 'Invalid comment status.' ) ); 3602 } 3603 3604 $post_id = ''; 3605 if ( isset( $struct['post_id'] ) ) { 3606 $post_id = absint( $struct['post_id'] ); 3607 } 3608 3609 $post_type = ''; 3610 if ( isset( $struct['post_type'] ) ) { 3611 $post_type_object = get_post_type_object( $struct['post_type'] ); 3612 if ( ! $post_type_object || ! post_type_supports( $post_type_object->name, 'comments' ) ) { 3613 return new IXR_Error( 404, __( 'Invalid post type.' ) ); 3614 } 3615 $post_type = $struct['post_type']; 3616 } 3617 3618 $offset = 0; 3619 if ( isset( $struct['offset'] ) ) { 3620 $offset = absint( $struct['offset'] ); 3621 } 3622 3623 $number = 10; 3624 if ( isset( $struct['number'] ) ) { 3625 $number = absint( $struct['number'] ); 3626 } 3627 3628 $comments = get_comments( 3629 array( 3630 'status' => $status, 3631 'post_id' => $post_id, 3632 'offset' => $offset, 3633 'number' => $number, 3634 'post_type' => $post_type, 3635 ) 3636 ); 3637 3638 $comments_struct = array(); 3639 if ( is_array( $comments ) ) { 3640 foreach ( $comments as $comment ) { 3641 $comments_struct[] = $this->_prepare_comment( $comment ); 3642 } 3643 } 3644 3645 return $comments_struct; 3646 } 3647 3648 /** 3649 * Delete a comment. 3650 * 3651 * By default, the comment will be moved to the Trash instead of deleted. 3652 * See wp_delete_comment() for more information on this behavior. 3653 * 3654 * @since 2.7.0 3655 * 3656 * @param array $args { 3657 * Method arguments. Note: arguments must be ordered as documented. 3658 * 3659 * @type int $blog_id (unused) 3660 * @type string $username 3661 * @type string $password 3662 * @type int $comment_ID 3663 * } 3664 * @return bool|IXR_Error See wp_delete_comment(). 3665 */ 3666 public function wp_deleteComment( $args ) { 3667 $this->escape( $args ); 3668 3669 $username = $args[1]; 3670 $password = $args[2]; 3671 $comment_ID = (int) $args[3]; 3672 3673 $user = $this->login( $username, $password ); 3674 if ( ! $user ) { 3675 return $this->error; 3676 } 3677 3678 if ( ! get_comment( $comment_ID ) ) { 3679 return new IXR_Error( 404, __( 'Invalid comment ID.' ) ); 3680 } 3681 3682 if ( ! current_user_can( 'edit_comment', $comment_ID ) ) { 3683 return new IXR_Error( 403, __( 'Sorry, you are not allowed to delete this comment.' ) ); 3684 } 3685 3686 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 3687 do_action( 'xmlrpc_call', 'wp.deleteComment', $args, $this ); 3688 3689 $status = wp_delete_comment( $comment_ID ); 3690 3691 if ( $status ) { 3692 /** 3693 * Fires after a comment has been successfully deleted via XML-RPC. 3694 * 3695 * @since 3.4.0 3696 * 3697 * @param int $comment_ID ID of the deleted comment. 3698 * @param array $args An array of arguments to delete the comment. 3699 */ 3700 do_action( 'xmlrpc_call_success_wp_deleteComment', $comment_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase 3701 } 3702 3703 return $status; 3704 } 3705 3706 /** 3707 * Edit comment. 3708 * 3709 * Besides the common blog_id (unused), username, and password arguments, it takes a 3710 * comment_id integer and a content_struct array as last argument. 3711 * 3712 * The allowed keys in the content_struct array are: 3713 * - 'author' 3714 * - 'author_url' 3715 * - 'author_email' 3716 * - 'content' 3717 * - 'date_created_gmt' 3718 * - 'status'. Common statuses are 'approve', 'hold', 'spam'. See get_comment_statuses() for more details 3719 * 3720 * @since 2.7.0 3721 * 3722 * @param array $args { 3723 * Method arguments. Note: arguments must be ordered as documented. 3724 * 3725 * @type int $blog_id (unused) 3726 * @type string $username 3727 * @type string $password 3728 * @type int $comment_ID 3729 * @type array $content_struct 3730 * } 3731 * @return true|IXR_Error True, on success. 3732 */ 3733 public function wp_editComment( $args ) { 3734 $this->escape( $args ); 3735 3736 $username = $args[1]; 3737 $password = $args[2]; 3738 $comment_ID = (int) $args[3]; 3739 $content_struct = $args[4]; 3740 3741 $user = $this->login( $username, $password ); 3742 if ( ! $user ) { 3743 return $this->error; 3744 } 3745 3746 if ( ! get_comment( $comment_ID ) ) { 3747 return new IXR_Error( 404, __( 'Invalid comment ID.' ) ); 3748 } 3749 3750 if ( ! current_user_can( 'edit_comment', $comment_ID ) ) { 3751 return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) ); 3752 } 3753 3754 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 3755 do_action( 'xmlrpc_call', 'wp.editComment', $args, $this ); 3756 $comment = array( 3757 'comment_ID' => $comment_ID, 3758 ); 3759 3760 if ( isset( $content_struct['status'] ) ) { 3761 $statuses = get_comment_statuses(); 3762 $statuses = array_keys( $statuses ); 3763 3764 if ( ! in_array( $content_struct['status'], $statuses, true ) ) { 3765 return new IXR_Error( 401, __( 'Invalid comment status.' ) ); 3766 } 3767 3768 $comment['comment_approved'] = $content_struct['status']; 3769 } 3770 3771 // Do some timestamp voodoo. 3772 if ( ! empty( $content_struct['date_created_gmt'] ) ) { 3773 // We know this is supposed to be GMT, so we're going to slap that Z on there by force. 3774 $dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z'; 3775 $comment['comment_date'] = get_date_from_gmt( $dateCreated ); 3776 $comment['comment_date_gmt'] = iso8601_to_datetime( $dateCreated, 'gmt' ); 3777 } 3778 3779 if ( isset( $content_struct['content'] ) ) { 3780 $comment['comment_content'] = $content_struct['content']; 3781 } 3782 3783 if ( isset( $content_struct['author'] ) ) { 3784 $comment['comment_author'] = $content_struct['author']; 3785 } 3786 3787 if ( isset( $content_struct['author_url'] ) ) { 3788 $comment['comment_author_url'] = $content_struct['author_url']; 3789 } 3790 3791 if ( isset( $content_struct['author_email'] ) ) { 3792 $comment['comment_author_email'] = $content_struct['author_email']; 3793 } 3794 3795 $result = wp_update_comment( $comment, true ); 3796 if ( is_wp_error( $result ) ) { 3797 return new IXR_Error( 500, $result->get_error_message() ); 3798 } 3799 3800 if ( ! $result ) { 3801 return new IXR_Error( 500, __( 'Sorry, the comment could not be updated.' ) ); 3802 } 3803 3804 /** 3805 * Fires after a comment has been successfully updated via XML-RPC. 3806 * 3807 * @since 3.4.0 3808 * 3809 * @param int $comment_ID ID of the updated comment. 3810 * @param array $args An array of arguments to update the comment. 3811 */ 3812 do_action( 'xmlrpc_call_success_wp_editComment', $comment_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase 3813 3814 return true; 3815 } 3816 3817 /** 3818 * Create new comment. 3819 * 3820 * @since 2.7.0 3821 * 3822 * @param array $args { 3823 * Method arguments. Note: arguments must be ordered as documented. 3824 * 3825 * @type int $blog_id (unused) 3826 * @type string $username 3827 * @type string $password 3828 * @type string|int $post 3829 * @type array $content_struct 3830 * } 3831 * @return int|IXR_Error See wp_new_comment(). 3832 */ 3833 public function wp_newComment( $args ) { 3834 $this->escape( $args ); 3835 3836 $username = $args[1]; 3837 $password = $args[2]; 3838 $post = $args[3]; 3839 $content_struct = $args[4]; 3840 3841 /** 3842 * Filters whether to allow anonymous comments over XML-RPC. 3843 * 3844 * @since 2.7.0 3845 * 3846 * @param bool $allow Whether to allow anonymous commenting via XML-RPC. 3847 * Default false. 3848 */ 3849 $allow_anon = apply_filters( 'xmlrpc_allow_anonymous_comments', false ); 3850 3851 $user = $this->login( $username, $password ); 3852 3853 if ( ! $user ) { 3854 $logged_in = false; 3855 if ( $allow_anon && get_option( 'comment_registration' ) ) { 3856 return new IXR_Error( 403, __( 'Sorry, you must be logged in to comment.' ) ); 3857 } elseif ( ! $allow_anon ) { 3858 return $this->error; 3859 } 3860 } else { 3861 $logged_in = true; 3862 } 3863 3864 if ( is_numeric( $post ) ) { 3865 $post_id = absint( $post ); 3866 } else { 3867 $post_id = url_to_postid( $post ); 3868 } 3869 3870 if ( ! $post_id ) { 3871 return new IXR_Error( 404, __( 'Invalid post ID.' ) ); 3872 } 3873 3874 if ( ! get_post( $post_id ) ) { 3875 return new IXR_Error( 404, __( 'Invalid post ID.' ) ); 3876 } 3877 3878 if ( ! comments_open( $post_id ) ) { 3879 return new IXR_Error( 403, __( 'Sorry, comments are closed for this item.' ) ); 3880 } 3881 3882 if ( 3883 'publish' === get_post_status( $post_id ) && 3884 ! current_user_can( 'edit_post', $post_id ) && 3885 post_password_required( $post_id ) 3886 ) { 3887 return new IXR_Error( 403, __( 'Sorry, you are not allowed to comment on this post.' ) ); 3888 } 3889 3890 if ( 3891 'private' === get_post_status( $post_id ) && 3892 ! current_user_can( 'read_post', $post_id ) 3893 ) { 3894 return new IXR_Error( 403, __( 'Sorry, you are not allowed to comment on this post.' ) ); 3895 } 3896 3897 $comment = array( 3898 'comment_post_ID' => $post_id, 3899 'comment_content' => trim( $content_struct['content'] ), 3900 ); 3901 3902 if ( $logged_in ) { 3903 $display_name = $user->display_name; 3904 $user_email = $user->user_email; 3905 $user_url = $user->user_url; 3906 3907 $comment['comment_author'] = $this->escape( $display_name ); 3908 $comment['comment_author_email'] = $this->escape( $user_email ); 3909 $comment['comment_author_url'] = $this->escape( $user_url ); 3910 $comment['user_ID'] = $user->ID; 3911 } else { 3912 $comment['comment_author'] = ''; 3913 if ( isset( $content_struct['author'] ) ) { 3914 $comment['comment_author'] = $content_struct['author']; 3915 } 3916 3917 $comment['comment_author_email'] = ''; 3918 if ( isset( $content_struct['author_email'] ) ) { 3919 $comment['comment_author_email'] = $content_struct['author_email']; 3920 } 3921 3922 $comment['comment_author_url'] = ''; 3923 if ( isset( $content_struct['author_url'] ) ) { 3924 $comment['comment_author_url'] = $content_struct['author_url']; 3925 } 3926 3927 $comment['user_ID'] = 0; 3928 3929 if ( get_option( 'require_name_email' ) ) { 3930 if ( strlen( $comment['comment_author_email'] ) < 6 || '' === $comment['comment_author'] ) { 3931 return new IXR_Error( 403, __( 'Comment author name and email are required.' ) ); 3932 } elseif ( ! is_email( $comment['comment_author_email'] ) ) { 3933 return new IXR_Error( 403, __( 'A valid email address is required.' ) ); 3934 } 3935 } 3936 } 3937 3938 $comment['comment_parent'] = isset( $content_struct['comment_parent'] ) ? absint( $content_struct['comment_parent'] ) : 0; 3939 3940 /** This filter is documented in wp-includes/comment.php */ 3941 $allow_empty = apply_filters( 'allow_empty_comment', false, $comment ); 3942 3943 if ( ! $allow_empty && '' === $comment['comment_content'] ) { 3944 return new IXR_Error( 403, __( 'Comment is required.' ) ); 3945 } 3946 3947 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 3948 do_action( 'xmlrpc_call', 'wp.newComment', $args, $this ); 3949 3950 $comment_ID = wp_new_comment( $comment, true ); 3951 if ( is_wp_error( $comment_ID ) ) { 3952 return new IXR_Error( 403, $comment_ID->get_error_message() ); 3953 } 3954 3955 if ( ! $comment_ID ) { 3956 return new IXR_Error( 403, __( 'Something went wrong.' ) ); 3957 } 3958 3959 /** 3960 * Fires after a new comment has been successfully created via XML-RPC. 3961 * 3962 * @since 3.4.0 3963 * 3964 * @param int $comment_ID ID of the new comment. 3965 * @param array $args An array of new comment arguments. 3966 */ 3967 do_action( 'xmlrpc_call_success_wp_newComment', $comment_ID, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase 3968 3969 return $comment_ID; 3970 } 3971 3972 /** 3973 * Retrieve all of the comment status. 3974 * 3975 * @since 2.7.0 3976 * 3977 * @param array $args { 3978 * Method arguments. Note: arguments must be ordered as documented. 3979 * 3980 * @type int $blog_id (unused) 3981 * @type string $username 3982 * @type string $password 3983 * } 3984 * @return array|IXR_Error 3985 */ 3986 public function wp_getCommentStatusList( $args ) { 3987 $this->escape( $args ); 3988 3989 $username = $args[1]; 3990 $password = $args[2]; 3991 3992 $user = $this->login( $username, $password ); 3993 if ( ! $user ) { 3994 return $this->error; 3995 } 3996 3997 if ( ! current_user_can( 'publish_posts' ) ) { 3998 return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) ); 3999 } 4000 4001 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 4002 do_action( 'xmlrpc_call', 'wp.getCommentStatusList', $args, $this ); 4003 4004 return get_comment_statuses(); 4005 } 4006 4007 /** 4008 * Retrieve comment count. 4009 * 4010 * @since 2.5.0 4011 * 4012 * @param array $args { 4013 * Method arguments. Note: arguments must be ordered as documented. 4014 * 4015 * @type int $blog_id (unused) 4016 * @type string $username 4017 * @type string $password 4018 * @type int $post_id 4019 * } 4020 * @return array|IXR_Error 4021 */ 4022 public function wp_getCommentCount( $args ) { 4023 $this->escape( $args ); 4024 4025 $username = $args[1]; 4026 $password = $args[2]; 4027 $post_id = (int) $args[3]; 4028 4029 $user = $this->login( $username, $password ); 4030 if ( ! $user ) { 4031 return $this->error; 4032 } 4033 4034 $post = get_post( $post_id, ARRAY_A ); 4035 if ( empty( $post['ID'] ) ) { 4036 return new IXR_Error( 404, __( 'Invalid post ID.' ) ); 4037 } 4038 4039 if ( ! current_user_can( 'edit_post', $post_id ) ) { 4040 return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details of this post.' ) ); 4041 } 4042 4043 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 4044 do_action( 'xmlrpc_call', 'wp.getCommentCount', $args, $this ); 4045 4046 $count = wp_count_comments( $post_id ); 4047 4048 return array( 4049 'approved' => $count->approved, 4050 'awaiting_moderation' => $count->moderated, 4051 'spam' => $count->spam, 4052 'total_comments' => $count->total_comments, 4053 ); 4054 } 4055 4056 /** 4057 * Retrieve post statuses. 4058 * 4059 * @since 2.5.0 4060 * 4061 * @param array $args { 4062 * Method arguments. Note: arguments must be ordered as documented. 4063 * 4064 * @type int $blog_id (unused) 4065 * @type string $username 4066 * @type string $password 4067 * } 4068 * @return array|IXR_Error 4069 */ 4070 public function wp_getPostStatusList( $args ) { 4071 $this->escape( $args ); 4072 4073 $username = $args[1]; 4074 $password = $args[2]; 4075 4076 $user = $this->login( $username, $password ); 4077 if ( ! $user ) { 4078 return $this->error; 4079 } 4080 4081 if ( ! current_user_can( 'edit_posts' ) ) { 4082 return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) ); 4083 } 4084 4085 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 4086 do_action( 'xmlrpc_call', 'wp.getPostStatusList', $args, $this ); 4087 4088 return get_post_statuses(); 4089 } 4090 4091 /** 4092 * Retrieve page statuses. 4093 * 4094 * @since 2.5.0 4095 * 4096 * @param array $args { 4097 * Method arguments. Note: arguments must be ordered as documented. 4098 * 4099 * @type int $blog_id (unused) 4100 * @type string $username 4101 * @type string $password 4102 * } 4103 * @return array|IXR_Error 4104 */ 4105 public function wp_getPageStatusList( $args ) { 4106 $this->escape( $args ); 4107 4108 $username = $args[1]; 4109 $password = $args[2]; 4110 4111 $user = $this->login( $username, $password ); 4112 if ( ! $user ) { 4113 return $this->error; 4114 } 4115 4116 if ( ! current_user_can( 'edit_pages' ) ) { 4117 return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) ); 4118 } 4119 4120 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 4121 do_action( 'xmlrpc_call', 'wp.getPageStatusList', $args, $this ); 4122 4123 return get_page_statuses(); 4124 } 4125 4126 /** 4127 * Retrieve page templates. 4128 * 4129 * @since 2.6.0 4130 * 4131 * @param array $args { 4132 * Method arguments. Note: arguments must be ordered as documented. 4133 * 4134 * @type int $blog_id (unused) 4135 * @type string $username 4136 * @type string $password 4137 * } 4138 * @return array|IXR_Error 4139 */ 4140 public function wp_getPageTemplates( $args ) { 4141 $this->escape( $args ); 4142 4143 $username = $args[1]; 4144 $password = $args[2]; 4145 4146 $user = $this->login( $username, $password ); 4147 if ( ! $user ) { 4148 return $this->error; 4149 } 4150 4151 if ( ! current_user_can( 'edit_pages' ) ) { 4152 return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) ); 4153 } 4154 4155 $templates = get_page_templates(); 4156 $templates['Default'] = 'default'; 4157 4158 return $templates; 4159 } 4160 4161 /** 4162 * Retrieve blog options. 4163 * 4164 * @since 2.6.0 4165 * 4166 * @param array $args { 4167 * Method arguments. Note: arguments must be ordered as documented. 4168 * 4169 * @type int $blog_id (unused) 4170 * @type string $username 4171 * @type string $password 4172 * @type array $options 4173 * } 4174 * @return array|IXR_Error 4175 */ 4176 public function wp_getOptions( $args ) { 4177 $this->escape( $args ); 4178 4179 $username = $args[1]; 4180 $password = $args[2]; 4181 $options = isset( $args[3] ) ? (array) $args[3] : array(); 4182 4183 $user = $this->login( $username, $password ); 4184 if ( ! $user ) { 4185 return $this->error; 4186 } 4187 4188 // If no specific options where asked for, return all of them. 4189 if ( count( $options ) == 0 ) { 4190 $options = array_keys( $this->blog_options ); 4191 } 4192 4193 return $this->_getOptions( $options ); 4194 } 4195 4196 /** 4197 * Retrieve blog options value from list. 4198 * 4199 * @since 2.6.0 4200 * 4201 * @param array $options Options to retrieve. 4202 * @return array 4203 */ 4204 public function _getOptions( $options ) { 4205 $data = array(); 4206 $can_manage = current_user_can( 'manage_options' ); 4207 foreach ( $options as $option ) { 4208 if ( array_key_exists( $option, $this->blog_options ) ) { 4209 $data[ $option ] = $this->blog_options[ $option ]; 4210 // Is the value static or dynamic? 4211 if ( isset( $data[ $option ]['option'] ) ) { 4212 $data[ $option ]['value'] = get_option( $data[ $option ]['option'] ); 4213 unset( $data[ $option ]['option'] ); 4214 } 4215 4216 if ( ! $can_manage ) { 4217 $data[ $option ]['readonly'] = true; 4218 } 4219 } 4220 } 4221 4222 return $data; 4223 } 4224 4225 /** 4226 * Update blog options. 4227 * 4228 * @since 2.6.0 4229 * 4230 * @param array $args { 4231 * Method arguments. Note: arguments must be ordered as documented. 4232 * 4233 * @type int $blog_id (unused) 4234 * @type string $username 4235 * @type string $password 4236 * @type array $options 4237 * } 4238 * @return array|IXR_Error 4239 */ 4240 public function wp_setOptions( $args ) { 4241 $this->escape( $args ); 4242 4243 $username = $args[1]; 4244 $password = $args[2]; 4245 $options = (array) $args[3]; 4246 4247 $user = $this->login( $username, $password ); 4248 if ( ! $user ) { 4249 return $this->error; 4250 } 4251 4252 if ( ! current_user_can( 'manage_options' ) ) { 4253 return new IXR_Error( 403, __( 'Sorry, you are not allowed to update options.' ) ); 4254 } 4255 4256 $option_names = array(); 4257 foreach ( $options as $o_name => $o_value ) { 4258 $option_names[] = $o_name; 4259 if ( ! array_key_exists( $o_name, $this->blog_options ) ) { 4260 continue; 4261 } 4262 4263 if ( true == $this->blog_options[ $o_name ]['readonly'] ) { 4264 continue; 4265 } 4266 4267 update_option( $this->blog_options[ $o_name ]['option'], wp_unslash( $o_value ) ); 4268 } 4269 4270 // Now return the updated values. 4271 return $this->_getOptions( $option_names ); 4272 } 4273 4274 /** 4275 * Retrieve a media item by ID 4276 * 4277 * @since 3.1.0 4278 * 4279 * @param array $args { 4280 * Method arguments. Note: arguments must be ordered as documented. 4281 * 4282 * @type int $blog_id (unused) 4283 * @type string $username 4284 * @type string $password 4285 * @type int $attachment_id 4286 * } 4287 * @return array|IXR_Error Associative array contains: 4288 * - 'date_created_gmt' 4289 * - 'parent' 4290 * - 'link' 4291 * - 'thumbnail' 4292 * - 'title' 4293 * - 'caption' 4294 * - 'description' 4295 * - 'metadata' 4296 */ 4297 public function wp_getMediaItem( $args ) { 4298 $this->escape( $args ); 4299 4300 $username = $args[1]; 4301 $password = $args[2]; 4302 $attachment_id = (int) $args[3]; 4303 4304 $user = $this->login( $username, $password ); 4305 if ( ! $user ) { 4306 return $this->error; 4307 } 4308 4309 if ( ! current_user_can( 'upload_files' ) ) { 4310 return new IXR_Error( 403, __( 'Sorry, you are not allowed to upload files.' ) ); 4311 } 4312 4313 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 4314 do_action( 'xmlrpc_call', 'wp.getMediaItem', $args, $this ); 4315 4316 $attachment = get_post( $attachment_id ); 4317 if ( ! $attachment || 'attachment' !== $attachment->post_type ) { 4318 return new IXR_Error( 404, __( 'Invalid attachment ID.' ) ); 4319 } 4320 4321 return $this->_prepare_media_item( $attachment ); 4322 } 4323 4324 /** 4325 * Retrieves a collection of media library items (or attachments) 4326 * 4327 * Besides the common blog_id (unused), username, and password arguments, it takes a filter 4328 * array as last argument. 4329 * 4330 * Accepted 'filter' keys are 'parent_id', 'mime_type', 'offset', and 'number'. 4331 * 4332 * The defaults are as follows: 4333 * - 'number' - Default is 5. Total number of media items to retrieve. 4334 * - 'offset' - Default is 0. See WP_Query::query() for more. 4335 * - 'parent_id' - Default is ''. The post where the media item is attached. Empty string shows all media items. 0 shows unattached media items. 4336 * - 'mime_type' - Default is ''. Filter by mime type (e.g., 'image/jpeg', 'application/pdf') 4337 * 4338 * @since 3.1.0 4339 * 4340 * @param array $args { 4341 * Method arguments. Note: arguments must be ordered as documented. 4342 * 4343 * @type int $blog_id (unused) 4344 * @type string $username 4345 * @type string $password 4346 * @type array $struct 4347 * } 4348 * @return array|IXR_Error Contains a collection of media items. See wp_xmlrpc_server::wp_getMediaItem() for a description of each item contents 4349 */ 4350 public function wp_getMediaLibrary( $args ) { 4351 $this->escape( $args ); 4352 4353 $username = $args[1]; 4354 $password = $args[2]; 4355 $struct = isset( $args[3] ) ? $args[3] : array(); 4356 4357 $user = $this->login( $username, $password ); 4358 if ( ! $user ) { 4359 return $this->error; 4360 } 4361 4362 if ( ! current_user_can( 'upload_files' ) ) { 4363 return new IXR_Error( 401, __( 'Sorry, you are not allowed to upload files.' ) ); 4364 } 4365 4366 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 4367 do_action( 'xmlrpc_call', 'wp.getMediaLibrary', $args, $this ); 4368 4369 $parent_id = ( isset( $struct['parent_id'] ) ) ? absint( $struct['parent_id'] ) : ''; 4370 $mime_type = ( isset( $struct['mime_type'] ) ) ? $struct['mime_type'] : ''; 4371 $offset = ( isset( $struct['offset'] ) ) ? absint( $struct['offset'] ) : 0; 4372 $number = ( isset( $struct['number'] ) ) ? absint( $struct['number'] ) : -1; 4373 4374 $attachments = get_posts( 4375 array( 4376 'post_type' => 'attachment', 4377 'post_parent' => $parent_id, 4378 'offset' => $offset, 4379 'numberposts' => $number, 4380 'post_mime_type' => $mime_type, 4381 ) 4382 ); 4383 4384 $attachments_struct = array(); 4385 4386 foreach ( $attachments as $attachment ) { 4387 $attachments_struct[] = $this->_prepare_media_item( $attachment ); 4388 } 4389 4390 return $attachments_struct; 4391 } 4392 4393 /** 4394 * Retrieves a list of post formats used by the site. 4395 * 4396 * @since 3.1.0 4397 * 4398 * @param array $args { 4399 * Method arguments. Note: arguments must be ordered as documented. 4400 * 4401 * @type int $blog_id (unused) 4402 * @type string $username 4403 * @type string $password 4404 * } 4405 * @return array|IXR_Error List of post formats, otherwise IXR_Error object. 4406 */ 4407 public function wp_getPostFormats( $args ) { 4408 $this->escape( $args ); 4409 4410 $username = $args[1]; 4411 $password = $args[2]; 4412 4413 $user = $this->login( $username, $password ); 4414 if ( ! $user ) { 4415 return $this->error; 4416 } 4417 4418 if ( ! current_user_can( 'edit_posts' ) ) { 4419 return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) ); 4420 } 4421 4422 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 4423 do_action( 'xmlrpc_call', 'wp.getPostFormats', $args, $this ); 4424 4425 $formats = get_post_format_strings(); 4426 4427 // Find out if they want a list of currently supports formats. 4428 if ( isset( $args[3] ) && is_array( $args[3] ) ) { 4429 if ( $args[3]['show-supported'] ) { 4430 if ( current_theme_supports( 'post-formats' ) ) { 4431 $supported = get_theme_support( 'post-formats' ); 4432 4433 $data = array(); 4434 $data['all'] = $formats; 4435 $data['supported'] = $supported[0]; 4436 4437 $formats = $data; 4438 } 4439 } 4440 } 4441 4442 return $formats; 4443 } 4444 4445 /** 4446 * Retrieves a post type 4447 * 4448 * @since 3.4.0 4449 * 4450 * @see get_post_type_object() 4451 * 4452 * @param array $args { 4453 * Method arguments. Note: arguments must be ordered as documented. 4454 * 4455 * @type int $blog_id (unused) 4456 * @type string $username 4457 * @type string $password 4458 * @type string $post_type_name 4459 * @type array $fields (optional) 4460 * } 4461 * @return array|IXR_Error Array contains: 4462 * - 'labels' 4463 * - 'description' 4464 * - 'capability_type' 4465 * - 'cap' 4466 * - 'map_meta_cap' 4467 * - 'hierarchical' 4468 * - 'menu_position' 4469 * - 'taxonomies' 4470 * - 'supports' 4471 */ 4472 public function wp_getPostType( $args ) { 4473 if ( ! $this->minimum_args( $args, 4 ) ) { 4474 return $this->error; 4475 } 4476 4477 $this->escape( $args ); 4478 4479 $username = $args[1]; 4480 $password = $args[2]; 4481 $post_type_name = $args[3]; 4482 4483 if ( isset( $args[4] ) ) { 4484 $fields = $args[4]; 4485 } else { 4486 /** 4487 * Filters the default query fields used by the given XML-RPC method. 4488 * 4489 * @since 3.4.0 4490 * 4491 * @param array $fields An array of post type query fields for the given method. 4492 * @param string $method The method name. 4493 */ 4494 $fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostType' ); 4495 } 4496 4497 $user = $this->login( $username, $password ); 4498 if ( ! $user ) { 4499 return $this->error; 4500 } 4501 4502 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 4503 do_action( 'xmlrpc_call', 'wp.getPostType', $args, $this ); 4504 4505 if ( ! post_type_exists( $post_type_name ) ) { 4506 return new IXR_Error( 403, __( 'Invalid post type.' ) ); 4507 } 4508 4509 $post_type = get_post_type_object( $post_type_name ); 4510 4511 if ( ! current_user_can( $post_type->cap->edit_posts ) ) { 4512 return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type.' ) ); 4513 } 4514 4515 return $this->_prepare_post_type( $post_type, $fields ); 4516 } 4517 4518 /** 4519 * Retrieves a post types 4520 * 4521 * @since 3.4.0 4522 * 4523 * @see get_post_types() 4524 * 4525 * @param array $args { 4526 * Method arguments. Note: arguments must be ordered as documented. 4527 * 4528 * @type int $blog_id (unused) 4529 * @type string $username 4530 * @type string $password 4531 * @type array $filter (optional) 4532 * @type array $fields (optional) 4533 * } 4534 * @return array|IXR_Error 4535 */ 4536 public function wp_getPostTypes( $args ) { 4537 if ( ! $this->minimum_args( $args, 3 ) ) { 4538 return $this->error; 4539 } 4540 4541 $this->escape( $args ); 4542 4543 $username = $args[1]; 4544 $password = $args[2]; 4545 $filter = isset( $args[3] ) ? $args[3] : array( 'public' => true ); 4546 4547 if ( isset( $args[4] ) ) { 4548 $fields = $args[4]; 4549 } else { 4550 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 4551 $fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostTypes' ); 4552 } 4553 4554 $user = $this->login( $username, $password ); 4555 if ( ! $user ) { 4556 return $this->error; 4557 } 4558 4559 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 4560 do_action( 'xmlrpc_call', 'wp.getPostTypes', $args, $this ); 4561 4562 $post_types = get_post_types( $filter, 'objects' ); 4563 4564 $struct = array(); 4565 4566 foreach ( $post_types as $post_type ) { 4567 if ( ! current_user_can( $post_type->cap->edit_posts ) ) { 4568 continue; 4569 } 4570 4571 $struct[ $post_type->name ] = $this->_prepare_post_type( $post_type, $fields ); 4572 } 4573 4574 return $struct; 4575 } 4576 4577 /** 4578 * Retrieve revisions for a specific post. 4579 * 4580 * @since 3.5.0 4581 * 4582 * The optional $fields parameter specifies what fields will be included 4583 * in the response array. 4584 * 4585 * @uses wp_get_post_revisions() 4586 * @see wp_getPost() for more on $fields 4587 * 4588 * @param array $args { 4589 * Method arguments. Note: arguments must be ordered as documented. 4590 * 4591 * @type int $blog_id (unused) 4592 * @type string $username 4593 * @type string $password 4594 * @type int $post_id 4595 * @type array $fields (optional) 4596 * } 4597 * @return array|IXR_Error contains a collection of posts. 4598 */ 4599 public function wp_getRevisions( $args ) { 4600 if ( ! $this->minimum_args( $args, 4 ) ) { 4601 return $this->error; 4602 } 4603 4604 $this->escape( $args ); 4605 4606 $username = $args[1]; 4607 $password = $args[2]; 4608 $post_id = (int) $args[3]; 4609 4610 if ( isset( $args[4] ) ) { 4611 $fields = $args[4]; 4612 } else { 4613 /** 4614 * Filters the default revision query fields used by the given XML-RPC method. 4615 * 4616 * @since 3.5.0 4617 * 4618 * @param array $field An array of revision query fields. 4619 * @param string $method The method name. 4620 */ 4621 $fields = apply_filters( 'xmlrpc_default_revision_fields', array( 'post_date', 'post_date_gmt' ), 'wp.getRevisions' ); 4622 } 4623 4624 $user = $this->login( $username, $password ); 4625 if ( ! $user ) { 4626 return $this->error; 4627 } 4628 4629 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 4630 do_action( 'xmlrpc_call', 'wp.getRevisions', $args, $this ); 4631 4632 $post = get_post( $post_id ); 4633 if ( ! $post ) { 4634 return new IXR_Error( 404, __( 'Invalid post ID.' ) ); 4635 } 4636 4637 if ( ! current_user_can( 'edit_post', $post_id ) ) { 4638 return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) ); 4639 } 4640 4641 // Check if revisions are enabled. 4642 if ( ! wp_revisions_enabled( $post ) ) { 4643 return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) ); 4644 } 4645 4646 $revisions = wp_get_post_revisions( $post_id ); 4647 4648 if ( ! $revisions ) { 4649 return array(); 4650 } 4651 4652 $struct = array(); 4653 4654 foreach ( $revisions as $revision ) { 4655 if ( ! current_user_can( 'read_post', $revision->ID ) ) { 4656 continue; 4657 } 4658 4659 // Skip autosaves. 4660 if ( wp_is_post_autosave( $revision ) ) { 4661 continue; 4662 } 4663 4664 $struct[] = $this->_prepare_post( get_object_vars( $revision ), $fields ); 4665 } 4666 4667 return $struct; 4668 } 4669 4670 /** 4671 * Restore a post revision 4672 * 4673 * @since 3.5.0 4674 * 4675 * @uses wp_restore_post_revision() 4676 * 4677 * @param array $args { 4678 * Method arguments. Note: arguments must be ordered as documented. 4679 * 4680 * @type int $blog_id (unused) 4681 * @type string $username 4682 * @type string $password 4683 * @type int $revision_id 4684 * } 4685 * @return bool|IXR_Error false if there was an error restoring, true if success. 4686 */ 4687 public function wp_restoreRevision( $args ) { 4688 if ( ! $this->minimum_args( $args, 3 ) ) { 4689 return $this->error; 4690 } 4691 4692 $this->escape( $args ); 4693 4694 $username = $args[1]; 4695 $password = $args[2]; 4696 $revision_id = (int) $args[3]; 4697 4698 $user = $this->login( $username, $password ); 4699 if ( ! $user ) { 4700 return $this->error; 4701 } 4702 4703 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 4704 do_action( 'xmlrpc_call', 'wp.restoreRevision', $args, $this ); 4705 4706 $revision = wp_get_post_revision( $revision_id ); 4707 if ( ! $revision ) { 4708 return new IXR_Error( 404, __( 'Invalid post ID.' ) ); 4709 } 4710 4711 if ( wp_is_post_autosave( $revision ) ) { 4712 return new IXR_Error( 404, __( 'Invalid post ID.' ) ); 4713 } 4714 4715 $post = get_post( $revision->post_parent ); 4716 if ( ! $post ) { 4717 return new IXR_Error( 404, __( 'Invalid post ID.' ) ); 4718 } 4719 4720 if ( ! current_user_can( 'edit_post', $revision->post_parent ) ) { 4721 return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) ); 4722 } 4723 4724 // Check if revisions are disabled. 4725 if ( ! wp_revisions_enabled( $post ) ) { 4726 return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) ); 4727 } 4728 4729 $post = wp_restore_post_revision( $revision_id ); 4730 4731 return (bool) $post; 4732 } 4733 4734 /* 4735 * Blogger API functions. 4736 * Specs on http://plant.blogger.com/api and https://groups.yahoo.com/group/bloggerDev/ 4737 */ 4738 4739 /** 4740 * Retrieve blogs that user owns. 4741 * 4742 * Will make more sense once we support multiple blogs. 4743 * 4744 * @since 1.5.0 4745 * 4746 * @param array $args { 4747 * Method arguments. Note: arguments must be ordered as documented. 4748 * 4749 * @type int $blog_id (unused) 4750 * @type string $username 4751 * @type string $password 4752 * } 4753 * @return array|IXR_Error 4754 */ 4755 public function blogger_getUsersBlogs( $args ) { 4756 if ( ! $this->minimum_args( $args, 3 ) ) { 4757 return $this->error; 4758 } 4759 4760 if ( is_multisite() ) { 4761 return $this->_multisite_getUsersBlogs( $args ); 4762 } 4763 4764 $this->escape( $args ); 4765 4766 $username = $args[1]; 4767 $password = $args[2]; 4768 4769 $user = $this->login( $username, $password ); 4770 if ( ! $user ) { 4771 return $this->error; 4772 } 4773 4774 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 4775 do_action( 'xmlrpc_call', 'blogger.getUsersBlogs', $args, $this ); 4776 4777 $is_admin = current_user_can( 'manage_options' ); 4778 4779 $struct = array( 4780 'isAdmin' => $is_admin, 4781 'url' => get_option( 'home' ) . '/', 4782 'blogid' => '1', 4783 'blogName' => get_option( 'blogname' ), 4784 'xmlrpc' => site_url( 'xmlrpc.php', 'rpc' ), 4785 ); 4786 4787 return array( $struct ); 4788 } 4789 4790 /** 4791 * Private function for retrieving a users blogs for multisite setups 4792 * 4793 * @since 3.0.0 4794 * 4795 * @param array $args { 4796 * Method arguments. Note: arguments must be ordered as documented. 4797 * 4798 * @type string $username Username. 4799 * @type string $password Password. 4800 * } 4801 * @return array|IXR_Error 4802 */ 4803 protected function _multisite_getUsersBlogs( $args ) { 4804 $current_blog = get_site(); 4805 4806 $domain = $current_blog->domain; 4807 $path = $current_blog->path . 'xmlrpc.php'; 4808 4809 $rpc = new IXR_Client( set_url_scheme( "http://{$domain}{$path}" ) ); 4810 $rpc->query( 'wp.getUsersBlogs', $args[1], $args[2] ); 4811 $blogs = $rpc->getResponse(); 4812 4813 if ( isset( $blogs['faultCode'] ) ) { 4814 return new IXR_Error( $blogs['faultCode'], $blogs['faultString'] ); 4815 } 4816 4817 if ( $_SERVER['HTTP_HOST'] == $domain && $_SERVER['REQUEST_URI'] == $path ) { 4818 return $blogs; 4819 } else { 4820 foreach ( (array) $blogs as $blog ) { 4821 if ( strpos( $blog['url'], $_SERVER['HTTP_HOST'] ) ) { 4822 return array( $blog ); 4823 } 4824 } 4825 return array(); 4826 } 4827 } 4828 4829 /** 4830 * Retrieve user's data. 4831 * 4832 * Gives your client some info about you, so you don't have to. 4833 * 4834 * @since 1.5.0 4835 * 4836 * @param array $args { 4837 * Method arguments. Note: arguments must be ordered as documented. 4838 * 4839 * @type int $blog_id (unused) 4840 * @type string $username 4841 * @type string $password 4842 * } 4843 * @return array|IXR_Error 4844 */ 4845 public function blogger_getUserInfo( $args ) { 4846 $this->escape( $args ); 4847 4848 $username = $args[1]; 4849 $password = $args[2]; 4850 4851 $user = $this->login( $username, $password ); 4852 if ( ! $user ) { 4853 return $this->error; 4854 } 4855 4856 if ( ! current_user_can( 'edit_posts' ) ) { 4857 return new IXR_Error( 401, __( 'Sorry, you are not allowed to access user data on this site.' ) ); 4858 } 4859 4860 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 4861 do_action( 'xmlrpc_call', 'blogger.getUserInfo', $args, $this ); 4862 4863 $struct = array( 4864 'nickname' => $user->nickname, 4865 'userid' => $user->ID, 4866 'url' => $user->user_url, 4867 'lastname' => $user->last_name, 4868 'firstname' => $user->first_name, 4869 ); 4870 4871 return $struct; 4872 } 4873 4874 /** 4875 * Retrieve post. 4876 * 4877 * @since 1.5.0 4878 * 4879 * @param array $args { 4880 * Method arguments. Note: arguments must be ordered as documented. 4881 * 4882 * @type int $blog_id (unused) 4883 * @type int $post_ID 4884 * @type string $username 4885 * @type string $password 4886 * } 4887 * @return array|IXR_Error 4888 */ 4889 public function blogger_getPost( $args ) { 4890 $this->escape( $args ); 4891 4892 $post_ID = (int) $args[1]; 4893 $username = $args[2]; 4894 $password = $args[3]; 4895 4896 $user = $this->login( $username, $password ); 4897 if ( ! $user ) { 4898 return $this->error; 4899 } 4900 4901 $post_data = get_post( $post_ID, ARRAY_A ); 4902 if ( ! $post_data ) { 4903 return new IXR_Error( 404, __( 'Invalid post ID.' ) ); 4904 } 4905 4906 if ( ! current_user_can( 'edit_post', $post_ID ) ) { 4907 return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) ); 4908 } 4909 4910 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ 4911 do_action( 'xmlrpc_call', 'blogger.getPost', $args,