| [ Index ] |
PHP Cross Reference of WordPress (latest release) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * XML-RPC protocol support for WordPress 4 * 5 * @license GPL v2 <./license.txt> 6 * @package WordPress 7 */ 8 9 /** 10 * Whether this is a XMLRPC Request 11 * 12 * @var bool 13 */ 14 define('XMLRPC_REQUEST', true); 15 16 // Some browser-embedded clients send cookies. We don't want them. 17 $_COOKIE = array(); 18 19 // A bug in PHP < 5.2.2 makes $HTTP_RAW_POST_DATA not set by default, 20 // but we can do it ourself. 21 if ( !isset( $HTTP_RAW_POST_DATA ) ) { 22 $HTTP_RAW_POST_DATA = file_get_contents( 'php://input' ); 23 } 24 25 // fix for mozBlog and other cases where '<?xml' isn't on the very first line 26 if ( isset($HTTP_RAW_POST_DATA) ) 27 $HTTP_RAW_POST_DATA = trim($HTTP_RAW_POST_DATA); 28 29 /** Include the bootstrap for setting up WordPress environment */ 30 include ('./wp-load.php'); 31 32 if ( isset( $_GET['rsd'] ) ) { // http://archipelago.phrasewise.com/rsd 33 header('Content-Type: text/xml; charset=' . get_option('blog_charset'), true); 34 ?> 35 <?php echo '<?xml version="1.0" encoding="'.get_option('blog_charset').'"?'.'>'; ?> 36 <rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd"> 37 <service> 38 <engineName>WordPress</engineName> 39 <engineLink>http://wordpress.org/</engineLink> 40 <homePageLink><?php bloginfo_rss('url') ?></homePageLink> 41 <apis> 42 <api name="WordPress" blogID="1" preferred="true" apiLink="<?php echo site_url('xmlrpc.php') ?>" /> 43 <api name="Movable Type" blogID="1" preferred="false" apiLink="<?php echo site_url('xmlrpc.php') ?>" /> 44 <api name="MetaWeblog" blogID="1" preferred="false" apiLink="<?php echo site_url('xmlrpc.php') ?>" /> 45 <api name="Blogger" blogID="1" preferred="false" apiLink="<?php echo site_url('xmlrpc.php') ?>" /> 46 <api name="Atom" blogID="" preferred="false" apiLink="<?php echo apply_filters('atom_service_url', site_url('wp-app.php/service') ) ?>" /> 47 </apis> 48 </service> 49 </rsd> 50 <?php 51 exit; 52 } 53 54 include_once (ABSPATH . 'wp-admin/includes/admin.php'); 55 include_once (ABSPATH . WPINC . '/class-IXR.php'); 56 57 // Turn off all warnings and errors. 58 // error_reporting(0); 59 60 /** 61 * Posts submitted via the xmlrpc interface get that title 62 * @name post_default_title 63 * @var string 64 */ 65 $post_default_title = ""; 66 67 /** 68 * Whether to enable XMLRPC Logging. 69 * 70 * @name xmlrpc_logging 71 * @var int|bool 72 */ 73 $xmlrpc_logging = 0; 74 75 /** 76 * logIO() - Writes logging info to a file. 77 * 78 * @uses $xmlrpc_logging 79 * @package WordPress 80 * @subpackage Logging 81 * 82 * @param string $io Whether input or output 83 * @param string $msg Information describing logging reason. 84 * @return bool Always return true 85 */ 86 function logIO($io,$msg) { 87 global $xmlrpc_logging; 88 if ($xmlrpc_logging) { 89 $fp = fopen("../xmlrpc.log","a+"); 90 $date = gmdate("Y-m-d H:i:s "); 91 $iot = ($io == "I") ? " Input: " : " Output: "; 92 fwrite($fp, "\n\n".$date.$iot.$msg); 93 fclose($fp); 94 } 95 return true; 96 } 97 98 if ( isset($HTTP_RAW_POST_DATA) ) 99 logIO("I", $HTTP_RAW_POST_DATA); 100 101 /** 102 * WordPress XMLRPC server implementation. 103 * 104 * Implements compatability for Blogger API, MetaWeblog API, MovableType, and 105 * pingback. Additional WordPress API for managing comments, pages, posts, 106 * options, etc. 107 * 108 * Since WordPress 2.6.0, WordPress XMLRPC server can be disabled in the 109 * administration panels. 110 * 111 * @package WordPress 112 * @subpackage Publishing 113 * @since 1.5.0 114 */ 115 class wp_xmlrpc_server extends IXR_Server { 116 117 /** 118 * Register all of the XMLRPC methods that XMLRPC server understands. 119 * 120 * PHP4 constructor and sets up server and method property. Passes XMLRPC 121 * methods through the 'xmlrpc_methods' filter to allow plugins to extend 122 * or replace XMLRPC methods. 123 * 124 * @since 1.5.0 125 * 126 * @return wp_xmlrpc_server 127 */ 128 function wp_xmlrpc_server() { 129 $this->methods = array( 130 // WordPress API 131 'wp.getUsersBlogs' => 'this:wp_getUsersBlogs', 132 'wp.getPage' => 'this:wp_getPage', 133 'wp.getPages' => 'this:wp_getPages', 134 'wp.newPage' => 'this:wp_newPage', 135 'wp.deletePage' => 'this:wp_deletePage', 136 'wp.editPage' => 'this:wp_editPage', 137 'wp.getPageList' => 'this:wp_getPageList', 138 'wp.getAuthors' => 'this:wp_getAuthors', 139 'wp.getCategories' => 'this:mw_getCategories', // Alias 140 'wp.getTags' => 'this:wp_getTags', 141 'wp.newCategory' => 'this:wp_newCategory', 142 'wp.deleteCategory' => 'this:wp_deleteCategory', 143 'wp.suggestCategories' => 'this:wp_suggestCategories', 144 'wp.uploadFile' => 'this:mw_newMediaObject', // Alias 145 'wp.getCommentCount' => 'this:wp_getCommentCount', 146 'wp.getPostStatusList' => 'this:wp_getPostStatusList', 147 'wp.getPageStatusList' => 'this:wp_getPageStatusList', 148 'wp.getPageTemplates' => 'this:wp_getPageTemplates', 149 'wp.getOptions' => 'this:wp_getOptions', 150 'wp.setOptions' => 'this:wp_setOptions', 151 'wp.getComment' => 'this:wp_getComment', 152 'wp.getComments' => 'this:wp_getComments', 153 'wp.deleteComment' => 'this:wp_deleteComment', 154 'wp.editComment' => 'this:wp_editComment', 155 'wp.newComment' => 'this:wp_newComment', 156 'wp.getCommentStatusList' => 'this:wp_getCommentStatusList', 157 158 // Blogger API 159 'blogger.getUsersBlogs' => 'this:blogger_getUsersBlogs', 160 'blogger.getUserInfo' => 'this:blogger_getUserInfo', 161 'blogger.getPost' => 'this:blogger_getPost', 162 'blogger.getRecentPosts' => 'this:blogger_getRecentPosts', 163 'blogger.getTemplate' => 'this:blogger_getTemplate', 164 'blogger.setTemplate' => 'this:blogger_setTemplate', 165 'blogger.newPost' => 'this:blogger_newPost', 166 'blogger.editPost' => 'this:blogger_editPost', 167 'blogger.deletePost' => 'this:blogger_deletePost', 168 169 // MetaWeblog API (with MT extensions to structs) 170 'metaWeblog.newPost' => 'this:mw_newPost', 171 'metaWeblog.editPost' => 'this:mw_editPost', 172 'metaWeblog.getPost' => 'this:mw_getPost', 173 'metaWeblog.getRecentPosts' => 'this:mw_getRecentPosts', 174 'metaWeblog.getCategories' => 'this:mw_getCategories', 175 'metaWeblog.newMediaObject' => 'this:mw_newMediaObject', 176 177 // MetaWeblog API aliases for Blogger API 178 // see http://www.xmlrpc.com/stories/storyReader$2460 179 'metaWeblog.deletePost' => 'this:blogger_deletePost', 180 'metaWeblog.getTemplate' => 'this:blogger_getTemplate', 181 'metaWeblog.setTemplate' => 'this:blogger_setTemplate', 182 'metaWeblog.getUsersBlogs' => 'this:blogger_getUsersBlogs', 183 184 // MovableType API 185 'mt.getCategoryList' => 'this:mt_getCategoryList', 186 'mt.getRecentPostTitles' => 'this:mt_getRecentPostTitles', 187 'mt.getPostCategories' => 'this:mt_getPostCategories', 188 'mt.setPostCategories' => 'this:mt_setPostCategories', 189 'mt.supportedMethods' => 'this:mt_supportedMethods', 190 'mt.supportedTextFilters' => 'this:mt_supportedTextFilters', 191 'mt.getTrackbackPings' => 'this:mt_getTrackbackPings', 192 'mt.publishPost' => 'this:mt_publishPost', 193 194 // PingBack 195 'pingback.ping' => 'this:pingback_ping', 196 'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks', 197 198 'demo.sayHello' => 'this:sayHello', 199 'demo.addTwoNumbers' => 'this:addTwoNumbers' 200 ); 201 202 $this->initialise_blog_option_info( ); 203 $this->methods = apply_filters('xmlrpc_methods', $this->methods); 204 $this->IXR_Server($this->methods); 205 } 206 207 /** 208 * Test XMLRPC API by saying, "Hello!" to client. 209 * 210 * @since 1.5.0 211 * 212 * @param array $args Method Parameters. 213 * @return string 214 */ 215 function sayHello($args) { 216 return 'Hello!'; 217 } 218 219 /** 220 * Test XMLRPC API by adding two numbers for client. 221 * 222 * @since 1.5.0 223 * 224 * @param array $args Method Parameters. 225 * @return int 226 */ 227 function addTwoNumbers($args) { 228 $number1 = $args[0]; 229 $number2 = $args[1]; 230 return $number1 + $number2; 231 } 232 233 /** 234 * Check user's credentials. 235 * 236 * @since 1.5.0 237 * 238 * @param string $user_login User's username. 239 * @param string $user_pass User's password. 240 * @return bool Whether authentication passed. 241 */ 242 function login_pass_ok($user_login, $user_pass) { 243 if ( !get_option( 'enable_xmlrpc' ) ) { 244 $this->error = new IXR_Error( 405, sprintf( __( 'XML-RPC services are disabled on this blog. An admin user can enable them at %s'), admin_url('options-writing.php') ) ); 245 return false; 246 } 247 248 if (!user_pass_ok($user_login, $user_pass)) { 249 $this->error = new IXR_Error(403, __('Bad login/pass combination.')); 250 return false; 251 } 252 return true; 253 } 254 255 /** 256 * Sanitize string or array of strings for database. 257 * 258 * @since 1.5.2 259 * 260 * @param string|array $array Sanitize single string or array of strings. 261 * @return string|array Type matches $array and sanitized for the database. 262 */ 263 function escape(&$array) { 264 global $wpdb; 265 266 if(!is_array($array)) { 267 return($wpdb->escape($array)); 268 } 269 else { 270 foreach ( (array) $array as $k => $v ) { 271 if (is_array($v)) { 272 $this->escape($array[$k]); 273 } else if (is_object($v)) { 274 //skip 275 } else { 276 $array[$k] = $wpdb->escape($v); 277 } 278 } 279 } 280 } 281 282 /** 283 * Retrieve custom fields for post. 284 * 285 * @since 2.5.0 286 * 287 * @param int $post_id Post ID. 288 * @return array Custom fields, if exist. 289 */ 290 function get_custom_fields($post_id) { 291 $post_id = (int) $post_id; 292 293 $custom_fields = array(); 294 295 foreach ( (array) has_meta($post_id) as $meta ) { 296 // Don't expose protected fields. 297 if ( strpos($meta['meta_key'], '_wp_') === 0 ) { 298 continue; 299 } 300 301 $custom_fields[] = array( 302 "id" => $meta['meta_id'], 303 "key" => $meta['meta_key'], 304 "value" => $meta['meta_value'] 305 ); 306 } 307 308 return $custom_fields; 309 } 310 311 /** 312 * Set custom fields for post. 313 * 314 * @since 2.5.0 315 * 316 * @param int $post_id Post ID. 317 * @param array $fields Custom fields. 318 */ 319 function set_custom_fields($post_id, $fields) { 320 $post_id = (int) $post_id; 321 322 foreach ( (array) $fields as $meta ) { 323 if ( isset($meta['id']) ) { 324 $meta['id'] = (int) $meta['id']; 325 326 if ( isset($meta['key']) ) { 327 update_meta($meta['id'], $meta['key'], $meta['value']); 328 } 329 else { 330 delete_meta($meta['id']); 331 } 332 } 333 else { 334 $_POST['metakeyinput'] = $meta['key']; 335 $_POST['metavalue'] = $meta['value']; 336 add_meta($post_id); 337 } 338 } 339 } 340 341 /** 342 * Setup blog options property. 343 * 344 * Passes property through 'xmlrpc_blog_options' filter. 345 * 346 * @since 2.6.0 347 */ 348 function initialise_blog_option_info( ) { 349 global $wp_version; 350 351 $this->blog_options = array( 352 // Read only options 353 'software_name' => array( 354 'desc' => __( 'Software Name' ), 355 'readonly' => true, 356 'value' => 'WordPress' 357 ), 358 'software_version' => array( 359 'desc' => __( 'Software Version' ), 360 'readonly' => true, 361 'value' => $wp_version 362 ), 363 'blog_url' => array( 364 'desc' => __( 'Blog URL' ), 365 'readonly' => true, 366 'option' => 'siteurl' 367 ), 368 369 // Updatable options 370 'time_zone' => array( 371 'desc' => __( 'Time Zone' ), 372 'readonly' => false, 373 'option' => 'gmt_offset' 374 ), 375 'blog_title' => array( 376 'desc' => __( 'Blog Title' ), 377 'readonly' => false, 378 'option' => 'blogname' 379 ), 380 'blog_tagline' => array( 381 'desc' => __( 'Blog Tagline' ), 382 'readonly' => false, 383 'option' => 'blogdescription' 384 ), 385 'date_format' => array( 386 'desc' => __( 'Date Format' ), 387 'readonly' => false, 388 'option' => 'date_format' 389 ), 390 'time_format' => array( 391 'desc' => __( 'Time Format' ), 392 'readonly' => false, 393 'option' => 'time_format' 394 ) 395 ); 396 397 $this->blog_options = apply_filters( 'xmlrpc_blog_options', $this->blog_options ); 398 } 399 400 /** 401 * Retrieve the blogs of the user. 402 * 403 * @since 2.6.0 404 * 405 * @param array $args Method parameters. 406 * @return array 407 */ 408 function wp_getUsersBlogs( $args ) { 409 // If this isn't on WPMU then just use blogger_getUsersBlogs 410 if( !function_exists( 'is_site_admin' ) ) { 411 array_unshift( $args, 1 ); 412 return $this->blogger_getUsersBlogs( $args ); 413 } 414 415 $this->escape( $args ); 416 417 $username = $args[0]; 418 $password = $args[1]; 419 420 if( !$this->login_pass_ok( $username, $password ) ) 421 return $this->error; 422 423 do_action( 'xmlrpc_call', 'wp.getUsersBlogs' ); 424 425 $user = set_current_user( 0, $username ); 426 427 $blogs = (array) get_blogs_of_user( $user->ID ); 428 $struct = array( ); 429 430 foreach( $blogs as $blog ) { 431 // Don't include blogs that aren't hosted at this site 432 if( $blog->site_id != $current_site->id ) 433 continue; 434 435 $blog_id = $blog->userblog_id; 436 switch_to_blog($blog_id); 437 $is_admin = current_user_can('level_8'); 438 439 $struct[] = array( 440 'isAdmin' => $is_admin, 441 'url' => get_option( 'home' ) . '/', 442 'blogid' => $blog_id, 443 'blogName' => get_option( 'blogname' ), 444 'xmlrpc' => get_option( 'home' ) . '/xmlrpc.php' 445 ); 446 447 restore_current_blog( ); 448 } 449 450 return $struct; 451 } 452 453 /** 454 * Retrieve page. 455 * 456 * @since 2.2.0 457 * 458 * @param array $args Method parameters. 459 * @return array 460 */ 461 function wp_getPage($args) { 462 $this->escape($args); 463 464 $blog_id = (int) $args[0]; 465 $page_id = (int) $args[1]; 466 $username = $args[2]; 467 $password = $args[3]; 468 469 if(!$this->login_pass_ok($username, $password)) { 470 return($this->error); 471 } 472 473 set_current_user( 0, $username ); 474 if( !current_user_can( 'edit_page', $page_id ) ) 475 return new IXR_Error( 401, __( 'Sorry, you can not edit this page.' ) ); 476 477 do_action('xmlrpc_call', 'wp.getPage'); 478 479 // Lookup page info. 480 $page = get_page($page_id); 481 482 // If we found the page then format the data. 483 if($page->ID && ($page->post_type == "page")) { 484 // Get all of the page content and link. 485 $full_page = get_extended($page->post_content); 486 $link = post_permalink($page->ID); 487 488 // Get info the page parent if there is one. 489 $parent_title = ""; 490 if(!empty($page->post_parent)) { 491 $parent = get_page($page->post_parent); 492 $parent_title = $parent->post_title; 493 } 494 495 // Determine comment and ping settings. 496 $allow_comments = ("open" == $page->comment_status) ? 1 : 0; 497 $allow_pings = ("open" == $page->ping_status) ? 1 : 0; 498 499 // Format page date. 500 $page_date = mysql2date("Ymd\TH:i:s", $page->post_date); 501 $page_date_gmt = mysql2date("Ymd\TH:i:s", $page->post_date_gmt); 502 503 // Pull the categories info together. 504 $categories = array(); 505 foreach(wp_get_post_categories($page->ID) as $cat_id) { 506 $categories[] = get_cat_name($cat_id); 507 } 508 509 // Get the author info. 510 $author = get_userdata($page->post_author); 511 512 $page_template = get_post_meta( $page->ID, '_wp_page_template', true ); 513 if( empty( $page_template ) ) 514 $page_template = 'default'; 515 516 $page_struct = array( 517 "dateCreated" => new IXR_Date($page_date), 518 "userid" => $page->post_author, 519 "page_id" => $page->ID, 520 "page_status" => $page->post_status, 521 "description" => $full_page["main"], 522 "title" => $page->post_title, 523 "link" => $link, 524 "permaLink" => $link, 525 "categories" => $categories, 526 "excerpt" => $page->post_excerpt, 527 "text_more" => $full_page["extended"], 528 "mt_allow_comments" => $allow_comments, 529 "mt_allow_pings" => $allow_pings, 530 "wp_slug" => $page->post_name, 531 "wp_password" => $page->post_password, 532 "wp_author" => $author->display_name, 533 "wp_page_parent_id" => $page->post_parent, 534 "wp_page_parent_title" => $parent_title, 535 "wp_page_order" => $page->menu_order, 536 "wp_author_id" => $author->ID, 537 "wp_author_display_name" => $author->display_name, 538 "date_created_gmt" => new IXR_Date($page_date_gmt), 539 "custom_fields" => $this->get_custom_fields($page_id), 540 "wp_page_template" => $page_template 541 ); 542 543 return($page_struct); 544 } 545 // If the page doesn't exist indicate that. 546 else { 547 return(new IXR_Error(404, __("Sorry, no such page."))); 548 } 549 } 550 551 /** 552 * Retrieve Pages. 553 * 554 * @since 2.2.0 555 * 556 * @param array $args Method parameters. 557 * @return array 558 */ 559 function wp_getPages($args) { 560 $this->escape($args); 561 562 $blog_id = (int) $args[0]; 563 $username = $args[1]; 564 $password = $args[2]; 565 $num_pages = (int) $args[3]; 566 567 if(!$this->login_pass_ok($username, $password)) { 568 return($this->error); 569 } 570 571 set_current_user( 0, $username ); 572 if( !current_user_can( 'edit_pages' ) ) 573 return new IXR_Error( 401, __( 'Sorry, you can not edit pages.' ) ); 574 575 do_action('xmlrpc_call', 'wp.getPages'); 576 577 $page_limit = 10; 578 if( isset( $num_pages ) ) { 579 $page_limit = $num_pages; 580 } 581 582 $pages = get_posts( "post_type=page&post_status=all&numberposts={$page_limit}" ); 583 $num_pages = count($pages); 584 585 // If we have pages, put together their info. 586 if($num_pages >= 1) { 587 $pages_struct = array(); 588 589 for($i = 0; $i < $num_pages; $i++) { 590 $page = wp_xmlrpc_server::wp_getPage(array( 591 $blog_id, $pages[$i]->ID, $username, $password 592 )); 593 $pages_struct[] = $page; 594 } 595 596 return($pages_struct); 597 } 598 // If no pages were found return an error. 599 else { 600 return(array()); 601 } 602 } 603 604 /** 605 * Create new page. 606 * 607 * @since 2.2.0 608 * 609 * @param array $args Method parameters. 610 * @return unknown 611 */ 612 function wp_newPage($args) { 613 // Items not escaped here will be escaped in newPost. 614 $username = $this->escape($args[1]); 615 $password = $this->escape($args[2]); 616 $page = $args[3]; 617 $publish = $args[4]; 618 619 if(!$this->login_pass_ok($username, $password)) { 620 return($this->error); 621 } 622 623 do_action('xmlrpc_call', 'wp.newPage'); 624 625 // Set the user context and check if they are allowed 626 // to add new pages. 627 $user = set_current_user(0, $username); 628 if(!current_user_can("publish_pages")) { 629 return(new IXR_Error(401, __("Sorry, you can not add new pages."))); 630 } 631 632 // Mark this as content for a page. 633 $args[3]["post_type"] = "page"; 634 635 // Let mw_newPost do all of the heavy lifting. 636 return($this->mw_newPost($args)); 637 } 638 639 /** 640 * Delete page. 641 * 642 * @since 2.2.0 643 * 644 * @param array $args Method parameters. 645 * @return bool True, if success. 646 */ 647 function wp_deletePage($args) { 648 $this->escape($args); 649 650 $blog_id = (int) $args[0]; 651 $username = $args[1]; 652 $password = $args[2]; 653 $page_id = (int) $args[3]; 654 655 if(!$this->login_pass_ok($username, $password)) { 656 return($this->error); 657 } 658 659 do_action('xmlrpc_call', 'wp.deletePage'); 660 661 // Get the current page based on the page_id and 662 // make sure it is a page and not a post. 663