root/tags/2.0/xmlrpc.php

Revision 3310, 36.1 kB (checked in by ryan, 3 years ago)

Don't treat a numeric user name as an id.

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