Ticket #1008: xmlrpc.php

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