[ Index ]

PHP Cross Reference of WordPress (latest release)

title

Body

[close]

/ -> xmlrpc.php (source)

   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