root/trunk/xmlrpc.php

Revision 8202, 73.7 kB (checked in by ryan, 1 week ago)

More informative error message when remote publishing is disabled. Don't disable if upgrading. Props josephscott. see #7157

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
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 bloginfo_rss('wpurl') ?>/xmlrpc.php" />
43       <api name="Movable Type" blogID="1" preferred="false" apiLink="<?php bloginfo_rss('wpurl') ?>/xmlrpc.php" />
44       <api name="MetaWeblog" blogID="1" preferred="false" apiLink="<?php bloginfo_rss('wpurl') ?>/xmlrpc.php" />
45       <api name="Blogger" blogID="1" preferred="false" apiLink="<?php bloginfo_rss('wpurl') ?>/xmlrpc.php" />
46       <api name="Atom" blogID="" preferred="false" apiLink="<?php echo apply_filters('atom_service_url', (get_bloginfo('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  * @internal
103  * Left undocumented to work on later. If you want to finish, then please do so.
104  *
105  * @package WordPress
106  * @subpackage Publishing
107  */
108 class wp_xmlrpc_server extends IXR_Server {
109
110     function wp_xmlrpc_server() {
111         $this->methods = array(
112             // WordPress API
113             'wp.getUsersBlogs'        => 'this:wp_getUsersBlogs',
114             'wp.getPage'            => 'this:wp_getPage',
115             'wp.getPages'            => 'this:wp_getPages',
116             'wp.newPage'            => 'this:wp_newPage',
117             'wp.deletePage'            => 'this:wp_deletePage',
118             'wp.editPage'            => 'this:wp_editPage',
119             'wp.getPageList'        => 'this:wp_getPageList',
120             'wp.getAuthors'            => 'this:wp_getAuthors',
121             'wp.getCategories'        => 'this:mw_getCategories',        // Alias
122             'wp.newCategory'        => 'this:wp_newCategory',
123             'wp.deleteCategory'        => 'this:wp_deleteCategory',
124             'wp.suggestCategories'    => 'this:wp_suggestCategories',
125             'wp.uploadFile'            => 'this:mw_newMediaObject',    // Alias
126             'wp.getCommentCount'    => 'this:wp_getCommentCount',
127             'wp.getPostStatusList'    => 'this:wp_getPostStatusList',
128             'wp.getPageStatusList'    => 'this:wp_getPageStatusList',
129             'wp.getPageTemplates'    => 'this:wp_getPageTemplates',
130             'wp.getOptions'            => 'this:wp_getOptions',
131             'wp.setOptions'            => 'this:wp_setOptions',
132
133             // Blogger API
134             'blogger.getUsersBlogs' => 'this:blogger_getUsersBlogs',
135             'blogger.getUserInfo' => 'this:blogger_getUserInfo',
136             'blogger.getPost' => 'this:blogger_getPost',
137             'blogger.getRecentPosts' => 'this:blogger_getRecentPosts',
138             'blogger.getTemplate' => 'this:blogger_getTemplate',
139             'blogger.setTemplate' => 'this:blogger_setTemplate',
140             'blogger.newPost' => 'this:blogger_newPost',
141             'blogger.editPost' => 'this:blogger_editPost',
142             'blogger.deletePost' => 'this:blogger_deletePost',
143
144             // MetaWeblog API (with MT extensions to structs)
145             'metaWeblog.newPost' => 'this:mw_newPost',
146             'metaWeblog.editPost' => 'this:mw_editPost',
147             'metaWeblog.getPost' => 'this:mw_getPost',
148             'metaWeblog.getRecentPosts' => 'this:mw_getRecentPosts',
149             'metaWeblog.getCategories' => 'this:mw_getCategories',
150             'metaWeblog.newMediaObject' => 'this:mw_newMediaObject',
151
152             // MetaWeblog API aliases for Blogger API
153             // see http://www.xmlrpc.com/stories/storyReader$2460
154             'metaWeblog.deletePost' => 'this:blogger_deletePost',
155             'metaWeblog.getTemplate' => 'this:blogger_getTemplate',
156             'metaWeblog.setTemplate' => 'this:blogger_setTemplate',
157             'metaWeblog.getUsersBlogs' => 'this:blogger_getUsersBlogs',
158
159             // MovableType API
160             'mt.getCategoryList' => 'this:mt_getCategoryList',
161             'mt.getRecentPostTitles' => 'this:mt_getRecentPostTitles',
162             'mt.getPostCategories' => 'this:mt_getPostCategories',
163             'mt.setPostCategories' => 'this:mt_setPostCategories',
164             'mt.supportedMethods' => 'this:mt_supportedMethods',
165             'mt.supportedTextFilters' => 'this:mt_supportedTextFilters',
166             'mt.getTrackbackPings' => 'this:mt_getTrackbackPings',
167             'mt.publishPost' => 'this:mt_publishPost',
168
169             // PingBack
170             'pingback.ping' => 'this:pingback_ping',
171             'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks',
172
173             'demo.sayHello' => 'this:sayHello',
174             'demo.addTwoNumbers' => 'this:addTwoNumbers'
175         );
176
177         $this->initialise_blog_option_info( );
178         $this->methods = apply_filters('xmlrpc_methods', $this->methods);
179         $this->IXR_Server($this->methods);
180     }
181
182     function sayHello($args) {
183         return 'Hello!';
184     }
185
186     function addTwoNumbers($args) {
187         $number1 = $args[0];
188         $number2 = $args[1];
189         return $number1 + $number2;
190     }
191
192     function login_pass_ok($user_login, $user_pass) {
193         if ( !get_option( 'enable_xmlrpc' ) ) {
194             $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') ) );
195             return false;
196         }
197
198         if (!user_pass_ok($user_login, $user_pass)) {
199             $this->error = new IXR_Error(403, __('Bad login/pass combination.'));
200             return false;
201         }
202         return true;
203     }
204
205     function escape(&$array) {
206         global $wpdb;
207
208         if(!is_array($array)) {
209             return($wpdb->escape($array));
210         }
211         else {
212             foreach ( (array) $array as $k => $v ) {
213                 if (is_array($v)) {
214                     $this->escape($array[$k]);
215                 } else if (is_object($v)) {
216                     //skip
217                 } else {
218                     $array[$k] = $wpdb->escape($v);
219                 }
220             }
221         }
222     }
223
224     function get_custom_fields($post_id) {
225         $post_id = (int) $post_id;
226
227         $custom_fields = array();
228
229         foreach ( (array) has_meta($post_id) as $meta ) {
230             // Don't expose protected fields.
231             if ( strpos($meta['meta_key'], '_wp_') === 0 ) {
232                 continue;
233             }
234
235             $custom_fields[] = array(
236                 "id"    => $meta['meta_id'],
237                 "key"   => $meta['meta_key'],
238                 "value" => $meta['meta_value']
239             );
240         }
241
242         return $custom_fields;
243     }
244
245     function set_custom_fields($post_id, $fields) {
246         $post_id = (int) $post_id;
247
248         foreach ( (array) $fields as $meta ) {
249             if ( isset($meta['id']) ) {
250                 $meta['id'] = (int) $meta['id'];
251
252                 if ( isset($meta['key']) ) {
253                     update_meta($meta['id'], $meta['key'], $meta['value']);
254                 }
255                 else {
256                     delete_meta($meta['id']);
257                 }
258             }
259             else {
260                 $_POST['metakeyinput'] = $meta['key'];
261                 $_POST['metavalue'] = $meta['value'];
262                 add_meta($post_id);
263             }
264         }
265     }
266
267     function initialise_blog_option_info( ) {
268         global $wp_version;
269
270         $this->blog_options = array(
271             // Read only options
272             'software_name'        => array(
273                 'desc'            => __( 'Software Name' ),
274                 'readonly'        => true,
275                 'value'            => 'WordPress'
276             ),
277             'software_version'    => array(
278                 'desc'            => __( 'Software Version' ),
279                 'readonly'        => true,
280                 'value'            => $wp_version
281             ),
282             'blog_url'            => array(
283                 'desc'            => __( 'Blog URL' ),
284                 'readonly'        => true,
285                 'option'        => 'siteurl'
286             ),
287
288             // Updatable options
289             'time_zone'            => array(
290                 'desc'            => __( 'Time Zone' ),
291                 'readonly'        => false,
292                 'option'        => 'gmt_offset'
293             ),
294             'blog_title'        => array(
295                 'desc'            => __( 'Blog Title' ),
296                 'readonly'        => false,
297                 'option'            => 'blogname'
298             ),
299             'blog_tagline'        => array(
300                 'desc'            => __( 'Blog Tagline' ),
301                 'readonly'        => false,
302                 'option'        => 'blogdescription'
303             ),
304             'date_format'        => array(
305                 'desc'            => __( 'Date Format' ),
306                 'readonly'        => false,
307                 'option'        => 'date_format'
308             ),
309             'time_format'        => array(
310                 'desc'            => __( 'Time Format' ),
311                 'readonly'        => false,
312                 'option'        => 'time_format'
313             )
314         );
315
316         $this->blog_options = apply_filters( 'xmlrpc_blog_options', $this->blog_options );
317     }
318
319     /**
320      * WordPress XML-RPC API
321      * wp_getUsersBlogs
322      */
323     function wp_getUsersBlogs( $args ) {
324         // If this isn't on WPMU then just use blogger_getUsersBlogs
325         if( !function_exists( 'is_site_admin' ) ) {
326             array_unshift( $args, 1 );
327             return $this->blogger_getUsersBlogs( $args );
328         }
329
330         $this->escape( $args );
331
332         $username = $args[0];
333         $password = $args[1];
334
335         if( !$this->login_pass_ok( $username, $password ) )
336             return $this->error;
337
338         do_action( 'xmlrpc_call', 'wp.getUsersBlogs' );
339
340         $user = set_current_user( 0, $username );
341
342         $blogs = (array) get_blogs_of_user( $user->ID );
343         $struct = array( );
344
345         foreach( $blogs as $blog ) {
346             // Don't include blogs that aren't hosted at this site
347             if( $blog->site_id != 1 )
348                 continue;
349
350             $blog_id = $blog->userblog_id;
351             switch_to_blog($blog_id);
352             $is_admin = current_user_can('level_8');
353
354             $struct[] = array(
355                 'isAdmin'        => $is_admin,
356                 'url'            => get_option( 'home' ) . '/',
357                 'blogid'        => $blog_id,
358                 'blogName'        => get_option( 'blogname' ),
359                 'xmlrpc'        => get_option( 'home' ) . '/xmlrpc.php'
360             );
361         }
362
363         return $struct;
364     }
365
366     /**
367      * WordPress XML-RPC API
368      * wp_getPage
369      */
370     function wp_getPage($args) {
371         $this->escape($args);
372
373         $blog_id    = (int) $args[0];
374         $page_id    = (int) $args[1];
375         $username    = $args[2];
376         $password    = $args[3];
377
378         if(!$this->login_pass_ok($username, $password)) {
379             return($this->error);
380         }
381
382         set_current_user( 0, $username );
383         if( !current_user_can( 'edit_page', $page_id ) )
384             return new IXR_Error( 401, __( 'Sorry, you can not edit this page.' ) );
385
386         do_action('xmlrpc_call', 'wp.getPage');
387
388         // Lookup page info.
389         $page = get_page($page_id);
390
391         // If we found the page then format the data.
392         if($page->ID && ($page->post_type == "page")) {
393             // Get all of the page content and link.
394             $full_page = get_extended($page->post_content);
395             $link = post_permalink($page->ID);
396
397             // Get info the page parent if there is one.
398             $parent_title = "";
399             if(!empty($page->post_parent)) {
400                 $parent = get_page($page->post_parent);
401                 $parent_title = $parent->post_title;
402             }
403
404             // Determine comment and ping settings.
405             $allow_comments = ("open" == $page->comment_status) ? 1 : 0;
406             $allow_pings = ("open" == $page->ping_status) ? 1 : 0;
407
408             // Format page date.
409             $page_date = mysql2date("Ymd\TH:i:s", $page->post_date);
410             $page_date_gmt = mysql2date("Ymd\TH:i:s", $page->post_date_gmt);
411
412             // Pull the categories info together.
413             $categories = array();
414             foreach(wp_get_post_categories($page->ID) as $cat_id) {
415                 $categories[] = get_cat_name($cat_id);
416             }
417
418             // Get the author info.
419             $author = get_userdata($page->post_author);
420
421             $page_template = get_post_meta( $page->ID, '_wp_page_template', true );
422             if( empty( $page_template ) )
423                 $page_template = 'default';
424
425             $page_struct = array(
426                 "dateCreated"            => new IXR_Date($page_date),
427                 "userid"                => $page->post_author,
428                 "page_id"                => $page->ID,
429                 "page_status"            => $page->post_status,
430                 "description"            => $full_page["main"],
431                 "title"                    => $page->post_title,
432                 "link"                    => $link,
433                 "permaLink"                => $link,
434                 "categories"            => $categories,
435                 "excerpt"                => $page->post_excerpt,
436                 "text_more"                => $full_page["extended"],
437                 "mt_allow_comments"        => $allow_comments,
438                 "mt_allow_pings"        => $allow_pings,
439                 "wp_slug"                => $page->post_name,
440                 "wp_password"            => $page->post_password,
441                 "wp_author"                => $author->display_name,
442                 "wp_page_parent_id"        => $page->post_parent,
443                 "wp_page_parent_title"    => $parent_title,
444                 "wp_page_order"            => $page->menu_order,
445                 "wp_author_id"            => $author->ID,
446                 "wp_author_display_name"    => $author->display_name,
447                 "date_created_gmt"        => new IXR_Date($page_date_gmt),
448                 "custom_fields"            => $this->get_custom_fields($page_id),
449                 "wp_page_template"        => $page_template
450             );
451
452             return($page_struct);
453         }
454         // If the page doesn't exist indicate that.
455         else {
456             return(new IXR_Error(404, __("Sorry, no such page.")));
457         }
458     }
459
460     /**
461      * WordPress XML-RPC API
462       * wp_getPages
463      */
464     function wp_getPages($args) {
465         $this->escape($args);
466
467         $blog_id    = (int) $args[0];
468         $username    = $args[1];
469         $password    = $args[2];
470
471         if(!$this->login_pass_ok($username, $password)) {
472             return($this->error);
473         }
474
475         set_current_user( 0, $username );
476         if( !current_user_can( 'edit_pages' ) )
477             return new IXR_Error( 401, __( 'Sorry, you can not edit pages.' ) );
478
479         do_action('xmlrpc_call', 'wp.getPages');
480
481         // Lookup info on pages.
482         $pages = get_pages();
483         $num_pages = count($pages);
484
485         // If we have pages, put together their info.
486         if($num_pages >= 1) {
487             $pages_struct = array();
488
489             for($i = 0; $i < $num_pages; $i++) {
490                 $page = wp_xmlrpc_server::wp_getPage(array(
491                     $blog_id, $pages[$i]->ID, $username, $password
492                 ));
493                 $pages_struct[] = $page;
494             }
495
496             return($pages_struct);
497         }
498         // If no pages were found return an error.
499         else {
500             return(array());
501         }
502     }
503
504     /**
505      * WordPress XML-RPC API
506       * wp_newPage
507      */