Wordpress 2.0.2
[autoinstalls/wordpress.git] / xmlrpc.php
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_settings('blog_charset'), true);
16
17 ?>
18 <?php echo '<?xml version="1.0" encoding="'.get_settings('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 . WPINC . '/class-IXR.php');
36
37 // Turn off all warnings and errors.
38 // error_reporting(0);
39
40 $post_default_title = ""; // posts submitted via the xmlrpc interface get that title
41
42 $xmlrpc_logging = 0;
43
44 function logIO($io,$msg) {
45         global $xmlrpc_logging;
46         if ($xmlrpc_logging) {
47                 $fp = fopen("../xmlrpc.log","a+");
48                 $date = gmdate("Y-m-d H:i:s ");
49                 $iot = ($io == "I") ? " Input: " : " Output: ";
50                 fwrite($fp, "\n\n".$date.$iot.$msg);
51                 fclose($fp);
52         }
53         return true;
54         }
55
56 function starify($string) {
57         $i = strlen($string);
58         return str_repeat('*', $i);
59 }
60
61 logIO("I", $HTTP_RAW_POST_DATA);
62
63
64 function mkdir_p($target) {
65         // from php.net/mkdir user contributed notes 
66         if (file_exists($target)) {
67           if (!is_dir($target)) {
68             return false;
69           } else {
70             return true;
71           }
72         }
73
74         // Attempting to create the directory may clutter up our display.
75         if (@mkdir($target)) {
76           return true;
77         }
78
79         // If the above failed, attempt to create the parent node, then try again.
80         if (mkdir_p(dirname($target))) {
81           return mkdir_p($target);
82         }
83
84         return false;
85 }
86
87
88 class wp_xmlrpc_server extends IXR_Server {
89
90         function wp_xmlrpc_server() {
91                 $this->methods = array(
92                   // Blogger API
93                   'blogger.getUsersBlogs' => 'this:blogger_getUsersBlogs',
94                   'blogger.getUserInfo' => 'this:blogger_getUserInfo',
95                   'blogger.getPost' => 'this:blogger_getPost',
96                   'blogger.getRecentPosts' => 'this:blogger_getRecentPosts',
97                   'blogger.getTemplate' => 'this:blogger_getTemplate',
98                   'blogger.setTemplate' => 'this:blogger_setTemplate',
99                   'blogger.newPost' => 'this:blogger_newPost',
100                   'blogger.editPost' => 'this:blogger_editPost',
101                   'blogger.deletePost' => 'this:blogger_deletePost',
102
103                   // MetaWeblog API (with MT extensions to structs)
104                   'metaWeblog.newPost' => 'this:mw_newPost',
105                   'metaWeblog.editPost' => 'this:mw_editPost',
106                   'metaWeblog.getPost' => 'this:mw_getPost',
107                   'metaWeblog.getRecentPosts' => 'this:mw_getRecentPosts',
108                   'metaWeblog.getCategories' => 'this:mw_getCategories',
109                   'metaWeblog.newMediaObject' => 'this:mw_newMediaObject',
110
111                   // MetaWeblog API aliases for Blogger API
112                   // see http://www.xmlrpc.com/stories/storyReader$2460
113                   'metaWeblog.deletePost' => 'this:blogger_deletePost',
114                   'metaWeblog.getTemplate' => 'this:blogger_getTemplate',
115                   'metaWeblog.setTemplate' => 'this:blogger_setTemplate',
116                   'metaWeblog.getUsersBlogs' => 'this:blogger_getUsersBlogs',
117
118                   // MovableType API
119                   'mt.getCategoryList' => 'this:mt_getCategoryList',
120                   'mt.getRecentPostTitles' => 'this:mt_getRecentPostTitles',
121                   'mt.getPostCategories' => 'this:mt_getPostCategories',
122                   'mt.setPostCategories' => 'this:mt_setPostCategories',
123                   'mt.supportedMethods' => 'this:mt_supportedMethods',
124                   'mt.supportedTextFilters' => 'this:mt_supportedTextFilters',
125                   'mt.getTrackbackPings' => 'this:mt_getTrackbackPings',
126                   'mt.publishPost' => 'this:mt_publishPost',
127
128                   // PingBack
129                   'pingback.ping' => 'this:pingback_ping',
130                   'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks',
131
132                   'demo.sayHello' => 'this:sayHello',
133                   'demo.addTwoNumbers' => 'this:addTwoNumbers'
134                 );
135                 $this->methods = apply_filters('xmlrpc_methods', $this->methods);
136                 $this->IXR_Server($this->methods);
137         }
138
139         function sayHello($args) {
140                 return 'Hello!';
141         }
142
143         function addTwoNumbers($args) {
144                 $number1 = $args[0];
145                 $number2 = $args[1];
146                 return $number1 + $number2;
147         }
148
149         function login_pass_ok($user_login, $user_pass) {
150           if (!user_pass_ok($user_login, $user_pass)) {
151             $this->error = new IXR_Error(403, 'Bad login/pass combination.');
152             return false;
153           }
154           return true;
155         }
156
157         function escape(&$array) {
158                 global $wpdb;
159
160                 foreach ($array as $k => $v) {
161                         if (is_array($v)) {
162                                 $this->escape($array[$k]);
163                         } else if (is_object($v)) {
164                                 //skip
165                         } else {
166                                 $array[$k] = $wpdb->escape($v);
167                         }
168                 }
169         }
170
171         /* Blogger API functions
172          * specs on http://plant.blogger.com/api and http://groups.yahoo.com/group/bloggerDev/
173          */
174
175
176         /* blogger.getUsersBlogs will make more sense once we support multiple blogs */
177         function blogger_getUsersBlogs($args) {
178
179                 $this->escape($args);
180
181           $user_login = $args[1];
182           $user_pass  = $args[2];
183
184           if (!$this->login_pass_ok($user_login, $user_pass)) {
185             return $this->error;
186           }
187
188           set_current_user(0, $user_login);
189           $is_admin = current_user_can('level_8');
190
191           $struct = array(
192             'isAdmin'  => $is_admin,
193             'url'      => get_settings('home') . '/',
194             'blogid'   => '1',
195             'blogName' => get_settings('blogname')
196           );
197
198           return array($struct);
199         }
200
201
202         /* blogger.getUsersInfo gives your client some info about you, so you don't have to */
203         function blogger_getUserInfo($args) {
204
205                 $this->escape($args);
206
207           $user_login = $args[1];
208           $user_pass  = $args[2];
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
216           $struct = array(
217             'nickname'  => $user_data->nickname,
218             'userid'    => $user_data->ID,
219             'url'       => $user_data->user_url,
220             'email'     => $user_data->user_email,
221             'lastname'  => $user_data->last_name,
222             'firstname' => $user_data->first_name
223           );
224
225           return $struct;
226         }
227
228
229         /* blogger.getPost ...gets a post */
230         function blogger_getPost($args) {
231
232                 $this->escape($args);
233
234           $post_ID    = $args[1];
235           $user_login = $args[2];
236           $user_pass  = $args[3];
237
238           if (!$this->login_pass_ok($user_login, $user_pass)) {
239             return $this->error;
240           }
241
242           $user_data = get_userdatabylogin($user_login);
243           $post_data = wp_get_single_post($post_ID, ARRAY_A);
244
245           $categories = implode(',', wp_get_post_cats(1, $post_ID));
246
247           $content  = '<title>'.stripslashes($post_data['post_title']).'</title>';
248           $content .= '<category>'.$categories.'</category>';
249           $content .= stripslashes($post_data['post_content']);
250
251           $struct = array(
252             'userid'    => $post_data['post_author'],
253             'dateCreated' => new IXR_Date(mysql2date('Ymd\TH:i:s', $post_data['post_date'])),
254             'content'     => $content,
255             'postid'  => $post_data['ID']
256           );
257
258           return $struct;
259         }
260
261
262         /* blogger.getRecentPosts ...gets recent posts */
263         function blogger_getRecentPosts($args) {
264
265           global $wpdb;
266
267                 $this->escape($args);
268
269           $blog_ID    = $args[1]; /* though we don't use it yet */
270           $user_login = $args[2];
271           $user_pass  = $args[3];
272           $num_posts  = $args[4];
273
274           if (!$this->login_pass_ok($user_login, $user_pass)) {
275             return $this->error;
276           }
277
278           $posts_list = wp_get_recent_posts($num_posts);
279
280           if (!$posts_list) {
281             $this->error = new IXR_Error(500, 'Either there are no posts, or something went wrong.');
282             return $this->error;
283           }
284
285           foreach ($posts_list as $entry) {
286           
287             $post_date = mysql2date('Ymd\TH:i:s', $entry['post_date']);
288             $categories = implode(',', wp_get_post_cats(1, $entry['ID']));
289
290             $content  = '<title>'.stripslashes($entry['post_title']).'</title>';
291             $content .= '<category>'.$categories.'</category>';
292             $content .= stripslashes($entry['post_content']);
293
294             $struct[] = array(
295               'userid' => $entry['post_author'],
296               'dateCreated' => new IXR_Date($post_date),
297               'content' => $content,
298               'postid' => $entry['ID'],
299             );
300
301           }
302
303           $recent_posts = array();
304           for ($j=0; $j<count($struct); $j++) {
305             array_push($recent_posts, $struct[$j]);
306           }
307
308           return $recent_posts;
309         }
310
311
312         /* blogger.getTemplate returns your blog_filename */
313         function blogger_getTemplate($args) {
314
315                 $this->escape($args);
316
317           $blog_ID    = $args[1];
318           $user_login = $args[2];
319           $user_pass  = $args[3];
320           $template   = $args[4]; /* could be 'main' or 'archiveIndex', but we don't use it */
321
322           if (!$this->login_pass_ok($user_login, $user_pass)) {
323             return $this->error;
324           }
325
326           set_current_user(0, $user_login);
327           if ( !current_user_can('edit_themes') ) {
328             return new IXR_Error(401, 'Sorry, this user can not edit the template.');
329           }
330
331           /* warning: here we make the assumption that the weblog's URI is on the same server */
332           $filename = get_settings('home') . '/';
333           $filename = preg_replace('#https?://.+?/#', $_SERVER['DOCUMENT_ROOT'].'/', $filename);
334
335           $f = fopen($filename, 'r');
336           $content = fread($f, filesize($filename));
337           fclose($f);
338
339           /* so it is actually editable with a windows/mac client */
340           // 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); 
341
342           return $content;
343         }
344
345
346         /* blogger.setTemplate updates the content of blog_filename */
347         function blogger_setTemplate($args) {
348
349                 $this->escape($args);
350
351           $blog_ID    = $args[1];
352           $user_login = $args[2];
353           $user_pass  = $args[3];
354           $content    = $args[4];
355           $template   = $args[5]; /* could be 'main' or 'archiveIndex', but we don't use it */
356
357           if (!$this->login_pass_ok($user_login, $user_pass)) {
358             return $this->error;
359           }
360
361           set_current_user(0, $user_login);
362           if ( !current_user_can('edit_themes') ) {
363             return new IXR_Error(401, 'Sorry, this user can not edit the template.');
364           }
365
366           /* warning: here we make the assumption that the weblog's URI is on the same server */
367           $filename = get_settings('home') . '/';
368           $filename = preg_replace('#https?://.+?/#', $_SERVER['DOCUMENT_ROOT'].'/', $filename);
369
370           if ($f = fopen($filename, 'w+')) {
371             fwrite($f, $content);
372             fclose($f);
373           } else {
374             return new IXR_Error(500, 'Either the file is not writable, or something wrong happened. The file has not been updated.');
375           }
376
377           return true;
378         }
379
380
381         /* blogger.newPost ...creates a new post */
382         function blogger_newPost($args) {
383
384           global $wpdb;
385
386                 $this->escape($args);
387
388           $blog_ID    = $args[1]; /* though we don't use it yet */
389           $user_login = $args[2];
390           $user_pass  = $args[3];
391           $content    = $args[4];
392           $publish    = $args[5];
393
394           if (!$this->login_pass_ok($user_login, $user_pass)) {
395             return $this->error;
396           }
397           
398           $cap = ($publish) ? 'publish_posts' : 'edit_posts';
399           $user = set_current_user(0, $user_login);
400           if ( !current_user_can($cap) )
401             return new IXR_Error(401, 'Sorry, you can not post on this weblog or category.');
402
403           $post_status = ($publish) ? 'publish' : 'draft';
404
405           $post_author = $user->ID;
406
407           $post_title = xmlrpc_getposttitle($content);
408           $post_category = xmlrpc_getpostcategory($content);
409           $post_content = xmlrpc_removepostdata($content);
410
411           $post_date = current_time('mysql');
412           $post_date_gmt = current_time('mysql', 1);
413
414           $post_data = compact('blog_ID', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status');
415
416           $post_ID = wp_insert_post($post_data);
417
418           if (!$post_ID) {
419             return new IXR_Error(500, 'Sorry, your entry could not be posted. Something wrong happened.');
420           }
421
422           logIO('O', "Posted ! ID: $post_ID");
423
424           return $post_ID;
425         }
426
427
428         /* blogger.editPost ...edits a post */
429         function blogger_editPost($args) {
430
431           global $wpdb;
432
433                 $this->escape($args);
434
435           $post_ID     = $args[1];
436           $user_login  = $args[2];
437           $user_pass   = $args[3];
438           $content     = $args[4];
439           $publish     = $args[5];
440
441           if (!$this->login_pass_ok($user_login, $user_pass)) {
442             return $this->error;
443           }
444
445           $actual_post = wp_get_single_post($post_ID,ARRAY_A);
446
447           if (!$actual_post) {
448                 return new IXR_Error(404, 'Sorry, no such post.');
449           }
450
451                 $this->escape($actual_post);
452
453           set_current_user(0, $user_login);
454           if ( !current_user_can('edit_post', $post_ID) )
455             return new IXR_Error(401, 'Sorry, you do not have the right to edit this post.');
456
457           extract($actual_post);
458
459           $post_title = xmlrpc_getposttitle($content);
460           $post_category = xmlrpc_getpostcategory($content);
461           $post_content = xmlrpc_removepostdata($content);
462
463           $postdata = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt');
464
465           $result = wp_update_post($postdata);
466
467           if (!$result) {
468                 return new IXR_Error(500, 'For some strange yet very annoying reason, this post could not be edited.');
469           }
470
471           return true;
472         }
473
474
475         /* blogger.deletePost ...deletes a post */
476         function blogger_deletePost($args) {
477
478           global $wpdb;
479
480                 $this->escape($args);
481
482           $post_ID     = $args[1];
483           $user_login  = $args[2];
484           $user_pass   = $args[3];
485           $publish     = $args[4];
486
487           if (!$this->login_pass_ok($user_login, $user_pass)) {
488             return $this->error;
489           }
490
491           $actual_post = wp_get_single_post($post_ID,ARRAY_A);
492
493           if (!$actual_post) {
494                 return new IXR_Error(404, 'Sorry, no such post.');
495           }
496
497           set_current_user(0, $user_login);
498           if ( !current_user_can('edit_post', $post_ID) )
499             return new IXR_Error(401, 'Sorry, you do not have the right to delete this post.');
500
501           $result = wp_delete_post($post_ID);
502
503           if (!$result) {
504                 return new IXR_Error(500, 'For some strange yet very annoying reason, this post could not be deleted.');
505           }
506
507           return true;
508         }
509
510
511
512         /* MetaWeblog API functions
513          * specs on wherever Dave Winer wants them to be
514          */
515
516         /* metaweblog.newPost creates a post */
517         function mw_newPost($args) {
518
519           global $wpdb, $post_default_category;
520
521                 $this->escape($args);
522
523           $blog_ID     = $args[0]; // we will support this in the near future
524           $user_login  = $args[1];
525           $user_pass   = $args[2];
526           $content_struct = $args[3];
527           $publish     = $args[4];
528
529           if (!$this->login_pass_ok($user_login, $user_pass)) {
530             return $this->error;
531           }
532
533           $user = set_current_user(0, $user_login);
534           if ( !current_user_can('publish_posts') )
535             return new IXR_Error(401, 'Sorry, you can not post on this weblog or category.');
536
537           $post_author = $user->ID;
538
539           $post_title = $content_struct['title'];
540           $post_content = apply_filters( 'content_save_pre', $content_struct['description'] );
541           $post_status = $publish ? 'publish' : 'draft';
542
543           $post_excerpt = $content_struct['mt_excerpt'];
544           $post_more = $content_struct['mt_text_more'];
545
546           $comment_status = (empty($content_struct['mt_allow_comments'])) ?
547             get_settings('default_comment_status')
548             : $content_struct['mt_allow_comments'];
549
550           $ping_status = (empty($content_struct['mt_allow_pings'])) ?
551             get_settings('default_ping_status')
552             : $content_struct['mt_allow_pings'];
553
554           if ($post_more) {
555             $post_content = $post_content . "\n<!--more-->\n" . $post_more;
556           }
557
558                 $to_ping = $content_struct['mt_tb_ping_urls'];
559
560           // Do some timestamp voodoo
561           $dateCreatedd = $content_struct['dateCreated'];
562           if (!empty($dateCreatedd)) {
563             $dateCreated = $dateCreatedd->getIso();
564             $post_date     = get_date_from_gmt(iso8601_to_datetime($dateCreated));
565             $post_date_gmt = iso8601_to_datetime($dateCreated, GMT);
566           } else {
567             $post_date     = current_time('mysql');
568             $post_date_gmt = current_time('mysql', 1);
569           }
570
571           $catnames = $content_struct['categories'];
572           logIO('O', 'Post cats: ' . printr($catnames,true));
573           $post_category = array();
574
575           if (is_array($catnames)) {
576             foreach ($catnames as $cat) {
577               $post_category[] = get_cat_ID($cat);
578             }
579           }
580                 
581           // We've got all the data -- post it:
582           $postdata = compact('post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'comment_status', 'ping_status', 'to_ping');
583
584           $post_ID = wp_insert_post($postdata);
585
586           if (!$post_ID) {
587             return new IXR_Error(500, 'Sorry, your entry could not be posted. Something wrong happened.');
588           }
589
590           logIO('O', "Posted ! ID: $post_ID");
591
592           return strval($post_ID);
593         }
594
595
596         /* metaweblog.editPost ...edits a post */
597         function mw_editPost($args) {
598
599           global $wpdb, $post_default_category;
600
601                 $this->escape($args);
602
603           $post_ID     = $args[0];
604           $user_login  = $args[1];
605           $user_pass   = $args[2];
606           $content_struct = $args[3];
607           $publish     = $args[4];
608
609           if (!$this->login_pass_ok($user_login, $user_pass)) {
610             return $this->error;
611           }
612
613           set_current_user(0, $user_login);
614           if ( !current_user_can('edit_post', $post_ID) )
615             return new IXR_Error(401, 'Sorry, you can not edit this post.');
616
617           $postdata = wp_get_single_post($post_ID, ARRAY_A);
618           extract($postdata);
619                 $this->escape($postdata);
620
621           $post_title = $content_struct['title'];
622           $post_content = apply_filters( 'content_save_pre', $content_struct['description'] );
623           $catnames = $content_struct['categories'];
624
625           $post_category = array();
626                 
627           if (is_array($catnames)) {
628             foreach ($catnames as $cat) {
629               $post_category[] = get_cat_ID($cat);
630             }
631           }
632
633           $post_excerpt = $content_struct['mt_excerpt'];
634           $post_more = $content_struct['mt_text_more'];
635           $post_status = $publish ? 'publish' : 'draft';
636
637           if ($post_more) {
638             $post_content = $post_content . "\n<!--more-->\n" . $post_more;
639           }
640
641                 $to_ping = $content_struct['mt_tb_ping_urls'];
642
643           $comment_status = (empty($content_struct['mt_allow_comments'])) ?
644             get_settings('default_comment_status')
645             : $content_struct['mt_allow_comments'];
646
647           $ping_status = (empty($content_struct['mt_allow_pings'])) ?
648             get_settings('default_ping_status')
649             : $content_struct['mt_allow_pings'];
650
651           // Do some timestamp voodoo
652           $dateCreatedd = $content_struct['dateCreated'];
653           if (!empty($dateCreatedd)) {
654             $dateCreated = $dateCreatedd->getIso();
655             $post_date     = get_date_from_gmt(iso8601_to_datetime($dateCreated));
656             $post_date_gmt = iso8601_to_datetime($dateCreated, GMT);
657           } else {
658             $post_date     = $postdata['post_date'];
659             $post_date_gmt = $postdata['post_date_gmt'];
660           }
661
662           // We've got all the data -- post it:
663           $newpost = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'comment_status', 'ping_status', 'post_date', 'post_date_gmt', 'to_ping');
664
665           $result = wp_update_post($newpost);
666           if (!$result) {
667             return new IXR_Error(500, 'Sorry, your entry could not be edited. Something wrong happened.');
668           }
669
670           logIO('O',"(MW) Edited ! ID: $post_ID");
671
672           return true;
673         }
674
675
676         /* metaweblog.getPost ...returns a post */
677         function mw_getPost($args) {
678
679           global $wpdb;
680
681                 $this->escape($args);
682
683           $post_ID     = $args[0];
684           $user_login  = $args[1];
685           $user_pass   = $args[2];
686
687           if (!$this->login_pass_ok($user_login, $user_pass)) {
688             return $this->error;
689           }
690
691           $postdata = wp_get_single_post($post_ID, ARRAY_A);
692
693           if ($postdata['post_date'] != '') {
694
695             $post_date = mysql2date('Ymd\TH:i:s', $postdata['post_date']);
696
697             $categories = array();
698             $catids = wp_get_post_cats('', $post_ID);
699             foreach($catids as $catid) {
700               $categories[] = get_cat_name($catid);
701             }
702
703             $post = get_extended($postdata['post_content']);
704             $link = post_permalink($postdata['ID']);
705
706             $allow_comments = ('open' == $postdata['comment_status']) ? 1 : 0;
707             $allow_pings = ('open' == $postdata['ping_status']) ? 1 : 0;
708
709             $resp = array(
710               'dateCreated' => new IXR_Date($post_date),
711               'userid' => $postdata['post_author'],
712               'postid' => $postdata['ID'],
713               'description' => $post['main'],
714               'title' => $postdata['post_title'],
715               'link' => $link,
716               'permaLink' => $link,
717 // commented out because no other tool seems to use this
718 //            'content' => $entry['post_content'],
719               'categories' => $categories,
720               'mt_excerpt' => $postdata['post_excerpt'],
721               'mt_text_more' => $post['extended'],
722               'mt_allow_comments' => $allow_comments,
723               'mt_allow_pings' => $allow_pings
724             );
725
726             return $resp;
727           } else {
728                 return new IXR_Error(404, 'Sorry, no such post.');
729           }
730         }
731
732
733         /* metaweblog.getRecentPosts ...returns recent posts */
734         function mw_getRecentPosts($args) {
735
736                 $this->escape($args);
737
738           $blog_ID     = $args[0];
739           $user_login  = $args[1];
740           $user_pass   = $args[2];
741           $num_posts   = $args[3];
742
743           if (!$this->login_pass_ok($user_login, $user_pass)) {
744             return $this->error;
745           }
746
747           $posts_list = wp_get_recent_posts($num_posts);
748
749           if (!$posts_list) {
750             $this->error = new IXR_Error(500, 'Either there are no posts, or something went wrong.');
751             return $this->error;
752           }
753
754           foreach ($posts_list as $entry) {
755           
756             $post_date = mysql2date('Ymd\TH:i:s', $entry['post_date']);
757             $categories = array();
758             $catids = wp_get_post_cats('', $entry['ID']);
759             foreach($catids as $catid) {
760               $categories[] = get_cat_name($catid);
761             }
762
763             $post = get_extended($entry['post_content']);
764             $link = post_permalink($entry['ID']);
765
766             $allow_comments = ('open' == $entry['comment_status']) ? 1 : 0;
767             $allow_pings = ('open' == $entry['ping_status']) ? 1 : 0;
768
769             $struct[] = array(
770               'dateCreated' => new IXR_Date($post_date),
771               'userid' => $entry['post_author'],
772               'postid' => $entry['ID'],
773               'description' => $post['main'],
774               'title' => $entry['post_title'],
775               'link' => $link,
776               'permaLink' => $link,
777 // commented out because no other tool seems to use this
778 //            'content' => $entry['post_content'],
779               'categories' => $categories,
780               'mt_excerpt' => $entry['post_excerpt'],
781               'mt_text_more' => $post['extended'],
782               'mt_allow_comments' => $allow_comments,
783               'mt_allow_pings' => $allow_pings
784             );
785
786           }
787
788           $recent_posts = array();
789           for ($j=0; $j<count($struct); $j++) {
790             array_push($recent_posts, $struct[$j]);
791           }
792           
793           return $recent_posts;
794         }
795
796
797         /* metaweblog.getCategories ...returns the list of categories on a given weblog */
798         function mw_getCategories($args) {
799
800           global $wpdb;
801
802                 $this->escape($args);
803
804           $blog_ID     = $args[0];
805           $user_login  = $args[1];
806           $user_pass   = $args[2];
807
808           if (!$this->login_pass_ok($user_login, $user_pass)) {
809             return $this->error;
810           }
811
812           $categories_struct = array();
813
814           // FIXME: can we avoid using direct SQL there?
815           if ($cats = $wpdb->get_results("SELECT cat_ID,cat_name FROM $wpdb->categories", ARRAY_A)) {
816             foreach ($cats as $cat) {
817               $struct['categoryId'] = $cat['cat_ID'];
818               $struct['description'] = $cat['cat_name'];
819               $struct['categoryName'] = $cat['cat_name'];
820               $struct['htmlUrl'] = wp_specialchars(get_category_link($cat['cat_ID']));
821               $struct['rssUrl'] = wp_specialchars(get_category_rss_link(false, $cat['cat_ID'], $cat['cat_name']));
822
823               $categories_struct[] = $struct;
824             }
825           }
826
827           return $categories_struct;
828         }
829
830
831         /* metaweblog.newMediaObject uploads a file, following your settings */
832         function mw_newMediaObject($args) {
833                 // adapted from a patch by Johann Richard
834                 // http://mycvs.org/archives/2004/06/30/file-upload-to-wordpress-in-ecto/
835
836                 global $wpdb;
837
838                 $blog_ID     = $wpdb->escape($args[0]);
839                 $user_login  = $wpdb->escape($args[1]);
840                 $user_pass   = $wpdb->escape($args[2]);
841                 $data        = $args[3];
842
843                 $name = $data['name'];
844                 $type = $data['type'];
845                 $bits = $data['bits'];
846
847                 logIO('O', '(MW) Received '.strlen($bits).' bytes');
848
849                 if ( !$this->login_pass_ok($user_login, $user_pass) )
850                         return $this->error;
851
852                 set_current_user(0, $user_login);
853                 if ( !current_user_can('upload_files') ) {
854                         logIO('O', '(MW) User does not have upload_files capability');
855                         $this->error = new IXR_Error(401, 'You are not allowed to upload files to this site.');
856                         return $this->error;
857                 }
858
859                 $upload = wp_upload_bits($name, $type, $bits);
860                 if ( ! empty($upload['error']) ) {
861                         logIO('O', '(MW) Could not write file '.$name);
862                         return new IXR_Error(500, 'Could not write file '.$name);
863                 }
864                 
865                 return array('url' => $upload['url']);
866         }
867
868
869         /* MovableType API functions
870          * specs on http://www.movabletype.org/docs/mtmanual_programmatic.html
871          */
872
873         /* mt.getRecentPostTitles ...returns recent posts' titles */
874         function mt_getRecentPostTitles($args) {
875
876                 $this->escape($args);
877
878           $blog_ID     = $args[0];
879           $user_login  = $args[1];
880           $user_pass   = $args[2];
881           $num_posts   = $args[3];
882
883           if (!$this->login_pass_ok($user_login, $user_pass)) {
884             return $this->error;
885           }
886
887           $posts_list = wp_get_recent_posts($num_posts);
888
889           if (!$posts_list) {
890             $this->error = new IXR_Error(500, 'Either there are no posts, or something went wrong.');
891             return $this->error;
892           }
893
894           foreach ($posts_list as $entry) {
895           
896             $post_date = mysql2date('Ymd\TH:i:s', $entry['post_date']);
897
898             $struct[] = array(
899               'dateCreated' => new IXR_Date($post_date),
900               'userid' => $entry['post_author'],
901               'postid' => $entry['ID'],
902               'title' => $entry['post_title'],
903             );
904
905           }
906
907           $recent_posts = array();
908           for ($j=0; $j<count($struct); $j++) {
909             array_push($recent_posts, $struct[$j]);
910           }
911           
912           return $recent_posts;
913         }
914
915
916         /* mt.getCategoryList ...returns the list of categories on a given weblog */
917         function mt_getCategoryList($args) {
918
919           global $wpdb;
920
921                 $this->escape($args);
922
923           $blog_ID     = $args[0];
924           $user_login  = $args[1];
925           $user_pass   = $args[2];
926
927           if (!$this->login_pass_ok($user_login, $user_pass)) {
928             return $this->error;
929           }
930
931           $categories_struct = array();
932
933           // FIXME: can we avoid using direct SQL there?
934           if ($cats = $wpdb->get_results("SELECT cat_ID, cat_name FROM $wpdb->categories", ARRAY_A)) {
935             foreach ($cats as $cat) {
936               $struct['categoryId'] = $cat['cat_ID'];
937               $struct['categoryName'] = $cat['cat_name'];
938
939               $categories_struct[] = $struct;
940             }
941           }
942
943           return $categories_struct;
944         }
945
946
947         /* mt.getPostCategories ...returns a post's categories */
948         function mt_getPostCategories($args) {
949
950                 $this->escape($args);
951
952           $post_ID     = $args[0];
953           $user_login  = $args[1];
954           $user_pass   = $args[2];
955
956           if (!$this->login_pass_ok($user_login, $user_pass)) {
957             return $this->error;
958           }
959
960           $categories = array();
961           $catids = wp_get_post_cats('', intval($post_ID));
962           // first listed category will be the primary category
963           $isPrimary = true;
964           foreach($catids as $catid) {
965             $categories[] = array(
966               'categoryName' => get_cat_name($catid),
967               'categoryId' => $catid,
968               'isPrimary' => $isPrimary
969             );
970             $isPrimary = false;
971           }
972  
973           return $categories;
974         }
975
976
977         /* mt.setPostCategories ...sets a post's categories */
978         function mt_setPostCategories($args) {
979
980                 $this->escape($args);
981
982           $post_ID     = $args[0];
983           $user_login  = $args[1];
984           $user_pass   = $args[2];
985           $categories  = $args[3];
986
987           if (!$this->login_pass_ok($user_login, $user_pass)) {
988             return $this->error;
989           }
990
991           set_current_user(0, $user_login);
992           if ( !current_user_can('edit_post', $post_ID) )
993             return new IXR_Error(401, 'Sorry, you can not edit this post.');
994
995           foreach($categories as $cat) {
996             $catids[] = $cat['categoryId'];
997           }
998         
999           wp_set_post_cats('', $post_ID, $catids);
1000
1001           return true;
1002         }
1003
1004
1005         /* mt.supportedMethods ...returns an array of methods supported by this server */
1006         function mt_supportedMethods($args) {
1007
1008           $supported_methods = array();
1009           foreach($this->methods as $key=>$value) {
1010             $supported_methods[] = $key;
1011           }
1012
1013           return $supported_methods;
1014         }
1015
1016
1017         /* mt.supportedTextFilters ...returns an empty array because we don't
1018            support per-post text filters yet */
1019         function mt_supportedTextFilters($args) {
1020           return array();
1021         }
1022
1023
1024         /* mt.getTrackbackPings ...returns trackbacks sent to a given post */
1025         function mt_getTrackbackPings($args) {
1026
1027           global $wpdb;
1028
1029           $post_ID = intval($args);
1030
1031           $actual_post = wp_get_single_post($post_ID, ARRAY_A);
1032
1033           if (!$actual_post) {
1034                 return new IXR_Error(404, 'Sorry, no such post.');
1035           }
1036
1037           $comments = $wpdb->get_results("SELECT comment_author_url, comment_content, comment_author_IP, comment_type FROM $wpdb->comments WHERE comment_post_ID = $post_ID");
1038
1039           if (!$comments) {
1040                 return array();
1041           }
1042
1043           $trackback_pings = array();
1044           foreach($comments as $comment) {
1045             if ( 'trackback' == $comment->comment_type ) {
1046               $content = $comment->comment_content;
1047               $title = substr($content, 8, (strpos($content, '</strong>') - 8));
1048               $trackback_pings[] = array(
1049                 'pingTitle' => $title,
1050                 'pingURL'   => $comment->comment_author_url,
1051                 'pingIP'    => $comment->comment_author_IP
1052               );
1053                 }
1054           }
1055
1056           return $trackback_pings;
1057         }
1058
1059
1060         /* mt.publishPost ...sets a post's publish status to 'publish' */
1061         function mt_publishPost($args) {
1062
1063                 $this->escape($args);
1064
1065           $post_ID     = $args[0];
1066           $user_login  = $args[1];
1067           $user_pass   = $args[2];
1068
1069           if (!$this->login_pass_ok($user_login, $user_pass)) {
1070             return $this->error;
1071           }
1072
1073           set_current_user(0, $user_login);
1074           if ( !current_user_can('edit_post', $post_ID) )
1075             return new IXR_Error(401, 'Sorry, you can not edit this post.');
1076
1077           $postdata = wp_get_single_post($post_ID,ARRAY_A);
1078
1079           $postdata['post_status'] = 'publish';
1080
1081           // retain old cats
1082           $cats = wp_get_post_cats('',$post_ID);
1083           $postdata['post_category'] = $cats;
1084                 $this->escape($postdata);
1085
1086           $result = wp_update_post($postdata);
1087
1088           return $result;
1089         }
1090
1091
1092
1093         /* PingBack functions
1094          * specs on www.hixie.ch/specs/pingback/pingback
1095          */
1096
1097         /* pingback.ping gets a pingback and registers it */
1098         function pingback_ping($args) {
1099                 global $wpdb, $wp_version; 
1100
1101                 $this->escape($args);
1102
1103                 $pagelinkedfrom = $args[0];
1104                 $pagelinkedto   = $args[1];
1105
1106                 $title = '';
1107
1108                 $pagelinkedfrom = str_replace('&amp;', '&', $pagelinkedfrom);
1109                 $pagelinkedto   = preg_replace('#&([^amp\;])#is', '&amp;$1', $pagelinkedto);
1110
1111                 $error_code = -1;
1112
1113                 // Check if the page linked to is in our site
1114                 $pos1 = strpos($pagelinkedto, str_replace(array('http://www.','http://','https://www.','https://'), '', get_settings('home')));
1115                 if( !$pos1 )
1116                         return new IXR_Error(0, 'Is there no link to us?');
1117
1118                 // let's find which post is linked to
1119                 // FIXME: does url_to_postid() cover all these cases already?
1120                 //        if so, then let's use it and drop the old code.
1121                 $urltest = parse_url($pagelinkedto);
1122                 if ($post_ID = url_to_postid($pagelinkedto)) {
1123                         $way = 'url_to_postid()';
1124                 } elseif (preg_match('#p/[0-9]{1,}#', $urltest['path'], $match)) {
1125                         // the path defines the post_ID (archives/p/XXXX)
1126                         $blah = explode('/', $match[0]);
1127                         $post_ID = $blah[1];
1128                         $way = 'from the path';
1129                 } elseif (preg_match('#p=[0-9]{1,}#', $urltest['query'], $match)) {
1130                         // the querystring defines the post_ID (?p=XXXX)
1131                         $blah = explode('=', $match[0]);
1132                         $post_ID = $blah[1];
1133                         $way = 'from the querystring';
1134                 } elseif (isset($urltest['fragment'])) {
1135                         // an #anchor is there, it's either...
1136                         if (intval($urltest['fragment'])) {
1137                                 // ...an integer #XXXX (simpliest case)
1138                                 $post_ID = $urltest['fragment'];
1139                                 $way = 'from the fragment (numeric)';
1140                         } elseif (preg_match('/post-[0-9]+/',$urltest['fragment'])) {
1141                                 // ...a post id in the form 'post-###'
1142                                 $post_ID = preg_replace('/[^0-9]+/', '', $urltest['fragment']);
1143                                 $way = 'from the fragment (post-###)';
1144                         } elseif (is_string($urltest['fragment'])) {
1145                                 // ...or a string #title, a little more complicated
1146                                 $title = preg_replace('/[^a-z0-9]/i', '.', $urltest['fragment']);
1147                                 $sql = "SELECT ID FROM $wpdb->posts WHERE post_title RLIKE '$title'";
1148                                 if (! ($post_ID = $wpdb->get_var($sql)) ) {
1149                                         // returning unknown error '0' is better than die()ing
1150                                         return new IXR_Error(0, '');
1151                                 }
1152                                 $way = 'from the fragment (title)';
1153                         }
1154                 } else {
1155                         // TODO: Attempt to extract a post ID from the given URL
1156                         return new IXR_Error(33, 'The specified target URI cannot be used as a target. It either doesn\'t exist, or it is not a pingback-enabled resource.');
1157                 }
1158                 $post_ID = (int) $post_ID;
1159
1160
1161                 logIO("O","(PB) URI='$pagelinkedto' ID='$post_ID' Found='$way'");
1162
1163                 $post = get_post($post_ID);
1164
1165                 if ( !$post ) // Post_ID not found
1166                         return new IXR_Error(33, 'The specified target URI cannot be used as a target. It either doesn\'t exist, or it is not a pingback-enabled resource.');
1167
1168                 if ( $post_ID == url_to_postid($pagelinkedfrom) )
1169                         return new IXR_Error(0, 'The source URI and the target URI cannot both point to the same resource.');
1170
1171                 // Check if pings are on
1172                 if ( 'closed' == $post->ping_status )
1173                         return new IXR_Error(33, 'The specified target URI cannot be used as a target. It either doesn\'t exist, or it is not a pingback-enabled resource.');
1174
1175                 // Let's check that the remote site didn't already pingback this entry
1176                 $result = $wpdb->get_results("SELECT * FROM $wpdb->comments WHERE comment_post_ID = '$post_ID' AND comment_author_url = '$pagelinkedfrom'");
1177
1178                 if ( $wpdb->num_rows ) // We already have a Pingback from this URL
1179                         return new IXR_Error(48, 'The pingback has already been registered.');
1180
1181                 // very stupid, but gives time to the 'from' server to publish !
1182                 sleep(1);
1183
1184                 // Let's check the remote site
1185                 $linea = wp_remote_fopen( $pagelinkedfrom );
1186                 if ( !$linea )
1187                         return new IXR_Error(16, 'The source URI does not exist.');
1188
1189                 // Work around bug in strip_tags():
1190                 $linea = str_replace('<!DOC', '<DOC', $linea);
1191                 $linea = preg_replace( '/[\s\r\n\t]+/', ' ', $linea ); // normalize spaces
1192                 $linea = preg_replace( "/ <(h1|h2|h3|h4|h5|h6|p|th|td|li|dt|dd|pre|caption|input|textarea|button|body)[^>]*>/", "\n\n", $linea );
1193
1194                 preg_match('|<title>([^<]*?)</title>|is', $linea, $matchtitle);
1195                 $title = $matchtitle[1];
1196                 if ( empty( $title ) )
1197                         return new IXR_Error(32, 'We cannot find a title on that page.');
1198
1199                 $linea = strip_tags( $linea, '<a>' ); // just keep the tag we need
1200
1201                 $p = explode( "\n\n", $linea );
1202                 
1203                 $sem_regexp_pb = "/(\\/|\\\|\*|\?|\+|\.|\^|\\$|\(|\)|\[|\]|\||\{|\})/";
1204                 $sem_regexp_fix = "\\\\$1";
1205                 $link = preg_replace( $sem_regexp_pb, $sem_regexp_fix, $pagelinkedfrom );
1206                 
1207                 $finished = false;
1208                 foreach ( $p as $para ) {
1209                         if ( $finished )
1210                                 continue;
1211                         if ( strstr( $para, $pagelinkedto ) ) {
1212                                 $context = preg_replace( "/.*<a[^>]+".$link."[^>]*>([^>]+)<\/a>.*/", "$1", $para );
1213                                 $excerpt = strip_tags( $para );
1214                                 $excerpt = trim( $excerpt );
1215                                 $use     = preg_quote( $context );
1216                                 $excerpt = preg_replace("|.*?\s(.{0,100}$use.{0,100})\s|s", "$1", $excerpt);
1217                                 $finished = true;
1218                         }
1219                 }
1220
1221                 if ( empty($context) ) // URL pattern not found
1222                         return new IXR_Error(17, 'The source URI does not contain a link to the target URI, and so cannot be used as a source.');
1223
1224                 $pagelinkedfrom = preg_replace('#&([^amp\;])#is', '&amp;$1', $pagelinkedfrom);
1225
1226                 $context = '[...] ' . wp_specialchars( $excerpt ) . ' [...]';
1227                 $original_pagelinkedfrom = $pagelinkedfrom;
1228                 $pagelinkedfrom = $wpdb->escape( $pagelinkedfrom );
1229                 $original_title = $title;
1230
1231                 $comment_post_ID = $post_ID;
1232                 $comment_author = $title;
1233                 $comment_author_url = $pagelinkedfrom;
1234                 $comment_content = $context;
1235                 $comment_type = 'pingback';
1236
1237                 $commentdata = compact('comment_post_ID', 'comment_author', 'comment_author_url', 'comment_content', 'comment_type');
1238
1239                 wp_new_comment($commentdata);
1240                 do_action('pingback_post', $wpdb->insert_id);
1241                 
1242                 return "Pingback from $pagelinkedfrom to $pagelinkedto registered. Keep the web talking! :-)";
1243         }
1244
1245
1246         /* pingback.extensions.getPingbacks returns an array of URLs
1247            that pingbacked the given URL
1248            specs on http://www.aquarionics.com/misc/archives/blogite/0198.html */
1249         function pingback_extensions_getPingbacks($args) {
1250
1251                 global $wpdb;
1252
1253                 $this->escape($args);
1254
1255                 $url = $args;
1256
1257                 $post_ID = url_to_postid($url);
1258                 if (!$post_ID) {
1259                         // We aren't sure that the resource is available and/or pingback enabled
1260                         return new IXR_Error(33, 'The specified target URI cannot be used as a target. It either doesn\'t exist, or it is not a pingback-enabled resource.');
1261                 }
1262
1263                 $actual_post = wp_get_single_post($post_ID, ARRAY_A);
1264
1265                 if (!$actual_post) {
1266                         // No such post = resource not found
1267                         return new IXR_Error(32, 'The specified target URI does not exist.');
1268                 }
1269
1270                 $comments = $wpdb->get_results("SELECT comment_author_url, comment_content, comment_author_IP, comment_type FROM $wpdb->comments WHERE comment_post_ID = $post_ID");
1271
1272                 if (!$comments) {
1273                         return array();
1274                 }
1275
1276                 $pingbacks = array();
1277                 foreach($comments as $comment) {
1278                         if ( 'pingback' == $comment->comment_type )
1279                                 $pingbacks[] = $comment->comment_author_url;
1280                 }
1281
1282                 return $pingbacks;
1283         }
1284 }
1285
1286
1287 $wp_xmlrpc_server = new wp_xmlrpc_server();
1288
1289 ?>