root/tags/2.1/xmlrpc.php

Revision 4710, 37.5 kB (checked in by ryan, 2 years ago)

sanitize_file_name(). fixes #3382 #3554

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 <?php
2
3 define('XMLRPC_REQUEST', true);
4
5 // Some browser-embedded clients send cookies. We don't want them.
6 $_COOKIE = array();
7
8 # fix for mozBlog and other cases where '<?xml' isn't on the very first line
9 if ( isset($HTTP_RAW_POST_DATA) )
10     $HTTP_RAW_POST_DATA = trim($HTTP_RAW_POST_DATA);
11
12 include('./wp-config.php');
13
14 if ( isset( $_GET['rsd'] ) ) { // http://archipelago.phrasewise.com/rsd
15 header('Content-type: text/xml; charset=' . get_option('blog_charset'), true);
16
17 ?>
18 <?php echo '<?xml version="1.0" encoding="'.get_option('blog_charset').'"?'.'>'; ?>
19 <rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd">
20   <service>
21     <engineName>WordPress</engineName>
22     <engineLink>http://wordpress.org/</engineLink>
23     <homePageLink><?php bloginfo_rss('url') ?></homePageLink>
24     <apis>
25       <api name="Movable Type" blogID="1" preferred="true" apiLink="<?php bloginfo_rss('url') ?>/xmlrpc.php" />
26       <api name="MetaWeblog" blogID="1" preferred="false" apiLink="<?php bloginfo_rss('url') ?>/xmlrpc.php" />
27       <api name="Blogger" blogID="1" preferred="false" apiLink="<?php bloginfo_rss('url') ?>/xmlrpc.php" />
28     </apis>
29   </service>
30 </rsd>
31 <?php
32 exit;
33 }
34
35 include_once(ABSPATH . 'wp-admin/admin-functions.php');
36 include_once(ABSPATH . WPINC . '/class-IXR.php');
37
38 // Turn off all warnings and errors.
39 // error_reporting(0);
40
41 $post_default_title = ""; // posts submitted via the xmlrpc interface get that title
42
43 $xmlrpc_logging = 0;
44
45 function logIO($io,$msg) {
46     global $xmlrpc_logging;
47     if ($xmlrpc_logging) {
48         $fp = fopen("../xmlrpc.log","a+");
49         $date = gmdate("Y-m-d H:i:s ");
50         $iot = ($io == "I") ? " Input: " : " Output: ";
51         fwrite($fp, "\n\n".$date.$iot.$msg);
52         fclose($fp);
53     }
54     return true;
55     }
56
57 function starify($string) {
58     $i = strlen($string);
59     return str_repeat('*', $i);
60 }
61
62 if ( isset($HTTP_RAW_POST_DATA) )
63   logIO("I", $HTTP_RAW_POST_DATA);
64
65
66 class wp_xmlrpc_server extends IXR_Server {
67
68     function wp_xmlrpc_server() {
69         $this->methods = array(
70             // Blogger API
71             'blogger.getUsersBlogs' => 'this:blogger_getUsersBlogs',
72             'blogger.getUserInfo' => 'this:blogger_getUserInfo',
73             'blogger.getPost' => 'this:blogger_getPost',
74             'blogger.getRecentPosts' => 'this:blogger_getRecentPosts',
75             'blogger.getTemplate' => 'this:blogger_getTemplate',
76             'blogger.setTemplate' => 'this:blogger_setTemplate',
77             'blogger.newPost' => 'this:blogger_newPost',
78             'blogger.editPost' => 'this:blogger_editPost',
79             'blogger.deletePost' => 'this:blogger_deletePost',
80
81             // MetaWeblog API (with MT extensions to structs)
82             'metaWeblog.newPost' => 'this:mw_newPost',
83             'metaWeblog.editPost' => 'this:mw_editPost',
84             'metaWeblog.getPost' => 'this:mw_getPost',
85             'metaWeblog.getRecentPosts' => 'this:mw_getRecentPosts',
86             'metaWeblog.getCategories' => 'this:mw_getCategories',
87             'metaWeblog.newMediaObject' => 'this:mw_newMediaObject',
88
89             // MetaWeblog API aliases for Blogger API
90             // see http://www.xmlrpc.com/stories/storyReader$2460
91             'metaWeblog.deletePost' => 'this:blogger_deletePost',
92             'metaWeblog.getTemplate' => 'this:blogger_getTemplate',
93             'metaWeblog.setTemplate' => 'this:blogger_setTemplate',
94             'metaWeblog.getUsersBlogs' => 'this:blogger_getUsersBlogs',
95
96             // MovableType API
97             'mt.getCategoryList' => 'this:mt_getCategoryList',
98             'mt.getRecentPostTitles' => 'this:mt_getRecentPostTitles',
99             'mt.getPostCategories' => 'this:mt_getPostCategories',
100             'mt.setPostCategories' => 'this:mt_setPostCategories',
101             'mt.supportedMethods' => 'this:mt_supportedMethods',
102             'mt.supportedTextFilters' => 'this:mt_supportedTextFilters',
103             'mt.getTrackbackPings' => 'this:mt_getTrackbackPings',
104             'mt.publishPost' => 'this:mt_publishPost',
105
106             // PingBack
107             'pingback.ping' => 'this:pingback_ping',
108             'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks',
109
110             'demo.sayHello' => 'this:sayHello',
111             'demo.addTwoNumbers' => 'this:addTwoNumbers'
112         );
113         $this->methods = apply_filters('xmlrpc_methods', $this->methods);
114         $this->IXR_Server($this->methods);
115     }
116
117     function sayHello($args) {
118         return 'Hello!';
119     }
120
121     function addTwoNumbers($args) {
122         $number1 = $args[0];
123         $number2 = $args[1];
124         return $number1 + $number2;
125     }
126
127     function login_pass_ok($user_login, $user_pass) {
128         if (!user_pass_ok($user_login, $user_pass)) {
129             $this->error = new IXR_Error(403, 'Bad login/pass combination.');
130             return false;
131         }
132         return true;
133     }
134
135     function escape(&$array) {
136         global $wpdb;
137
138         foreach ( (array) $array as $k => $v ) {
139             if (is_array($v)) {
140                 $this->escape($array[$k]);
141             } else if (is_object($v)) {
142                 //skip
143             } else {
144                 $array[$k] = $wpdb->escape($v);
145             }
146         }
147     }
148
149     /* Blogger API functions
150      * specs on http://plant.blogger.com/api and http://groups.yahoo.com/group/bloggerDev/
151      */
152
153
154     /* blogger.getUsersBlogs will make more sense once we support multiple blogs */
155     function blogger_getUsersBlogs($args) {
156
157         $this->escape($args);
158
159       $user_login = $args[1];
160       $user_pass  = $args[2];
161
162       if (!$this->login_pass_ok($user_login, $user_pass)) {
163         return $this->error;
164       }
165
166       set_current_user(0, $user_login);
167       $is_admin = current_user_can('level_8');
168
169       $struct = array(
170         'isAdmin'  => $is_admin,
171         'url'      => get_option('home') . '/',
172         'blogid'   => '1',
173         'blogName' => get_option('blogname')
174       );
175
176       return array($struct);
177     }
178
179
180     /* blogger.getUsersInfo gives your client some info about you, so you don't have to */
181     function blogger_getUserInfo($args) {
182
183         $this->escape($args);
184
185         $user_login = $args[1];
186         $user_pass  = $args[2];
187
188         if (!$this->login_pass_ok($user_login, $user_pass)) {
189             return $this->error;
190         }
191
192         $user_data = get_userdatabylogin($user_login);
193
194         $struct = array(
195             'nickname'  => $user_data->nickname,
196             'userid'    => $user_data->ID,
197             'url'       => $user_data->user_url,
198             'email'     => $user_data->user_email,
199             'lastname'  => $user_data->last_name,
200             'firstname' => $user_data->first_name
201         );
202
203         return $struct;
204     }
205
206
207     /* blogger.getPost ...gets a post */
208     function blogger_getPost($args) {
209
210         $this->escape($args);
211
212         $post_ID    = $args[1];
213         $user_login = $args[2];
214         $user_pass  = $args[3];
215
216         if (!$this->login_pass_ok($user_login, $user_pass)) {
217             return $this->error;
218         }
219
220         $user_data = get_userdatabylogin($user_login);
221         $post_data = wp_get_single_post($post_ID, ARRAY_A);
222
223         $categories = implode(',', wp_get_post_categories($post_ID));
224
225         $content  = '<title>'.stripslashes($post_data['post_title']).'</title>';
226         $content .= '<category>'.$categories.'</category>';
227         $content .= stripslashes($post_data['post_content']);
228
229         $struct = array(
230             'userid'    => $post_data['post_author'],
231             'dateCreated' => new IXR_Date(mysql2date('Ymd\TH:i:s', $post_data['post_date'])),
232             'content'     => $content,
233             'postid'  => $post_data['ID']
234         );
235
236         return $struct;
237     }
238
239
240     /* blogger.getRecentPosts ...gets recent posts */
241     function blogger_getRecentPosts($args) {
242
243         global $wpdb;
244
245         $this->escape($args);
246
247         $blog_ID    = $args[1]; /* though we don't use it yet */
248         $user_login = $args[2];
249         $user_pass  = $args[3];
250         $num_posts  = $args[4];
251
252         if (!$this->login_pass_ok($user_login, $user_pass)) {
253             return $this->error;
254         }
255
256         $posts_list = wp_get_recent_posts($num_posts);
257
258         if (!$posts_list) {
259             $this->error = new IXR_Error(500, 'Either there are no posts, or something went wrong.');
260             return $this->error;
261         }
262
263         foreach ($posts_list as $entry) {
264
265             $post_date = mysql2date('Ymd\TH:i:s', $entry['post_date']);
266             $categories = implode(',', wp_get_post_categories($entry['ID']));
267
268             $content  = '<title>'.stripslashes($entry['post_title']).'</title>';
269             $content .= '<category>'.$categories.'</category>';
270             $content .= stripslashes($entry['post_content']);
271
272             $struct[] = array(
273                 'userid' => $entry['post_author'],
274                 'dateCreated' => new IXR_Date($post_date),
275                 'content' => $content,
276                 'postid' => $entry['ID'],
277             );
278
279         }
280
281         $recent_posts = array();
282         for ($j=0; $j<count($struct); $j++) {
283             array_push($recent_posts, $struct[$j]);
284         }
285
286         return $recent_posts;
287     }
288
289
290     /* blogger.getTemplate returns your blog_filename */
291     function blogger_getTemplate($args) {
292
293         $this->escape($args);
294
295       $blog_ID    = $args[1];
296       $user_login = $args[2];
297       $user_pass  = $args[3];
298       $template   = $args[4]; /* could be 'main' or 'archiveIndex', but we don't use it */
299
300       if (!$this->login_pass_ok($user_login, $user_pass)) {
301         return $this->error;
302       }
303
304       set_current_user(0, $user_login);
305       if ( !current_user_can('edit_themes') ) {
306         return new IXR_Error(401, 'Sorry, this user can not edit the template.');
307       }
308
309       /* warning: here we make the assumption that the weblog's URL is on the same server */
310       $filename = get_option('home') . '/';
311       $filename = preg_replace('#https?://.+?/#', $_SERVER['DOCUMENT_ROOT'].'/', $filename);
312
313       $f = fopen($filename, 'r');
314       $content = fread($f, filesize($filename));
315       fclose($f);
316
317       /* so it is actually editable with a windows/mac client */
318       // FIXME: (or delete me) do we really want to cater to bad clients at the expense of good ones by BEEPing up their line breaks? commented.     $content = str_replace("\n", "\r\n", $content);
319
320       return $content;
321     }
322
323
324     /* blogger.setTemplate updates the content of blog_filename */
325     function blogger_setTemplate($args) {
326
327         $this->escape($args);
328
329       $blog_ID    = $args[1];
330       $user_login = $args[2];
331       $user_pass  = $args[3];
332       $content    = $args[4];
333       $template   = $args[5]; /* could be 'main' or 'archiveIndex', but we don't use it */
334
335       if (!$this->login_pass_ok($user_login, $user_pass)) {
336         return $this->error;
337       }
338
339       set_current_user(0, $user_login);
340       if ( !current_user_can('edit_themes') ) {
341         return new IXR_Error(401, 'Sorry, this user can not edit the template.');
342       }
343
344       /* warning: here we make the assumption that the weblog's URL is on the same server */
345       $filename = get_option('home') . '/';
346       $filename = preg_replace('#https?://.+?/#', $_SERVER['DOCUMENT_ROOT'].'/', $filename);
347
348       if ($f = fopen($filename, 'w+')) {
349         fwrite($f, $content);
350         fclose($f);
351       } else {
352         return new IXR_Error(500, 'Either the file is not writable, or something wrong happened. The file has not been updated.');
353       }
354
355       return true;
356     }
357
358
359     /* blogger.newPost ...creates a new post */
360     function blogger_newPost($args) {
361
362       global $wpdb;
363
364         $this->escape($args);
365
366       $blog_ID    = $args[1]; /* though we don't use it yet */
367       $user_login = $args[2];
368       $user_pass  = $args[3];
369       $content    = $args[4];
370       $publish    = $args[5];
371
372       if (!$this->login_pass_ok($user_login, $user_pass)) {
373         return $this->error;
374       }
375       
376       $cap = ($publish) ? 'publish_posts' : 'edit_posts';
377       $user = set_current_user(0, $user_login);
378       if ( !current_user_can($cap) )
379         return new IXR_Error(401, 'Sorry, you can not post on this weblog or category.');
380
381       $post_status = ($publish) ? 'publish' : 'draft';
382
383       $post_author = $user->ID;
384
385       $post_title = xmlrpc_getposttitle($content);
386       $post_category = xmlrpc_getpostcategory($content);
387       $post_content = xmlrpc_removepostdata($content);
388
389       $post_date = current_time('mysql');
390       $post_date_gmt = current_time('mysql', 1);
391
392       $post_data = compact('blog_ID', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status');
393
394       $post_ID = wp_insert_post($post_data);
395
396       if (!$post_ID) {
397         return new IXR_Error(500, 'Sorry, your entry could not be posted. Something wrong happened.');
398       }
399       $this->attach_uploads( $post_ID, $post_content );
400
401       logIO('O', "Posted ! ID: $post_ID");
402
403       return $post_ID;
404     }
405
406
407     /* blogger.editPost ...edits a post */
408     function blogger_editPost($args) {
409
410       global $wpdb;
411
412         $this->escape($args);
413
414       $post_ID     = $args[1];
415       $user_login  = $args[2];
416       $user_pass   = $args[3];
417       $content     = $args[4];
418       $publish     = $args[5];
419
420       if (!$this->login_pass_ok($user_login, $user_pass)) {
421         return $this->error;
422       }
423
424       $actual_post = wp_get_single_post($post_ID,ARRAY_A);
425
426       if (!$actual_post) {
427           return new IXR_Error(404, 'Sorry, no such post.');
428       }
429
430         $this->escape($actual_post);
431
432       set_current_user(0, $user_login);
433       if ( !current_user_can('edit_post', $post_ID) )
434         return new IXR_Error(401, 'Sorry, you do not have the right to edit this post.');
435
436       extract($actual_post);
437
438       $post_title = xmlrpc_getposttitle($content);
439       $post_category = xmlrpc_getpostcategory($content);
440       $post_content = xmlrpc_removepostdata($content);
441
442       $postdata = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt');
443
444       $result = wp_update_post($postdata);
445
446       if (!$result) {
447           return new IXR_Error(500, 'For some strange yet very annoying reason, this post could not be edited.');
448       }
449       $this->attach_uploads( $ID, $post_content );
450
451       return true;
452     }
453
454
455     /* blogger.deletePost ...deletes a post */
456     function blogger_deletePost($args) {
457
458       global $wpdb;
459
460         $this->escape($args);
461
462       $post_ID     = $args[1];
463       $user_login  = $args[2];
464       $user_pass   = $args[3];
465       $publish     = $args[4];
466
467       if (!$this->login_pass_ok($user_login, $user_pass)) {
468         return $this->error;
469       }
470
471       $actual_post = wp_get_single_post($post_ID,ARRAY_A);
472
473       if (!$actual_post) {
474           return new IXR_Error(404, 'Sorry, no such post.');
475       }
476
477       set_current_user(0, $user_login);
478       if ( !current_user_can('edit_post', $post_ID) )
479         return new IXR_Error(401, 'Sorry, you do not have the right to delete this post.');
480
481       $result = wp_delete_post($post_ID);
482
483       if (!$result) {
484           return new IXR_Error(500, 'For some strange yet very annoying reason, this post could not be deleted.');
485       }
486
487       return true;
488     }
489
490
491
492     /* MetaWeblog API functions
493      * specs on wherever Dave Winer wants them to be
494      */
495
496     /* metaweblog.newPost creates a post */
497     function mw_newPost($args) {
498
499       global $wpdb, $post_default_category;
500
501         $this->escape($args);
502
503       $blog_ID     = $args[0]; // we will support this in the near future
504       $user_login  = $args[1];
505       $user_pass   = $args[2];
506       $content_struct = $args[3];
507       $publish     = $args[4];
508
509       if (!$this->login_pass_ok($user_login, $user_pass)) {
510         return $this->error;
511       }
512
513       $user = set_current_user(0, $user_login);
514       if ( !current_user_can('publish_posts') )
515         return new IXR_Error(401, 'Sorry, you can not post on this weblog or category.');
516
517       $post_author = $user->ID;
518
519       $post_title = $content_struct['title'];
520       $post_content = apply_filters( 'content_save_pre', $content_struct['description'] );
521       $post_status = $publish ? 'publish' : 'draft';
522