WordPress.org

Make WordPress Core

Ticket #3052: xmlrpc.php

File xmlrpc.php, 36.4 KB (added by webrocker, 9 years ago)

pingback_ping function patched on line 1192

Line 
1<?php
2
3define('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
9if ( isset($HTTP_RAW_POST_DATA) )
10        $HTTP_RAW_POST_DATA = trim($HTTP_RAW_POST_DATA);
11
12include('./wp-config.php');
13
14if ( isset( $_GET['rsd'] ) ) { // http://archipelago.phrasewise.com/rsd
15header('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
32exit;
33}
34
35include_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 = 1;
43
44function 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
56function starify($string) {
57        $i = strlen($string);
58        return str_repeat('*', $i);
59}
60
61logIO("I", $HTTP_RAW_POST_DATA);
62
63
64function 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
88class 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|div)[^>]*>/", "\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 = (int) $post_ID;
1232                $comment_author = $title;
1233                $this->escape($comment_author);
1234                $comment_author_url = $pagelinkedfrom;
1235                $comment_content = $context;
1236                $this->escape($comment_content);
1237                $comment_type = 'pingback';
1238
1239                $commentdata = compact('comment_post_ID', 'comment_author', 'comment_author_url', 'comment_content', 'comment_type');
1240
1241                wp_new_comment($commentdata);
1242                do_action('pingback_post', $wpdb->insert_id);
1243               
1244                return "Pingback from $pagelinkedfrom to $pagelinkedto registered. Keep the web talking! :-)";
1245        }
1246
1247
1248        /* pingback.extensions.getPingbacks returns an array of URLs
1249           that pingbacked the given URL
1250           specs on http://www.aquarionics.com/misc/archives/blogite/0198.html */
1251        function pingback_extensions_getPingbacks($args) {
1252
1253                global $wpdb;
1254
1255                $this->escape($args);
1256
1257                $url = $args;
1258
1259                $post_ID = url_to_postid($url);
1260                if (!$post_ID) {
1261                        // We aren't sure that the resource is available and/or pingback enabled
1262                        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.');
1263                }
1264
1265                $actual_post = wp_get_single_post($post_ID, ARRAY_A);
1266
1267                if (!$actual_post) {
1268                        // No such post = resource not found
1269                        return new IXR_Error(32, 'The specified target URI does not exist.');
1270                }
1271
1272                $comments = $wpdb->get_results("SELECT comment_author_url, comment_content, comment_author_IP, comment_type FROM $wpdb->comments WHERE comment_post_ID = $post_ID");
1273
1274                if (!$comments) {
1275                        return array();
1276                }
1277
1278                $pingbacks = array();
1279                foreach($comments as $comment) {
1280                        if ( 'pingback' == $comment->comment_type )
1281                                $pingbacks[] = $comment->comment_author_url;
1282                }
1283
1284                return $pingbacks;
1285        }
1286}
1287
1288
1289$wp_xmlrpc_server = new wp_xmlrpc_server();
1290
1291?>