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