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