root/branches/1.5/xmlrpc.php

Revision 2780, 37.4 kB (checked in by matt, 3 years ago)

Fixes #967, Fixes #972, Fixes #1578, Fixes #1580, Fixes #1567, Fixes #1481, Fixes #1186

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