]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - xmlrpc.php
Wordpress 2.6.2
[autoinstalls/wordpress.git] / xmlrpc.php
1 <?php
2 /**
3  * XML-RPC protocol support for WordPress
4  *
5  * @license GPL v2 <./license.txt>
6  * @package WordPress
7  */
8
9 /**
10  * Whether this is a XMLRPC Request
11  *
12  * @var bool
13  */
14 define('XMLRPC_REQUEST', true);
15
16 // Some browser-embedded clients send cookies. We don't want them.
17 $_COOKIE = array();
18
19 // A bug in PHP < 5.2.2 makes $HTTP_RAW_POST_DATA not set by default,
20 // but we can do it ourself.
21 if ( !isset( $HTTP_RAW_POST_DATA ) ) {
22         $HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
23 }
24
25 // fix for mozBlog and other cases where '<?xml' isn't on the very first line
26 if ( isset($HTTP_RAW_POST_DATA) )
27         $HTTP_RAW_POST_DATA = trim($HTTP_RAW_POST_DATA);
28
29 /** Include the bootstrap for setting up WordPress environment */
30 include('./wp-load.php');
31
32 if ( isset( $_GET['rsd'] ) ) { // http://archipelago.phrasewise.com/rsd
33 header('Content-Type: text/xml; charset=' . get_option('blog_charset'), true);
34 ?>
35 <?php echo '<?xml version="1.0" encoding="'.get_option('blog_charset').'"?'.'>'; ?>
36 <rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd">
37   <service>
38     <engineName>WordPress</engineName>
39     <engineLink>http://wordpress.org/</engineLink>
40     <homePageLink><?php bloginfo_rss('url') ?></homePageLink>
41     <apis>
42       <api name="WordPress" blogID="1" preferred="true" apiLink="<?php echo site_url('xmlrpc.php') ?>" />
43       <api name="Movable Type" blogID="1" preferred="false" apiLink="<?php echo site_url('xmlrpc.php') ?>" />
44       <api name="MetaWeblog" blogID="1" preferred="false" apiLink="<?php echo site_url('xmlrpc.php') ?>" />
45       <api name="Blogger" blogID="1" preferred="false" apiLink="<?php echo site_url('xmlrpc.php') ?>" />
46       <api name="Atom" blogID="" preferred="false" apiLink="<?php echo apply_filters('atom_service_url', site_url('wp-app.php/service') ) ?>" />
47     </apis>
48   </service>
49 </rsd>
50 <?php
51 exit;
52 }
53
54 include_once(ABSPATH . 'wp-admin/includes/admin.php');
55 include_once(ABSPATH . WPINC . '/class-IXR.php');
56
57 // Turn off all warnings and errors.
58 // error_reporting(0);
59
60 /**
61  * Posts submitted via the xmlrpc interface get that title
62  * @name post_default_title
63  * @var string
64  */
65 $post_default_title = "";
66
67 /**
68  * Whether to enable XMLRPC Logging.
69  *
70  * @name xmlrpc_logging
71  * @var int|bool
72  */
73 $xmlrpc_logging = 0;
74
75 /**
76  * logIO() - Writes logging info to a file.
77  *
78  * @uses $xmlrpc_logging
79  * @package WordPress
80  * @subpackage Logging
81  *
82  * @param string $io Whether input or output
83  * @param string $msg Information describing logging reason.
84  * @return bool Always return true
85  */
86 function logIO($io,$msg) {
87         global $xmlrpc_logging;
88         if ($xmlrpc_logging) {
89                 $fp = fopen("../xmlrpc.log","a+");
90                 $date = gmdate("Y-m-d H:i:s ");
91                 $iot = ($io == "I") ? " Input: " : " Output: ";
92                 fwrite($fp, "\n\n".$date.$iot.$msg);
93                 fclose($fp);
94         }
95         return true;
96 }
97
98 if ( isset($HTTP_RAW_POST_DATA) )
99         logIO("I", $HTTP_RAW_POST_DATA);
100
101 /**
102  * @internal
103  * Left undocumented to work on later. If you want to finish, then please do so.
104  *
105  * @package WordPress
106  * @subpackage Publishing
107  */
108 class wp_xmlrpc_server extends IXR_Server {
109
110         function wp_xmlrpc_server() {
111                 $this->methods = array(
112                         // WordPress API
113                         'wp.getUsersBlogs'              => 'this:wp_getUsersBlogs',
114                         'wp.getPage'                    => 'this:wp_getPage',
115                         'wp.getPages'                   => 'this:wp_getPages',
116                         'wp.newPage'                    => 'this:wp_newPage',
117                         'wp.deletePage'                 => 'this:wp_deletePage',
118                         'wp.editPage'                   => 'this:wp_editPage',
119                         'wp.getPageList'                => 'this:wp_getPageList',
120                         'wp.getAuthors'                 => 'this:wp_getAuthors',
121                         'wp.getCategories'              => 'this:mw_getCategories',             // Alias
122                         'wp.newCategory'                => 'this:wp_newCategory',
123                         'wp.deleteCategory'             => 'this:wp_deleteCategory',
124                         'wp.suggestCategories'  => 'this:wp_suggestCategories',
125                         'wp.uploadFile'                 => 'this:mw_newMediaObject',    // Alias
126                         'wp.getCommentCount'    => 'this:wp_getCommentCount',
127                         'wp.getPostStatusList'  => 'this:wp_getPostStatusList',
128                         'wp.getPageStatusList'  => 'this:wp_getPageStatusList',
129                         'wp.getPageTemplates'   => 'this:wp_getPageTemplates',
130                         'wp.getOptions'                 => 'this:wp_getOptions',
131                         'wp.setOptions'                 => 'this:wp_setOptions',
132
133                         // Blogger API
134                         'blogger.getUsersBlogs' => 'this:blogger_getUsersBlogs',
135                         'blogger.getUserInfo' => 'this:blogger_getUserInfo',
136                         'blogger.getPost' => 'this:blogger_getPost',
137                         'blogger.getRecentPosts' => 'this:blogger_getRecentPosts',
138                         'blogger.getTemplate' => 'this:blogger_getTemplate',
139                         'blogger.setTemplate' => 'this:blogger_setTemplate',
140                         'blogger.newPost' => 'this:blogger_newPost',
141                         'blogger.editPost' => 'this:blogger_editPost',
142                         'blogger.deletePost' => 'this:blogger_deletePost',
143
144                         // MetaWeblog API (with MT extensions to structs)
145                         'metaWeblog.newPost' => 'this:mw_newPost',
146                         'metaWeblog.editPost' => 'this:mw_editPost',
147                         'metaWeblog.getPost' => 'this:mw_getPost',
148                         'metaWeblog.getRecentPosts' => 'this:mw_getRecentPosts',
149                         'metaWeblog.getCategories' => 'this:mw_getCategories',
150                         'metaWeblog.newMediaObject' => 'this:mw_newMediaObject',
151
152                         // MetaWeblog API aliases for Blogger API
153                         // see http://www.xmlrpc.com/stories/storyReader$2460
154                         'metaWeblog.deletePost' => 'this:blogger_deletePost',
155                         'metaWeblog.getTemplate' => 'this:blogger_getTemplate',
156                         'metaWeblog.setTemplate' => 'this:blogger_setTemplate',
157                         'metaWeblog.getUsersBlogs' => 'this:blogger_getUsersBlogs',
158
159                         // MovableType API
160                         'mt.getCategoryList' => 'this:mt_getCategoryList',
161                         'mt.getRecentPostTitles' => 'this:mt_getRecentPostTitles',
162                         'mt.getPostCategories' => 'this:mt_getPostCategories',
163                         'mt.setPostCategories' => 'this:mt_setPostCategories',
164                         'mt.supportedMethods' => 'this:mt_supportedMethods',
165                         'mt.supportedTextFilters' => 'this:mt_supportedTextFilters',
166                         'mt.getTrackbackPings' => 'this:mt_getTrackbackPings',
167                         'mt.publishPost' => 'this:mt_publishPost',
168
169                         // PingBack
170                         'pingback.ping' => 'this:pingback_ping',
171                         'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks',
172
173                         'demo.sayHello' => 'this:sayHello',
174                         'demo.addTwoNumbers' => 'this:addTwoNumbers'
175                 );
176
177                 $this->initialise_blog_option_info( );
178                 $this->methods = apply_filters('xmlrpc_methods', $this->methods);
179                 $this->IXR_Server($this->methods);
180         }
181
182         function sayHello($args) {
183                 return 'Hello!';
184         }
185
186         function addTwoNumbers($args) {
187                 $number1 = $args[0];
188                 $number2 = $args[1];
189                 return $number1 + $number2;
190         }
191
192         function login_pass_ok($user_login, $user_pass) {
193                 if ( !get_option( 'enable_xmlrpc' ) ) {
194                         $this->error = new IXR_Error( 405, sprintf( __( 'XML-RPC services are disabled on this blog.  An admin user can enable them at %s'),  admin_url('options-writing.php') ) );
195                         return false;
196                 }
197
198                 if (!user_pass_ok($user_login, $user_pass)) {
199                         $this->error = new IXR_Error(403, __('Bad login/pass combination.'));
200                         return false;
201                 }
202                 return true;
203         }
204
205         function escape(&$array) {
206                 global $wpdb;
207
208                 if(!is_array($array)) {
209                         return($wpdb->escape($array));
210                 }
211                 else {
212                         foreach ( (array) $array as $k => $v ) {
213                                 if (is_array($v)) {
214                                         $this->escape($array[$k]);
215                                 } else if (is_object($v)) {
216                                         //skip
217                                 } else {
218                                         $array[$k] = $wpdb->escape($v);
219                                 }
220                         }
221                 }
222         }
223
224         function get_custom_fields($post_id) {
225                 $post_id = (int) $post_id;
226
227                 $custom_fields = array();
228
229                 foreach ( (array) has_meta($post_id) as $meta ) {
230                         // Don't expose protected fields.
231                         if ( strpos($meta['meta_key'], '_wp_') === 0 ) {
232                                 continue;
233                         }
234
235                         $custom_fields[] = array(
236                                 "id"    => $meta['meta_id'],
237                                 "key"   => $meta['meta_key'],
238                                 "value" => $meta['meta_value']
239                         );
240                 }
241
242                 return $custom_fields;
243         }
244
245         function set_custom_fields($post_id, $fields) {
246                 $post_id = (int) $post_id;
247
248                 foreach ( (array) $fields as $meta ) {
249                         if ( isset($meta['id']) ) {
250                                 $meta['id'] = (int) $meta['id'];
251
252                                 if ( isset($meta['key']) ) {
253                                         update_meta($meta['id'], $meta['key'], $meta['value']);
254                                 }
255                                 else {
256                                         delete_meta($meta['id']);
257                                 }
258                         }
259                         else {
260                                 $_POST['metakeyinput'] = $meta['key'];
261                                 $_POST['metavalue'] = $meta['value'];
262                                 add_meta($post_id);
263                         }
264                 }
265         }
266
267         function initialise_blog_option_info( ) {
268                 global $wp_version;
269
270                 $this->blog_options = array(
271                         // Read only options
272                         'software_name'         => array(
273                                 'desc'                  => __( 'Software Name' ),
274                                 'readonly'              => true,
275                                 'value'                 => 'WordPress'
276                         ),
277                         'software_version'      => array(
278                                 'desc'                  => __( 'Software Version' ),
279                                 'readonly'              => true,
280                                 'value'                 => $wp_version
281                         ),
282                         'blog_url'                      => array(
283                                 'desc'                  => __( 'Blog URL' ),
284                                 'readonly'              => true,
285                                 'option'                => 'siteurl'
286                         ),
287
288                         // Updatable options
289                         'time_zone'                     => array(
290                                 'desc'                  => __( 'Time Zone' ),
291                                 'readonly'              => false,
292                                 'option'                => 'gmt_offset'
293                         ),
294                         'blog_title'            => array(
295                                 'desc'                  => __( 'Blog Title' ),
296                                 'readonly'              => false,
297                                 'option'                        => 'blogname'
298                         ),
299                         'blog_tagline'          => array(
300                                 'desc'                  => __( 'Blog Tagline' ),
301                                 'readonly'              => false,
302                                 'option'                => 'blogdescription'
303                         ),
304                         'date_format'           => array(
305                                 'desc'                  => __( 'Date Format' ),
306                                 'readonly'              => false,
307                                 'option'                => 'date_format'
308                         ),
309                         'time_format'           => array(
310                                 'desc'                  => __( 'Time Format' ),
311                                 'readonly'              => false,
312                                 'option'                => 'time_format'
313                         )
314                 );
315
316                 $this->blog_options = apply_filters( 'xmlrpc_blog_options', $this->blog_options );
317         }
318
319         /**
320          * WordPress XML-RPC API
321          * wp_getUsersBlogs
322          */
323         function wp_getUsersBlogs( $args ) {
324                 // If this isn't on WPMU then just use blogger_getUsersBlogs
325                 if( !function_exists( 'is_site_admin' ) ) {
326                         array_unshift( $args, 1 );
327                         return $this->blogger_getUsersBlogs( $args );
328                 }
329
330                 $this->escape( $args );
331
332                 $username = $args[0];
333                 $password = $args[1];
334
335                 if( !$this->login_pass_ok( $username, $password ) )
336                         return $this->error;
337
338                 do_action( 'xmlrpc_call', 'wp.getUsersBlogs' );
339
340                 $user = set_current_user( 0, $username );
341
342                 $blogs = (array) get_blogs_of_user( $user->ID );
343                 $struct = array( );
344
345                 foreach( $blogs as $blog ) {
346                         // Don't include blogs that aren't hosted at this site
347                         if( $blog->site_id != 1 )
348                                 continue;
349
350                         $blog_id = $blog->userblog_id;
351                         switch_to_blog($blog_id);
352                         $is_admin = current_user_can('level_8');
353
354                         $struct[] = array(
355                                 'isAdmin'               => $is_admin,
356                                 'url'                   => get_option( 'home' ) . '/',
357                                 'blogid'                => $blog_id,
358                                 'blogName'              => get_option( 'blogname' ),
359                                 'xmlrpc'                => get_option( 'home' ) . '/xmlrpc.php'
360                         );
361                 }
362
363                 return $struct;
364         }
365
366         /**
367          * WordPress XML-RPC API
368          * wp_getPage
369          */
370         function wp_getPage($args) {
371                 $this->escape($args);
372
373                 $blog_id        = (int) $args[0];
374                 $page_id        = (int) $args[1];
375                 $username       = $args[2];
376                 $password       = $args[3];
377
378                 if(!$this->login_pass_ok($username, $password)) {
379                         return($this->error);
380                 }
381
382                 set_current_user( 0, $username );
383                 if( !current_user_can( 'edit_page', $page_id ) )
384                         return new IXR_Error( 401, __( 'Sorry, you can not edit this page.' ) );
385
386                 do_action('xmlrpc_call', 'wp.getPage');
387
388                 // Lookup page info.
389                 $page = get_page($page_id);
390
391                 // If we found the page then format the data.
392                 if($page->ID && ($page->post_type == "page")) {
393                         // Get all of the page content and link.
394                         $full_page = get_extended($page->post_content);
395                         $link = post_permalink($page->ID);
396
397                         // Get info the page parent if there is one.
398                         $parent_title = "";
399                         if(!empty($page->post_parent)) {
400                                 $parent = get_page($page->post_parent);
401                                 $parent_title = $parent->post_title;
402                         }
403
404                         // Determine comment and ping settings.
405                         $allow_comments = ("open" == $page->comment_status) ? 1 : 0;
406                         $allow_pings = ("open" == $page->ping_status) ? 1 : 0;
407
408                         // Format page date.
409                         $page_date = mysql2date("Ymd\TH:i:s", $page->post_date);
410                         $page_date_gmt = mysql2date("Ymd\TH:i:s", $page->post_date_gmt);
411
412                         // Pull the categories info together.
413                         $categories = array();
414                         foreach(wp_get_post_categories($page->ID) as $cat_id) {
415                                 $categories[] = get_cat_name($cat_id);
416                         }
417
418                         // Get the author info.
419                         $author = get_userdata($page->post_author);
420
421                         $page_template = get_post_meta( $page->ID, '_wp_page_template', true );
422                         if( empty( $page_template ) )
423                                 $page_template = 'default';
424
425                         $page_struct = array(
426                                 "dateCreated"                   => new IXR_Date($page_date),
427                                 "userid"                                => $page->post_author,
428                                 "page_id"                               => $page->ID,
429                                 "page_status"                   => $page->post_status,
430                                 "description"                   => $full_page["main"],
431                                 "title"                                 => $page->post_title,
432                                 "link"                                  => $link,
433                                 "permaLink"                             => $link,
434                                 "categories"                    => $categories,
435                                 "excerpt"                               => $page->post_excerpt,
436                                 "text_more"                             => $full_page["extended"],
437                                 "mt_allow_comments"             => $allow_comments,
438                                 "mt_allow_pings"                => $allow_pings,
439                                 "wp_slug"                               => $page->post_name,
440                                 "wp_password"                   => $page->post_password,
441                                 "wp_author"                             => $author->display_name,
442                                 "wp_page_parent_id"             => $page->post_parent,
443                                 "wp_page_parent_title"  => $parent_title,
444                                 "wp_page_order"                 => $page->menu_order,
445                                 "wp_author_id"                  => $author->ID,
446                                 "wp_author_display_name"        => $author->display_name,
447                                 "date_created_gmt"              => new IXR_Date($page_date_gmt),
448                                 "custom_fields"                 => $this->get_custom_fields($page_id),
449                                 "wp_page_template"              => $page_template
450                         );
451
452                         return($page_struct);
453                 }
454                 // If the page doesn't exist indicate that.
455                 else {
456                         return(new IXR_Error(404, __("Sorry, no such page.")));
457                 }
458         }
459
460         /**
461          * WordPress XML-RPC API
462          * wp_getPages
463          */
464         function wp_getPages($args) {
465                 $this->escape($args);
466
467                 $blog_id        = (int) $args[0];
468                 $username       = $args[1];
469                 $password       = $args[2];
470
471                 if(!$this->login_pass_ok($username, $password)) {
472                         return($this->error);
473                 }
474
475                 set_current_user( 0, $username );
476                 if( !current_user_can( 'edit_pages' ) )
477                         return new IXR_Error( 401, __( 'Sorry, you can not edit pages.' ) );
478
479                 do_action('xmlrpc_call', 'wp.getPages');
480
481                 // Lookup info on pages.
482                 $pages = get_pages();
483                 $num_pages = count($pages);
484
485                 // If we have pages, put together their info.
486                 if($num_pages >= 1) {
487                         $pages_struct = array();
488
489                         for($i = 0; $i < $num_pages; $i++) {
490                                 $page = wp_xmlrpc_server::wp_getPage(array(
491                                         $blog_id, $pages[$i]->ID, $username, $password
492                                 ));
493                                 $pages_struct[] = $page;
494                         }
495
496                         return($pages_struct);
497                 }
498                 // If no pages were found return an error.
499                 else {
500                         return(array());
501                 }
502         }
503
504         /**
505          * WordPress XML-RPC API
506          * wp_newPage
507          */
508         function wp_newPage($args) {
509                 // Items not escaped here will be escaped in newPost.
510                 $username       = $this->escape($args[1]);
511                 $password       = $this->escape($args[2]);
512                 $page           = $args[3];
513                 $publish        = $args[4];
514
515                 if(!$this->login_pass_ok($username, $password)) {
516                         return($this->error);
517                 }
518
519                 do_action('xmlrpc_call', 'wp.newPage');
520
521                 // Set the user context and check if they are allowed
522                 // to add new pages.
523                 $user = set_current_user(0, $username);
524                 if(!current_user_can("publish_pages")) {
525                         return(new IXR_Error(401, __("Sorry, you can not add new pages.")));
526                 }
527
528                 // Mark this as content for a page.
529                 $args[3]["post_type"] = "page";
530
531                 // Let mw_newPost do all of the heavy lifting.
532                 return($this->mw_newPost($args));
533         }
534
535         /**
536          * WordPress XML-RPC API
537          * wp_deletePage
538          */
539         function wp_deletePage($args) {
540                 $this->escape($args);
541
542                 $blog_id        = (int) $args[0];
543                 $username       = $args[1];
544                 $password       = $args[2];
545                 $page_id        = (int) $args[3];
546
547                 if(!$this->login_pass_ok($username, $password)) {
548                         return($this->error);
549                 }
550
551                 do_action('xmlrpc_call', 'wp.deletePage');
552
553                 // Get the current page based on the page_id and
554                 // make sure it is a page and not a post.
555                 $actual_page = wp_get_single_post($page_id, ARRAY_A);
556                 if(
557                         !$actual_page
558                         || ($actual_page["post_type"] != "page")
559                 ) {
560                         return(new IXR_Error(404, __("Sorry, no such page.")));
561                 }
562
563                 // Set the user context and make sure they can delete pages.
564                 set_current_user(0, $username);
565                 if(!current_user_can("delete_page", $page_id)) {
566                         return(new IXR_Error(401, __("Sorry, you do not have the right to delete this page.")));
567                 }
568
569                 // Attempt to delete the page.
570                 $result = wp_delete_post($page_id);
571                 if(!$result) {
572                         return(new IXR_Error(500, __("Failed to delete the page.")));
573                 }
574
575                 return(true);
576         }
577
578         /**
579          * WordPress XML-RPC API
580          * wp_editPage
581          */
582         function wp_editPage($args) {
583                 // Items not escaped here will be escaped in editPost.
584                 $blog_id        = (int) $args[0];
585                 $page_id        = (int) $this->escape($args[1]);
586                 $username       = $this->escape($args[2]);
587                 $password       = $this->escape($args[3]);
588                 $content        = $args[4];
589                 $publish        = $args[5];
590
591                 if(!$this->login_pass_ok($username, $password)) {
592                         return($this->error);
593                 }
594
595                 do_action('xmlrpc_call', 'wp.editPage');
596
597                 // Get the page data and make sure it is a page.
598                 $actual_page = wp_get_single_post($page_id, ARRAY_A);
599                 if(
600                         !$actual_page
601                         || ($actual_page["post_type"] != "page")
602                 ) {
603                         return(new IXR_Error(404, __("Sorry, no such page.")));
604                 }
605
606                 // Set the user context and make sure they are allowed to edit pages.
607                 set_current_user(0, $username);
608                 if(!current_user_can("edit_page", $page_id)) {
609                         return(new IXR_Error(401, __("Sorry, you do not have the right to edit this page.")));
610                 }
611
612                 // Mark this as content for a page.
613                 $content["post_type"] = "page";
614
615                 // Arrange args in the way mw_editPost understands.
616                 $args = array(
617                         $page_id,
618                         $username,
619                         $password,
620                         $content,
621                         $publish
622                 );
623
624                 // Let mw_editPost do all of the heavy lifting.
625                 return($this->mw_editPost($args));
626         }
627
628         /**
629          * WordPress XML-RPC API
630          * wp_getPageList
631          */
632         function wp_getPageList($args) {
633                 global $wpdb;
634
635                 $this->escape($args);
636
637                 $blog_id                                = (int) $args[0];
638                 $username                               = $args[1];
639                 $password                               = $args[2];
640
641                 if(!$this->login_pass_ok($username, $password)) {
642                         return($this->error);
643                 }
644
645                 set_current_user( 0, $username );
646                 if( !current_user_can( 'edit_pages' ) )
647                         return new IXR_Error( 401, __( 'Sorry, you can not edit pages.' ) );
648
649                 do_action('xmlrpc_call', 'wp.getPageList');
650
651                 // Get list of pages ids and titles
652                 $page_list = $wpdb->get_results("
653                         SELECT ID page_id,
654                                 post_title page_title,
655                                 post_parent page_parent_id,
656                                 post_date_gmt,
657                                 post_date
658                         FROM {$wpdb->posts}
659                         WHERE post_type = 'page'
660                         ORDER BY ID
661                 ");
662
663                 // The date needs to be formated properly.
664                 $num_pages = count($page_list);
665                 for($i = 0; $i < $num_pages; $i++) {
666                         $post_date = mysql2date("Ymd\TH:i:s", $page_list[$i]->post_date);
667                         $post_date_gmt = mysql2date("Ymd\TH:i:s", $page_list[$i]->post_date_gmt);
668
669                         $page_list[$i]->dateCreated = new IXR_Date($post_date);
670                         $page_list[$i]->date_created_gmt = new IXR_Date($post_date_gmt);
671
672                         unset($page_list[$i]->post_date_gmt);
673                         unset($page_list[$i]->post_date);
674                 }
675
676                 return($page_list);
677         }
678
679         /**
680          * WordPress XML-RPC API
681          * wp_getAuthors
682          */
683         function wp_getAuthors($args) {
684
685                 $this->escape($args);
686
687                 $blog_id        = (int) $args[0];
688                 $username       = $args[1];
689                 $password       = $args[2];
690
691                 if(!$this->login_pass_ok($username, $password)) {
692                         return($this->error);
693                 }
694
695                 set_current_user(0, $username);
696                 if(!current_user_can("edit_posts")) {
697                         return(new IXR_Error(401, __("Sorry, you can not edit posts on this blog.")));
698         }
699
700                 do_action('xmlrpc_call', 'wp.getAuthors');
701
702                 $authors = array();
703                 foreach( (array) get_users_of_blog() as $row ) {
704                         $authors[] = array(
705                                 "user_id"       => $row->user_id,
706                                 "user_login"    => $row->user_login,
707                                 "display_name"  => $row->display_name
708                         );
709                 }
710
711                 return($authors);
712         }
713
714         /**
715          * WordPress XML-RPC API
716          * wp_newCategory
717          */
718         function wp_newCategory($args) {
719                 $this->escape($args);
720
721                 $blog_id                                = (int) $args[0];
722                 $username                               = $args[1];
723                 $password                               = $args[2];
724                 $category                               = $args[3];
725
726                 if(!$this->login_pass_ok($username, $password)) {
727                         return($this->error);
728                 }
729
730                 do_action('xmlrpc_call', 'wp.newCategory');
731
732                 // Set the user context and make sure they are
733                 // allowed to add a category.
734                 set_current_user(0, $username);
735                 if(!current_user_can("manage_categories")) {
736                         return(new IXR_Error(401, __("Sorry, you do not have the right to add a category.")));
737                 }
738
739                 // If no slug was provided make it empty so that
740                 // WordPress will generate one.
741                 if(empty($category["slug"])) {
742                         $category["slug"] = "";
743                 }
744
745                 // If no parent_id was provided make it empty
746                 // so that it will be a top level page (no parent).
747                 if ( !isset($category["parent_id"]) )
748                         $category["parent_id"] = "";
749
750                 // If no description was provided make it empty.
751                 if(empty($category["description"])) {
752                         $category["description"] = "";
753                 }
754
755                 $new_category = array(
756                         "cat_name"                              => $category["name"],
757                         "category_nicename"             => $category["slug"],
758                         "category_parent"               => $category["parent_id"],
759                         "category_description"  => $category["description"]
760                 );
761
762                 $cat_id = wp_insert_category($new_category);
763                 if(!$cat_id) {
764                         return(new IXR_Error(500, __("Sorry, the new category failed.")));
765                 }
766
767                 return($cat_id);
768         }
769
770         /**
771          * WordPress XML-RPC API
772          * wp_deleteCategory
773          */
774         function wp_deleteCategory($args) {
775                 $this->escape($args);
776
777                 $blog_id                = (int) $args[0];
778                 $username               = $args[1];
779                 $password               = $args[2];
780                 $category_id    = (int) $args[3];
781
782                 if( !$this->login_pass_ok( $username, $password ) ) {
783                         return $this->error;
784                 }
785
786                 do_action('xmlrpc_call', 'wp.deleteCategory');
787
788                 set_current_user(0, $username);
789                 if( !current_user_can("manage_categories") ) {
790                         return new IXR_Error( 401, __( "Sorry, you do not have the right to delete a category." ) );
791                 }
792
793                 return wp_delete_category( $category_id );
794         }
795
796
797         /**
798          * WordPress XML-RPC API
799          * wp_suggestCategories
800          */
801         function wp_suggestCategories($args) {
802                 $this->escape($args);
803
804                 $blog_id                                = (int) $args[0];
805                 $username                               = $args[1];
806                 $password                               = $args[2];
807                 $category                               = $args[3];
808                 $max_results                    = (int) $args[4];
809
810                 if(!$this->login_pass_ok($username, $password)) {
811                         return($this->error);
812                 }
813
814                 set_current_user(0, $username);
815                 if( !current_user_can( 'edit_posts' ) )
816                         return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts to this blog in order to view categories.' ) );
817
818                 do_action('xmlrpc_call', 'wp.suggestCategories');
819
820                 $category_suggestions = array();
821                 $args = array('get' => 'all', 'number' => $max_results, 'name__like' => $category);
822                 foreach ( (array) get_categories($args) as $cat ) {
823                         $category_suggestions[] = array(
824                                 "category_id"   => $cat->cat_ID,
825                                 "category_name" => $cat->cat_name
826                         );
827                 }
828
829                 return($category_suggestions);
830         }
831
832         function wp_getCommentCount( $args ) {
833                 $this->escape($args);
834
835                 $blog_id        = (int) $args[0];
836                 $username       = $args[1];
837                 $password       = $args[2];
838                 $post_id        = (int) $args[3];
839
840                 if( !$this->login_pass_ok( $username, $password ) ) {
841                         return $this->error;
842                 }
843
844                 set_current_user( 0, $username );
845                 if( !current_user_can( 'edit_posts' ) ) {
846                         return new IXR_Error( 403, __( 'You are not allowed access to details about comments.' ) );
847                 }
848
849                 do_action('xmlrpc_call', 'wp.getCommentCount');
850
851                 $count = wp_count_comments( $post_id );
852                 return array(
853                         "approved" => $count->approved,
854                         "awaiting_moderation" => $count->moderated,
855                         "spam" => $count->spam,
856                         "total_comments" => $count->total_comments
857                 );
858         }
859
860
861         function wp_getPostStatusList( $args ) {
862                 $this->escape( $args );
863
864                 $blog_id        = (int) $args[0];
865                 $username       = $args[1];
866                 $password       = $args[2];
867
868                 if( !$this->login_pass_ok( $username, $password ) ) {
869                         return $this->error;
870                 }
871
872                 set_current_user( 0, $username );
873                 if( !current_user_can( 'edit_posts' ) ) {
874                         return new IXR_Error( 403, __( 'You are not allowed access to details about this blog.' ) );
875                 }
876
877                 do_action('xmlrpc_call', 'wp.getPostStatusList');
878
879                 return get_post_statuses( );
880         }
881
882
883         function wp_getPageStatusList( $args ) {
884                 $this->escape( $args );
885
886                 $blog_id        = (int) $args[0];
887                 $username       = $args[1];
888                 $password       = $args[2];
889
890                 if( !$this->login_pass_ok( $username, $password ) ) {
891                         return $this->error;
892                 }
893
894                 set_current_user( 0, $username );
895                 if( !current_user_can( 'edit_posts' ) ) {
896                         return new IXR_Error( 403, __( 'You are not allowed access to details about this blog.' ) );
897                 }
898
899                 do_action('xmlrpc_call', 'wp.getPageStatusList');
900
901                 return get_page_statuses( );
902         }
903
904         function wp_getPageTemplates( $args ) {
905                 $this->escape( $args );
906
907                 $blog_id        = (int) $args[0];
908                 $username       = $args[1];
909                 $password       = $args[2];
910
911                 if( !$this->login_pass_ok( $username, $password ) ) {
912                         return $this->error;
913                 }
914
915                 set_current_user( 0, $username );
916                 if( !current_user_can( 'edit_pages' ) ) {
917                         return new IXR_Error( 403, __( 'You are not allowed access to details about this blog.' ) );
918                 }
919
920                 $templates = get_page_templates( );
921                 $templates['Default'] = 'default';
922
923                 return $templates;
924         }
925
926         function wp_getOptions( $args ) {
927                 $this->escape( $args );
928
929                 $blog_id        = (int) $args[0];
930                 $username       = $args[1];
931                 $password       = $args[2];
932                 $options        = (array) $args[3];
933
934                 if( !$this->login_pass_ok( $username, $password ) )
935                         return $this->error;
936
937                 $user = set_current_user( 0, $username );
938
939                 // If no specific options where asked for, return all of them
940                 if (count( $options ) == 0 ) {
941                         $options = array_keys($this->blog_options);
942                 }
943
944                 return $this->_getOptions($options);
945         }
946
947         function _getOptions($options)
948         {
949                 $data = array( );
950                 foreach( $options as $option ) {
951                         if( array_key_exists( $option, $this->blog_options ) )
952                         {
953                                 $data[$option] = $this->blog_options[$option];
954                                 //Is the value static or dynamic?
955                                 if( isset( $data[$option]['option'] ) ) {
956                                         $data[$option]['value'] = get_option( $data[$option]['option'] );
957                                         unset($data[$option]['option']);
958                                 }
959                         }
960                 }
961
962                 return $data;
963         }
964
965         function wp_setOptions( $args ) {
966                 $this->escape( $args );
967
968                 $blog_id        = (int) $args[0];
969                 $username       = $args[1];
970                 $password       = $args[2];
971                 $options        = (array) $args[3];
972
973                 if( !$this->login_pass_ok( $username, $password ) )
974                         return $this->error;
975
976                 $user = set_current_user( 0, $username );
977                 if( !current_user_can( 'manage_options' ) )
978                         return new IXR_Error( 403, __( 'You are not allowed to update options.' ) );
979
980                 foreach( $options as $o_name => $o_value ) {
981                         $option_names[] = $o_name;
982                         if( empty( $o_value ) )
983                                 continue;
984
985                         if( !array_key_exists( $o_name, $this->blog_options ) )
986                                 continue;
987
988                         if( $this->blog_options[$o_name]['readonly'] == true )
989                                 continue;
990
991                         update_option( $this->blog_options[$o_name]['option'], $o_value );
992                 }
993
994                 //Now return the updated values
995                 return $this->_getOptions($option_names);
996         }
997
998         /* Blogger API functions
999          * specs on http://plant.blogger.com/api and http://groups.yahoo.com/group/bloggerDev/
1000          */
1001
1002
1003         /* blogger.getUsersBlogs will make more sense once we support multiple blogs */
1004         function blogger_getUsersBlogs($args) {
1005
1006                 $this->escape($args);
1007
1008                 $user_login = $args[1];
1009                 $user_pass  = $args[2];
1010
1011                 if (!$this->login_pass_ok($user_login, $user_pass)) {
1012                         return $this->error;
1013                 }
1014
1015                 do_action('xmlrpc_call', 'blogger.getUsersBlogs');
1016
1017                 set_current_user(0, $user_login);
1018                 $is_admin = current_user_can('manage_options');
1019
1020                 $struct = array(
1021                         'isAdmin'  => $is_admin,
1022                         'url'      => get_option('home') . '/',
1023                         'blogid'   => '1',
1024                         'blogName' => get_option('blogname'),
1025                         'xmlrpc'   => get_option('home') . '/xmlrpc.php',
1026                 );
1027
1028                 return array($struct);
1029         }
1030
1031
1032         /* blogger.getUsersInfo gives your client some info about you, so you don't have to */
1033         function blogger_getUserInfo($args) {
1034
1035                 $this->escape($args);
1036
1037                 $user_login = $args[1];
1038                 $user_pass  = $args[2];
1039
1040                 if (!$this->login_pass_ok($user_login, $user_pass)) {
1041                         return $this->error;
1042                 }
1043
1044                 set_current_user( 0, $user_login );
1045                 if( !current_user_can( 'edit_posts' ) )
1046                         return new IXR_Error( 401, __( 'Sorry, you do not have access to user data on this blog.' ) );
1047
1048                 do_action('xmlrpc_call', 'blogger.getUserInfo');
1049
1050                 $user_data = get_userdatabylogin($user_login);
1051
1052                 $struct = array(
1053                         'nickname'  => $user_data->nickname,
1054                         'userid'    => $user_data->ID,
1055                         'url'       => $user_data->user_url,
1056                         'lastname'  => $user_data->last_name,
1057                         'firstname' => $user_data->first_name
1058                 );
1059
1060                 return $struct;
1061         }
1062
1063
1064         /* blogger.getPost ...gets a post */
1065         function blogger_getPost($args) {
1066
1067                 $this->escape($args);
1068
1069                 $post_ID    = (int) $args[1];
1070                 $user_login = $args[2];
1071                 $user_pass  = $args[3];
1072
1073                 if (!$this->login_pass_ok($user_login, $user_pass)) {
1074                         return $this->error;
1075                 }
1076
1077                 set_current_user( 0, $user_login );
1078                 if( !current_user_can( 'edit_post', $post_ID ) )
1079                         return new IXR_Error( 401, __( 'Sorry, you can not edit this post.' ) );
1080
1081                 do_action('xmlrpc_call', 'blogger.getPost');
1082
1083                 $post_data = wp_get_single_post($post_ID, ARRAY_A);
1084
1085                 $categories = implode(',', wp_get_post_categories($post_ID));
1086
1087                 $content  = '<title>'.stripslashes($post_data['post_title']).'</title>';
1088                 $content .= '<category>'.$categories.'</category>';
1089                 $content .= stripslashes($post_data['post_content']);
1090
1091                 $struct = array(
1092                         'userid'    => $post_data['post_author'],
1093                         'dateCreated' => new IXR_Date(mysql2date('Ymd\TH:i:s', $post_data['post_date'])),
1094                         'content'     => $content,
1095                         'postid'  => $post_data['ID']
1096                 );
1097
1098                 return $struct;
1099         }
1100
1101
1102         /* blogger.getRecentPosts ...gets recent posts */
1103         function blogger_getRecentPosts($args) {
1104
1105                 $this->escape($args);
1106
1107                 $blog_ID    = (int) $args[1]; /* though we don't use it yet */
1108                 $user_login = $args[2];
1109                 $user_pass  = $args[3];
1110                 $num_posts  = $args[4];
1111
1112                 if (!$this->login_pass_ok($user_login, $user_pass)) {
1113                         return $this->error;
1114                 }
1115
1116                 do_action('xmlrpc_call', 'blogger.getRecentPosts');
1117
1118                 $posts_list = wp_get_recent_posts($num_posts);
1119
1120                 set_current_user( 0, $user_login );
1121
1122                 if (!$posts_list) {
1123                         $this->error = new IXR_Error(500, __('Either there are no posts, or something went wrong.'));
1124                         return $this->error;
1125                 }
1126
1127                 foreach ($posts_list as $entry) {
1128                         if( !current_user_can( 'edit_post', $entry['ID'] ) )
1129                                 continue;
1130
1131                         $post_date = mysql2date('Ymd\TH:i:s', $entry['post_date']);
1132                         $categories = implode(',', wp_get_post_categories($entry['ID']));
1133
1134                         $content  = '<title>'.stripslashes($entry['post_title']).'</title>';
1135                         $content .= '<category>'.$categories.'</category>';
1136                         $content .= stripslashes($entry['post_content']);
1137
1138                         $struct[] = array(
1139                                 'userid' => $entry['post_author'],
1140                                 'dateCreated' => new IXR_Date($post_date),
1141                                 'content' => $content,
1142                                 'postid' => $entry['ID'],
1143                         );
1144
1145                 }
1146
1147                 $recent_posts = array();
1148                 for ($j=0; $j<count($struct); $j++) {
1149                         array_push($recent_posts, $struct[$j]);
1150                 }
1151
1152                 return $recent_posts;
1153         }
1154
1155
1156         /* blogger.getTemplate returns your blog_filename */
1157         function blogger_getTemplate($args) {
1158
1159                 $this->escape($args);
1160
1161           $blog_ID    = (int) $args[1];
1162           $user_login = $args[2];
1163           $user_pass  = $args[3];
1164           $template   = $args[4]; /* could be 'main' or 'archiveIndex', but we don't use it */
1165
1166           if (!$this->login_pass_ok($user_login, $user_pass)) {
1167             return $this->error;
1168           }
1169
1170           do_action('xmlrpc_call', 'blogger.getTemplate');
1171
1172           set_current_user(0, $user_login);
1173           if ( !current_user_can('edit_themes') ) {
1174             return new IXR_Error(401, __('Sorry, this user can not edit the template.'));
1175           }
1176
1177           /* warning: here we make the assumption that the blog's URL is on the same server */
1178           $filename = get_option('home') . '/';
1179           $filename = preg_replace('#https?://.+?/#', $_SERVER['DOCUMENT_ROOT'].'/', $filename);
1180
1181           $f = fopen($filename, 'r');
1182           $content = fread($f, filesize($filename));
1183           fclose($f);
1184
1185           /* so it is actually editable with a windows/mac client */
1186           // 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);
1187
1188           return $content;
1189         }
1190
1191
1192         /* blogger.setTemplate updates the content of blog_filename */
1193         function blogger_setTemplate($args) {
1194
1195                 $this->escape($args);
1196
1197                 $blog_ID    = (int) $args[1];
1198                 $user_login = $args[2];
1199                 $user_pass  = $args[3];
1200                 $content    = $args[4];
1201                 $template   = $args[5]; /* could be 'main' or 'archiveIndex', but we don't use it */
1202
1203                 if (!$this->login_pass_ok($user_login, $user_pass)) {
1204                         return $this->error;
1205                 }
1206
1207                 do_action('xmlrpc_call', 'blogger.setTemplate');
1208
1209                 set_current_user(0, $user_login);
1210                 if ( !current_user_can('edit_themes') ) {
1211                         return new IXR_Error(401, __('Sorry, this user can not edit the template.'));
1212                 }
1213
1214                 /* warning: here we make the assumption that the blog's URL is on the same server */
1215                 $filename = get_option('home') . '/';
1216                 $filename = preg_replace('#https?://.+?/#', $_SERVER['DOCUMENT_ROOT'].'/', $filename);
1217
1218                 if ($f = fopen($filename, 'w+')) {
1219                         fwrite($f, $content);
1220                         fclose($f);
1221                 } else {
1222                         return new IXR_Error(500, __('Either the file is not writable, or something wrong happened. The file has not been updated.'));
1223                 }
1224
1225                 return true;
1226         }
1227
1228
1229         /* blogger.newPost ...creates a new post */
1230         function blogger_newPost($args) {
1231
1232                 $this->escape($args);
1233
1234                 $blog_ID    = (int) $args[1]; /* though we don't use it yet */
1235                 $user_login = $args[2];
1236                 $user_pass  = $args[3];
1237                 $content    = $args[4];
1238                 $publish    = $args[5];
1239
1240                 if (!$this->login_pass_ok($user_login, $user_pass)) {
1241                         return $this->error;
1242                 }
1243
1244                 do_action('xmlrpc_call', 'blogger.newPost');
1245
1246                 $cap = ($publish) ? 'publish_posts' : 'edit_posts';
1247                 $user = set_current_user(0, $user_login);
1248                 if ( !current_user_can($cap) )
1249                         return new IXR_Error(401, __('Sorry, you are not allowed to post on this blog.'));
1250
1251                 $post_status = ($publish) ? 'publish' : 'draft';
1252
1253                 $post_author = $user->ID;
1254
1255                 $post_title = xmlrpc_getposttitle($content);
1256                 $post_category = xmlrpc_getpostcategory($content);
1257                 $post_content = xmlrpc_removepostdata($content);
1258
1259                 $post_date = current_time('mysql');
1260                 $post_date_gmt = current_time('mysql', 1);
1261
1262                 $post_data = compact('blog_ID', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status');
1263
1264                 $post_ID = wp_insert_post($post_data);
1265                 if ( is_wp_error( $post_ID ) )
1266                         return new IXR_Error(500, $post_ID->get_error_message());
1267
1268                 if (!$post_ID)
1269                         return new IXR_Error(500, __('Sorry, your entry could not be posted. Something wrong happened.'));
1270
1271                 $this->attach_uploads( $post_ID, $post_content );
1272
1273                 logIO('O', "Posted ! ID: $post_ID");
1274
1275                 return $post_ID;
1276         }
1277
1278         /* blogger.editPost ...edits a post */
1279         function blogger_editPost($args) {
1280
1281                 $this->escape($args);
1282
1283                 $post_ID     = (int) $args[1];
1284                 $user_login  = $args[2];
1285                 $user_pass   = $args[3];
1286                 $content     = $args[4];
1287                 $publish     = $args[5];
1288
1289                 if (!$this->login_pass_ok($user_login, $user_pass)) {
1290                         return $this->error;
1291                 }
1292
1293                 do_action('xmlrpc_call', 'blogger.editPost');
1294
1295                 $actual_post = wp_get_single_post($post_ID,ARRAY_A);
1296
1297                 if (!$actual_post) {
1298                         return new IXR_Error(404, __('Sorry, no such post.'));
1299                 }
1300
1301                 $this->escape($actual_post);
1302
1303                 set_current_user(0, $user_login);
1304                 if ( !current_user_can('edit_post', $post_ID) )
1305                         return new IXR_Error(401, __('Sorry, you do not have the right to edit this post.'));
1306
1307                 extract($actual_post, EXTR_SKIP);
1308
1309                 if ( ('publish' == $post_status) && !current_user_can('publish_posts') )
1310                         return new IXR_Error(401, __('Sorry, you do not have the right to publish this post.'));
1311
1312                 $post_title = xmlrpc_getposttitle($content);
1313                 $post_category = xmlrpc_getpostcategory($content);
1314                 $post_content = xmlrpc_removepostdata($content);
1315
1316                 $postdata = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt');
1317
1318                 $result = wp_update_post($postdata);
1319
1320                 if (!$result) {
1321                         return new IXR_Error(500, __('For some strange yet very annoying reason, this post could not be edited.'));
1322                 }
1323                 $this->attach_uploads( $ID, $post_content );
1324
1325                 return true;
1326         }
1327
1328
1329         /* blogger.deletePost ...deletes a post */
1330         function blogger_deletePost($args) {
1331                 $this->escape($args);
1332
1333                 $post_ID     = (int) $args[1];
1334                 $user_login  = $args[2];
1335                 $user_pass   = $args[3];
1336                 $publish     = $args[4];
1337
1338                 if (!$this->login_pass_ok($user_login, $user_pass)) {
1339                         return $this->error;
1340                 }
1341
1342                 do_action('xmlrpc_call', 'blogger.deletePost');
1343
1344                 $actual_post = wp_get_single_post($post_ID,ARRAY_A);
1345
1346                 if (!$actual_post) {
1347                         return new IXR_Error(404, __('Sorry, no such post.'));
1348                 }
1349
1350                 set_current_user(0, $user_login);
1351                 if ( !current_user_can('edit_post', $post_ID) )
1352                         return new IXR_Error(401, __('Sorry, you do not have the right to delete this post.'));
1353
1354                 $result = wp_delete_post($post_ID);
1355
1356                 if (!$result) {
1357                         return new IXR_Error(500, __('For some strange yet very annoying reason, this post could not be deleted.'));
1358                 }
1359
1360                 return true;
1361         }
1362
1363
1364
1365         /* MetaWeblog API functions
1366          * specs on wherever Dave Winer wants them to be
1367          */
1368
1369         /* metaweblog.newPost creates a post */
1370         function mw_newPost($args) {
1371                 $this->escape($args);
1372
1373                 $blog_ID     = (int) $args[0]; // we will support this in the near future
1374                 $user_login  = $args[1];
1375                 $user_pass   = $args[2];
1376                 $content_struct = $args[3];
1377                 $publish     = $args[4];
1378
1379                 if (!$this->login_pass_ok($user_login, $user_pass)) {
1380                         return $this->error;
1381                 }
1382                 $user = set_current_user(0, $user_login);
1383
1384                 do_action('xmlrpc_call', 'metaWeblog.newPost');
1385
1386                 $cap = ( $publish ) ? 'publish_posts' : 'edit_posts';
1387                 $error_message = __( 'Sorry, you are not allowed to publish posts on this blog.' );
1388                 $post_type = 'post';
1389                 $page_template = '';
1390                 if( !empty( $content_struct['post_type'] ) ) {
1391                         if( $content_struct['post_type'] == 'page' ) {
1392                                 $cap = ( $publish ) ? 'publish_pages' : 'edit_pages';
1393                                 $error_message = __( 'Sorry, you are not allowed to publish pages on this blog.' );
1394                                 $post_type = 'page';
1395                                 if( !empty( $content_struct['wp_page_template'] ) )
1396                                         $page_template = $content_struct['wp_page_template'];
1397                         }
1398                         elseif( $content_struct['post_type'] == 'post' ) {
1399                                 // This is the default, no changes needed
1400                         }
1401                         else {
1402                                 // No other post_type values are allowed here
1403                                 return new IXR_Error( 401, __( 'Invalid post type.' ) );
1404                         }
1405                 }
1406
1407                 if( !current_user_can( $cap ) ) {
1408                         return new IXR_Error( 401, $error_message );
1409                 }
1410
1411                 // Let WordPress generate the post_name (slug) unless
1412                 // one has been provided.
1413                 $post_name = "";
1414                 if(isset($content_struct["wp_slug"])) {
1415                         $post_name = $content_struct["wp_slug"];
1416                 }
1417
1418                 // Only use a password if one was given.
1419                 if(isset($content_struct["wp_password"])) {
1420                         $post_password = $content_struct["wp_password"];
1421                 }
1422
1423                 // Only set a post parent if one was provided.
1424                 if(isset($content_struct["wp_page_parent_id"])) {
1425                         $post_parent = $content_struct["wp_page_parent_id"];
1426                 }
1427
1428                 // Only set the menu_order if it was provided.
1429                 if(isset($content_struct["wp_page_order"])) {
1430                         $menu_order = $content_struct["wp_page_order"];
1431                 }
1432
1433                 $post_author = $user->ID;
1434
1435                 // If an author id was provided then use it instead.
1436                 if(
1437                         isset($content_struct["wp_author_id"])
1438                         && ($user->ID != $content_struct["wp_author_id"])
1439                 ) {
1440                         switch($post_type) {
1441                                 case "post":
1442                                         if(!current_user_can("edit_others_posts")) {
1443                                                 return(new IXR_Error(401, __("You are not allowed to post as this user")));
1444                                         }
1445                                         break;
1446                                 case "page":
1447                                         if(!current_user_can("edit_others_pages")) {
1448                                                 return(new IXR_Error(401, __("You are not allowed to create pages as this user")));
1449                                         }
1450                                         break;
1451                                 default:
1452                                         return(new IXR_Error(401, __("Invalid post type.")));
1453                                         break;
1454                         }
1455                         $post_author = $content_struct["wp_author_id"];
1456                 }
1457
1458                 $post_title = $content_struct['title'];
1459                 $post_content = apply_filters( 'content_save_pre', $content_struct['description'] );
1460
1461                 $post_status = $publish ? 'publish' : 'draft';
1462
1463                 if( isset( $content_struct["{$post_type}_status"] ) ) {
1464                         switch( $content_struct["{$post_type}_status"] ) {
1465                                 case 'draft':
1466                                 case 'private':
1467                                 case 'publish':
1468                                         $post_status = $content_struct["{$post_type}_status"];
1469                                         break;
1470                                 case 'pending':
1471                                         // Pending is only valid for posts, not pages.
1472                                         if( $post_type === 'post' ) {
1473                                                 $post_status = $content_struct["{$post_type}_status"];
1474                                         }
1475                                         break;
1476                                 default:
1477                                         $post_status = $publish ? 'publish' : 'draft';
1478                                         break;
1479                         }
1480                 }
1481
1482                 $post_excerpt = $content_struct['mt_excerpt'];
1483                 $post_more = $content_struct['mt_text_more'];
1484
1485                 $tags_input = $content_struct['mt_keywords'];
1486
1487                 if(isset($content_struct["mt_allow_comments"])) {
1488                         if(!is_numeric($content_struct["mt_allow_comments"])) {
1489                                 switch($content_struct["mt_allow_comments"]) {
1490                                         case "closed":
1491                                                 $comment_status = "closed";
1492                                                 break;
1493                                         case "open":
1494                                                 $comment_status = "open";
1495                                                 break;
1496                                         default:
1497                                                 $comment_status = get_option("default_comment_status");
1498                                                 break;
1499                                 }
1500                         }
1501                         else {
1502                                 switch((int) $content_struct["mt_allow_comments"]) {
1503                                         case 0:
1504                                         case 2:
1505                                                 $comment_status = "closed";
1506                                                 break;
1507                                         case 1:
1508                                                 $comment_status = "open";
1509                                                 break;
1510                                         default:
1511                                                 $comment_status = get_option("default_comment_status");
1512                                                 break;
1513                                 }
1514                         }
1515                 }
1516                 else {
1517                         $comment_status = get_option("default_comment_status");
1518                 }
1519
1520                 if(isset($content_struct["mt_allow_pings"])) {
1521                         if(!is_numeric($content_struct["mt_allow_pings"])) {
1522                                 switch($content_struct['mt_allow_pings']) {
1523                                         case "closed":
1524                                                 $ping_status = "closed";
1525                                                 break;
1526                                         case "open":
1527                                                 $ping_status = "open";
1528                                                 break;
1529                                         default:
1530                                                 $ping_status = get_option("default_ping_status");
1531                                                 break;
1532                                 }
1533                         }
1534                         else {
1535                                 switch((int) $content_struct["mt_allow_pings"]) {
1536                                         case 0:
1537                                                 $ping_status = "closed";
1538                                                 break;
1539                                         case 1:
1540                                                 $ping_status = "open";
1541                                                 break;
1542                                         default:
1543                                                 $ping_status = get_option("default_ping_status");
1544                                                 break;
1545                                 }
1546                         }
1547                 }
1548                 else {
1549                         $ping_status = get_option("default_ping_status");
1550                 }
1551
1552                 if ($post_more) {
1553                         $post_content = $post_content . "<!--more-->" . $post_more;
1554                 }
1555
1556                 $to_ping = $content_struct['mt_tb_ping_urls'];
1557                 if ( is_array($to_ping) )
1558                         $to_ping = implode(' ', $to_ping);
1559
1560                 // Do some timestamp voodoo
1561                 if ( !empty( $content_struct['date_created_gmt'] ) )
1562                         $dateCreated = str_replace( 'Z', '', $content_struct['date_created_gmt']->getIso() ) . 'Z'; // We know this is supposed to be GMT, so we're going to slap that Z on there by force
1563                 elseif ( !empty( $content_struct['dateCreated']) )
1564                         $dateCreated = $content_struct['dateCreated']->getIso();
1565
1566                 if ( !empty( $dateCreated ) ) {
1567                         $post_date = get_date_from_gmt(iso8601_to_datetime($dateCreated));
1568                         $post_date_gmt = iso8601_to_datetime($dateCreated, GMT);
1569                 } else {
1570                         $post_date = current_time('mysql');
1571                         $post_date_gmt = current_time('mysql', 1);
1572                 }
1573
1574                 $catnames = $content_struct['categories'];
1575                 logIO('O', 'Post cats: ' . var_export($catnames,true));
1576                 $post_category = array();
1577
1578                 if (is_array($catnames)) {
1579                         foreach ($catnames as $cat) {
1580                                 $post_category[] = get_cat_ID($cat);
1581                         }
1582                 }
1583
1584                 // We've got all the data -- post it:
1585                 $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', 'post_type', 'post_name', 'post_password', 'post_parent', 'menu_order', 'tags_input', 'page_template');
1586
1587                 $post_ID = wp_insert_post($postdata, true);
1588                 if ( is_wp_error( $post_ID ) )
1589                         return new IXR_Error(500, $post_ID->get_error_message());
1590
1591                 if (!$post_ID) {
1592                         return new IXR_Error(500, __('Sorry, your entry could not be posted. Something wrong happened.'));
1593                 }
1594
1595                 if ( isset($content_struct['custom_fields']) ) {
1596                         $this->set_custom_fields($post_ID, $content_struct['custom_fields']);
1597                 }
1598
1599                 // Handle enclosures 
1600                 $enclosure = $content_struct['enclosure']; 
1601                 if( is_array( $enclosure ) && isset( $enclosure['url'] ) && isset( $enclosure['length'] ) && isset( $enclosure['type'] ) ) { 
1602                         add_post_meta( $post_ID, 'enclosure', $enclosure['url'] . "\n" . $enclosure['length'] . "\n" . $enclosure['type'] );
1603                 } 
1604
1605                 $this->attach_uploads( $post_ID, $post_content );
1606
1607                 logIO('O', "Posted ! ID: $post_ID");
1608
1609                 return strval($post_ID);
1610         }
1611
1612         function attach_uploads( $post_ID, $post_content ) {
1613                 global $wpdb;
1614
1615                 // find any unattached files
1616                 $attachments = $wpdb->get_results( "SELECT ID, guid FROM {$wpdb->posts} WHERE post_parent = '-1' AND post_type = 'attachment'" );
1617                 if( is_array( $attachments ) ) {
1618                         foreach( $attachments as $file ) {
1619                                 if( strpos( $post_content, $file->guid ) !== false ) {
1620                                         $wpdb->query( $wpdb->prepare("UPDATE {$wpdb->posts} SET post_parent = %d WHERE ID = %d", $post_ID, $file->ID) );
1621                                 }
1622                         }
1623                 }
1624         }
1625
1626         /* metaweblog.editPost ...edits a post */
1627         function mw_editPost($args) {
1628
1629                 $this->escape($args);
1630
1631                 $post_ID     = (int) $args[0];
1632                 $user_login  = $args[1];
1633                 $user_pass   = $args[2];
1634                 $content_struct = $args[3];
1635                 $publish     = $args[4];
1636
1637                 if (!$this->login_pass_ok($user_login, $user_pass)) {
1638                         return $this->error;
1639                 }
1640                 $user = set_current_user(0, $user_login);
1641
1642                 do_action('xmlrpc_call', 'metaWeblog.editPost');
1643
1644                 $cap = ( $publish ) ? 'publish_posts' : 'edit_posts';
1645                 $error_message = __( 'Sorry, you are not allowed to publish posts on this blog.' );
1646                 $post_type = 'post';
1647                 $page_template = '';
1648                 if( !empty( $content_struct['post_type'] ) ) {
1649                         if( $content_struct['post_type'] == 'page' ) {
1650                                 $cap = ( $publish ) ? 'publish_pages' : 'edit_pages';
1651                                 $error_message = __( 'Sorry, you are not allowed to publish pages on this blog.' );
1652                                 $post_type = 'page';
1653                                 if( !empty( $content_struct['wp_page_template'] ) )
1654                                         $page_template = $content_struct['wp_page_template'];
1655                         }
1656                         elseif( $content_struct['post_type'] == 'post' ) {
1657                                 // This is the default, no changes needed
1658                         }
1659                         else {
1660                                 // No other post_type values are allowed here
1661                                 return new IXR_Error( 401, __( 'Invalid post type.' ) );
1662                         }
1663                 }
1664
1665                 if( !current_user_can( $cap ) ) {
1666                         return new IXR_Error( 401, $error_message );
1667                 }
1668
1669                 $postdata = wp_get_single_post($post_ID, ARRAY_A);
1670
1671                 // If there is no post data for the give post id, stop
1672                 // now and return an error.  Other wise a new post will be
1673                 // created (which was the old behavior).
1674                 if(empty($postdata["ID"])) {
1675                         return(new IXR_Error(404, __("Invalid post id.")));
1676                 }
1677
1678                 $this->escape($postdata);
1679                 extract($postdata, EXTR_SKIP);
1680
1681                 // Let WordPress manage slug if none was provided.
1682                 $post_name = "";
1683                 if(isset($content_struct["wp_slug"])) {
1684                         $post_name = $content_struct["wp_slug"];
1685                 }
1686
1687                 // Only use a password if one was given.
1688                 if(isset($content_struct["wp_password"])) {
1689                         $post_password = $content_struct["wp_password"];
1690                 }
1691
1692                 // Only set a post parent if one was given.
1693                 if(isset($content_struct["wp_page_parent_id"])) {
1694                         $post_parent = $content_struct["wp_page_parent_id"];
1695                 }
1696
1697                 // Only set the menu_order if it was given.
1698                 if(isset($content_struct["wp_page_order"])) {
1699                         $menu_order = $content_struct["wp_page_order"];
1700                 }
1701
1702                 $post_author = $postdata["post_author"];
1703
1704                 // Only set the post_author if one is set.
1705                 if(
1706                         isset($content_struct["wp_author_id"])
1707                         && ($user->ID != $content_struct["wp_author_id"])
1708                 ) {
1709                         switch($post_type) {
1710                                 case "post":
1711                                         if(!current_user_can("edit_others_posts")) {
1712                                                 return(new IXR_Error(401, __("You are not allowed to change the post author as this user.")));
1713                                         }
1714                                         break;
1715                                 case "page":
1716                                         if(!current_user_can("edit_others_pages")) {
1717                                                 return(new IXR_Error(401, __("You are not allowed to change the page author as this user.")));
1718                                         }
1719                                         break;
1720                                 default:
1721                                         return(new IXR_Error(401, __("Invalid post type.")));
1722                                         break;
1723                         }
1724                         $post_author = $content_struct["wp_author_id"];
1725                 }
1726
1727                 if(isset($content_struct["mt_allow_comments"])) {
1728                         if(!is_numeric($content_struct["mt_allow_comments"])) {
1729                                 switch($content_struct["mt_allow_comments"]) {
1730                                         case "closed":
1731                                                 $comment_status = "closed";
1732                                                 break;
1733                                         case "open":
1734                                                 $comment_status = "open";
1735                                                 break;
1736                                         default:
1737                                                 $comment_status = get_option("default_comment_status");
1738                                                 break;
1739                                 }
1740                         }
1741                         else {
1742                                 switch((int) $content_struct["mt_allow_comments"]) {
1743                                         case 0:
1744                                         case 2:
1745                                                 $comment_status = "closed";
1746                                                 break;
1747                                         case 1:
1748                                                 $comment_status = "open";
1749                                                 break;
1750                                         default:
1751                                                 $comment_status = get_option("default_comment_status");
1752                                                 break;
1753                                 }
1754                         }
1755                 }
1756
1757                 if(isset($content_struct["mt_allow_pings"])) {
1758                         if(!is_numeric($content_struct["mt_allow_pings"])) {
1759                                 switch($content_struct["mt_allow_pings"]) {
1760                                         case "closed":
1761                                                 $ping_status = "closed";
1762                                                 break;
1763                                         case "open":
1764                                                 $ping_status = "open";
1765                                                 break;
1766                                         default:
1767                                                 $ping_status = get_option("default_ping_status");
1768                                                 break;
1769                                 }
1770                         }
1771                         else {
1772                                 switch((int) $content_struct["mt_allow_pings"]) {
1773                                         case 0:
1774                                                 $ping_status = "closed";
1775                                                 break;
1776                                         case 1:
1777                                                 $ping_status = "open";
1778                                                 break;
1779                                         default:
1780                                                 $ping_status = get_option("default_ping_status");
1781                                                 break;
1782                                 }
1783                         }
1784                 }
1785
1786                 $post_title = $content_struct['title'];
1787                 $post_content = apply_filters( 'content_save_pre', $content_struct['description'] );
1788                 $catnames = $content_struct['categories'];
1789
1790                 $post_category = array();
1791
1792                 if (is_array($catnames)) {
1793                         foreach ($catnames as $cat) {
1794                                 $post_category[] = get_cat_ID($cat);
1795                         }
1796                 }
1797
1798                 $post_excerpt = $content_struct['mt_excerpt'];
1799                 $post_more = $content_struct['mt_text_more'];
1800
1801                 $post_status = $publish ? 'publish' : 'draft';
1802                 if( isset( $content_struct["{$post_type}_status"] ) ) {
1803                         switch( $content_struct["{$post_type}_status"] ) {
1804                                 case 'draft':
1805                                 case 'private':
1806                                 case 'publish':
1807                                         $post_status = $content_struct["{$post_type}_status"];
1808                                         break;
1809                                 case 'pending':
1810                                         // Pending is only valid for posts, not pages.
1811                                         if( $post_type === 'post' ) {
1812                                                 $post_status = $content_struct["{$post_type}_status"];
1813                                         }
1814                                         break;
1815                                 default:
1816                                         $post_status = $publish ? 'publish' : 'draft';
1817                                         break;
1818                         }
1819                 }
1820
1821                 $tags_input = $content_struct['mt_keywords'];
1822
1823                 if ( ('publish' == $post_status) ) {
1824                         if ( ( 'page' == $post_type ) && !current_user_can('publish_pages') )
1825                                 return new IXR_Error(401, __('Sorry, you do not have the right to publish this page.'));
1826                         else if ( !current_user_can('publish_posts') )
1827                                 return new IXR_Error(401, __('Sorry, you do not have the right to publish this post.'));
1828                 }
1829
1830                 if ($post_more) {
1831                         $post_content = $post_content . "<!--more-->" . $post_more;
1832                 }
1833
1834                 $to_ping = $content_struct['mt_tb_ping_urls'];
1835                 if ( is_array($to_ping) )
1836                         $to_ping = implode(' ', $to_ping);
1837
1838                 // Do some timestamp voodoo
1839                 if ( !empty( $content_struct['date_created_gmt'] ) )
1840                         $dateCreated = str_replace( 'Z', '', $content_struct['date_created_gmt']->getIso() ) . 'Z'; // We know this is supposed to be GMT, so we're going to slap that Z on there by force
1841                 elseif ( !empty( $content_struct['dateCreated']) )
1842                         $dateCreated = $content_struct['dateCreated']->getIso();
1843
1844                 if ( !empty( $dateCreated ) ) {
1845                         $post_date = get_date_from_gmt(iso8601_to_datetime($dateCreated));
1846                         $post_date_gmt = iso8601_to_datetime($dateCreated, GMT);
1847                 } else {
1848                         $post_date     = $postdata['post_date'];
1849                         $post_date_gmt = $postdata['post_date_gmt'];
1850                 }
1851
1852                 // We've got all the data -- post it:
1853                 $newpost = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'comment_status', 'ping_status', 'post_date', 'post_date_gmt', 'to_ping', 'post_name', 'post_password', 'post_parent', 'menu_order', 'post_author', 'tags_input', 'page_template');
1854
1855                 $result = wp_update_post($newpost, true);
1856                 if ( is_wp_error( $result ) )
1857                         return new IXR_Error(500, $result->get_error_message());
1858
1859                 if (!$result) {
1860                         return new IXR_Error(500, __('Sorry, your entry could not be edited. Something wrong happened.'));
1861                 }
1862
1863                 if ( isset($content_struct['custom_fields']) ) {
1864                         $this->set_custom_fields($post_ID, $content_struct['custom_fields']);
1865                 }
1866
1867                 // Handle enclosures 
1868                 $enclosure = $content_struct['enclosure']; 
1869                 if( is_array( $enclosure ) && isset( $enclosure['url'] ) && isset( $enclosure['length'] ) && isset( $enclosure['type'] ) ) { 
1870                         add_post_meta( $post_ID, 'enclosure', $enclosure['url'] . "\n" . $enclosure['length'] . "\n" . $enclosure['type'] );
1871                 } 
1872
1873                 $this->attach_uploads( $ID, $post_content );
1874
1875                 logIO('O',"(MW) Edited ! ID: $post_ID");
1876
1877                 return true;
1878         }
1879
1880
1881         /* metaweblog.getPost ...returns a post */
1882         function mw_getPost($args) {
1883
1884                 $this->escape($args);
1885
1886                 $post_ID     = (int) $args[0];
1887                 $user_login  = $args[1];
1888                 $user_pass   = $args[2];
1889
1890                 if (!$this->login_pass_ok($user_login, $user_pass)) {
1891                         return $this->error;
1892                 }
1893
1894                 set_current_user( 0, $user_login );
1895                 if( !current_user_can( 'edit_post', $post_ID ) )
1896                         return new IXR_Error( 401, __( 'Sorry, you can not edit this post.' ) );
1897
1898                 do_action('xmlrpc_call', 'metaWeblog.getPost');
1899
1900                 $postdata = wp_get_single_post($post_ID, ARRAY_A);
1901
1902                 if ($postdata['post_date'] != '') {
1903                         $post_date = mysql2date('Ymd\TH:i:s', $postdata['post_date']);
1904                         $post_date_gmt = mysql2date('Ymd\TH:i:s', $postdata['post_date_gmt']);
1905
1906                         $categories = array();
1907                         $catids = wp_get_post_categories($post_ID);
1908                         foreach($catids as $catid)
1909                                 $categories[] = get_cat_name($catid);
1910
1911                         $tagnames = array();
1912                         $tags = wp_get_post_tags( $post_ID );
1913                         if ( !empty( $tags ) ) {
1914                                 foreach ( $tags as $tag )
1915                                         $tagnames[] = $tag->name;
1916                                 $tagnames = implode( ', ', $tagnames );
1917                         } else {
1918                                 $tagnames = '';
1919                         }
1920
1921                         $post = get_extended($postdata['post_content']);
1922                         $link = post_permalink($postdata['ID']);
1923
1924                         // Get the author info.
1925                         $author = get_userdata($postdata['post_author']);
1926
1927                         $allow_comments = ('open' == $postdata['comment_status']) ? 1 : 0;
1928                         $allow_pings = ('open' == $postdata['ping_status']) ? 1 : 0;
1929
1930                         // Consider future posts as published
1931                         if( $postdata['post_status'] === 'future' ) {
1932                                 $postdata['post_status'] = 'publish';
1933                         }
1934
1935                         $resp = array(
1936                                 'dateCreated' => new IXR_Date($post_date),
1937                                 'userid' => $postdata['post_author'],
1938                                 'postid' => $postdata['ID'],
1939                                 'description' => $post['main'],
1940                                 'title' => $postdata['post_title'],
1941                                 'link' => $link,
1942                                 'permaLink' => $link,
1943                                 // commented out because no other tool seems to use this
1944                                 //            'content' => $entry['post_content'],
1945                                 'categories' => $categories,
1946                                 'mt_excerpt' => $postdata['post_excerpt'],
1947                                 'mt_text_more' => $post['extended'],
1948                                 'mt_allow_comments' => $allow_comments,
1949                                 'mt_allow_pings' => $allow_pings,
1950                                 'mt_keywords' => $tagnames,
1951                                 'wp_slug' => $postdata['post_name'],
1952                                 'wp_password' => $postdata['post_password'],
1953                                 'wp_author_id' => $author->ID,
1954                                 'wp_author_display_name'        => $author->display_name,
1955                                 'date_created_gmt' => new IXR_Date($post_date_gmt),
1956                                 'post_status' => $postdata['post_status'],
1957                                 'custom_fields' => $this->get_custom_fields($post_ID)
1958                         );
1959
1960                         return $resp;
1961                 } else {
1962                         return new IXR_Error(404, __('Sorry, no such post.'));
1963                 }
1964         }
1965
1966
1967         /* metaweblog.getRecentPosts ...returns recent posts */
1968         function mw_getRecentPosts($args) {
1969
1970                 $this->escape($args);
1971
1972                 $blog_ID     = (int) $args[0];
1973                 $user_login  = $args[1];
1974                 $user_pass   = $args[2];
1975                 $num_posts   = (int) $args[3];
1976
1977                 if (!$this->login_pass_ok($user_login, $user_pass)) {
1978                         return $this->error;
1979                 }
1980
1981                 do_action('xmlrpc_call', 'metaWeblog.getRecentPosts');
1982
1983                 $posts_list = wp_get_recent_posts($num_posts);
1984
1985                 if (!$posts_list) {
1986                         $this->error = new IXR_Error(500, __('Either there are no posts, or something went wrong.'));
1987                         return $this->error;
1988                 }
1989
1990                 set_current_user( 0, $user_login );
1991
1992                 foreach ($posts_list as $entry) {
1993                         if( !current_user_can( 'edit_post', $entry['ID'] ) )
1994                                 continue;
1995
1996                         $post_date = mysql2date('Ymd\TH:i:s', $entry['post_date']);
1997                         $post_date_gmt = mysql2date('Ymd\TH:i:s', $entry['post_date_gmt']);
1998
1999                         $categories = array();
2000                         $catids = wp_get_post_categories($entry['ID']);
2001                         foreach($catids as $catid) {
2002                                 $categories[] = get_cat_name($catid);
2003                         }
2004
2005                         $tagnames = array();
2006                         $tags = wp_get_post_tags( $entry['ID'] );
2007                         if ( !empty( $tags ) ) {
2008                                 foreach ( $tags as $tag ) {
2009                                         $tagnames[] = $tag->name;
2010                                 }
2011                                 $tagnames = implode( ', ', $tagnames );
2012                         } else {
2013                                 $tagnames = '';
2014                         }
2015
2016                         $post = get_extended($entry['post_content']);
2017                         $link = post_permalink($entry['ID']);
2018
2019                         // Get the post author info.
2020                         $author = get_userdata($entry['post_author']);
2021
2022                         $allow_comments = ('open' == $entry['comment_status']) ? 1 : 0;
2023                         $allow_pings = ('open' == $entry['ping_status']) ? 1 : 0;
2024
2025                         // Consider future posts as published
2026                         if( $entry['post_status'] === 'future' ) {
2027                                 $entry['post_status'] = 'publish';
2028                         }
2029
2030                         $struct[] = array(
2031                                 'dateCreated' => new IXR_Date($post_date),
2032                                 'userid' => $entry['post_author'],
2033                                 'postid' => $entry['ID'],
2034                                 'description' => $post['main'],
2035                                 'title' => $entry['post_title'],
2036                                 'link' => $link,
2037                                 'permaLink' => $link,
2038 // commented out because no other tool seems to use this
2039 //            'content' => $entry['post_content'],
2040                                 'categories' => $categories,
2041                                 'mt_excerpt' => $entry['post_excerpt'],
2042                                 'mt_text_more' => $post['extended'],
2043                                 'mt_allow_comments' => $allow_comments,
2044                                 'mt_allow_pings' => $allow_pings,
2045                                 'mt_keywords' => $tagnames,
2046                                 'wp_slug' => $entry['post_name'],
2047                                 'wp_password' => $entry['post_password'],
2048                                 'wp_author_id' => $author->ID,
2049                                 'wp_author_display_name' => $author->display_name,
2050                                 'date_created_gmt' => new IXR_Date($post_date_gmt),
2051                                 'post_status' => $entry['post_status'],
2052                                 'custom_fields' => $this->get_custom_fields($entry['ID'])
2053                         );
2054
2055                 }
2056
2057                 $recent_posts = array();
2058                 for ($j=0; $j<count($struct); $j++) {
2059                         array_push($recent_posts, $struct[$j]);
2060                 }
2061
2062                 return $recent_posts;
2063         }
2064
2065
2066         /* metaweblog.getCategories ...returns the list of categories on a given blog */
2067         function mw_getCategories($args) {
2068
2069                 $this->escape($args);
2070
2071                 $blog_ID     = (int) $args[0];
2072                 $user_login  = $args[1];
2073                 $user_pass   = $args[2];
2074
2075                 if (!$this->login_pass_ok($user_login, $user_pass)) {
2076                         return $this->error;
2077                 }
2078
2079                 set_current_user( 0, $user_login );
2080                 if( !current_user_can( 'edit_posts' ) )
2081                         return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this blog in order to view categories.' ) );
2082
2083                 do_action('xmlrpc_call', 'metaWeblog.getCategories');
2084
2085                 $categories_struct = array();
2086
2087                 if ( $cats = get_categories('get=all') ) {
2088                         foreach ( $cats as $cat ) {
2089                                 $struct['categoryId'] = $cat->term_id;
2090                                 $struct['parentId'] = $cat->parent;
2091                                 $struct['description'] = $cat->name;
2092                                 $struct['categoryName'] = $cat->name;
2093                                 $struct['htmlUrl'] = wp_specialchars(get_category_link($cat->term_id));
2094                                 $struct['rssUrl'] = wp_specialchars(get_category_rss_link(false, $cat->term_id, $cat->name));
2095
2096                                 $categories_struct[] = $struct;
2097                         }
2098                 }
2099
2100                 return $categories_struct;
2101         }
2102
2103
2104         /* metaweblog.newMediaObject uploads a file, following your settings */
2105         function mw_newMediaObject($args) {
2106                 // adapted from a patch by Johann Richard
2107                 // http://mycvs.org/archives/2004/06/30/file-upload-to-wordpress-in-ecto/
2108
2109                 global $wpdb;
2110
2111                 $blog_ID     = (int) $args[0];
2112                 $user_login  = $wpdb->escape($args[1]);
2113                 $user_pass   = $wpdb->escape($args[2]);
2114                 $data        = $args[3];
2115
2116                 $name = sanitize_file_name( $data['name'] );
2117                 $type = $data['type'];
2118                 $bits = $data['bits'];
2119
2120                 logIO('O', '(MW) Received '.strlen($bits).' bytes');
2121
2122                 if ( !$this->login_pass_ok($user_login, $user_pass) )
2123                         return $this->error;
2124
2125                 do_action('xmlrpc_call', 'metaWeblog.newMediaObject');
2126
2127                 set_current_user(0, $user_login);
2128                 if ( !current_user_can('upload_files') ) {
2129                         logIO('O', '(MW) User does not have upload_files capability');
2130                         $this->error = new IXR_Error(401, __('You are not allowed to upload files to this site.'));
2131                         return $this->error;
2132                 }
2133
2134                 if ( $upload_err = apply_filters( "pre_upload_error", false ) )
2135                         return new IXR_Error(500, $upload_err);
2136
2137                 if(!empty($data["overwrite"]) && ($data["overwrite"] == true)) {
2138                         // Get postmeta info on the object.
2139                         $old_file = $wpdb->get_row("
2140                                 SELECT ID
2141                                 FROM {$wpdb->posts}
2142                                 WHERE post_title = '{$name}'
2143                                         AND post_type = 'attachment'
2144                         ");
2145
2146                         // Delete previous file.
2147                         wp_delete_attachment($old_file->ID);
2148
2149                         // Make sure the new name is different by pre-pending the
2150                         // previous post id.
2151                         $filename = preg_replace("/^wpid\d+-/", "", $name);
2152                         $name = "wpid{$old_file->ID}-{$filename}";
2153                 }
2154
2155                 $upload = wp_upload_bits($name, $type, $bits);
2156                 if ( ! empty($upload['error']) ) {
2157                         $errorString = sprintf(__('Could not write file %1$s (%2$s)'), $name, $upload['error']);
2158                         logIO('O', '(MW) ' . $errorString);
2159                         return new IXR_Error(500, $errorString);
2160                 }
2161                 // Construct the attachment array
2162                 // attach to post_id -1
2163                 $post_id = -1;
2164                 $attachment = array(
2165                         'post_title' => $name,
2166                         'post_content' => '',
2167                         'post_type' => 'attachment',
2168                         'post_parent' => $post_id,
2169                         'post_mime_type' => $type,
2170                         'guid' => $upload[ 'url' ]
2171                 );
2172
2173                 // Save the data
2174                 $id = wp_insert_attachment( $attachment, $upload[ 'file' ], $post_id );
2175                 wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $upload['file'] ) );
2176
2177                 return apply_filters( 'wp_handle_upload', array( 'file' => $name, 'url' => $upload[ 'url' ], 'type' => $type ) );
2178         }
2179
2180
2181         /* MovableType API functions
2182          * specs on http://www.movabletype.org/docs/mtmanual_programmatic.html
2183          */
2184
2185         /* mt.getRecentPostTitles ...returns recent posts' titles */
2186         function mt_getRecentPostTitles($args) {
2187
2188                 $this->escape($args);
2189
2190                 $blog_ID     = (int) $args[0];
2191                 $user_login  = $args[1];
2192                 $user_pass   = $args[2];
2193                 $num_posts   = (int) $args[3];
2194
2195                 if (!$this->login_pass_ok($user_login, $user_pass)) {
2196                         return $this->error;
2197                 }
2198
2199                 do_action('xmlrpc_call', 'mt.getRecentPostTitles');
2200
2201                 $posts_list = wp_get_recent_posts($num_posts);
2202
2203                 if (!$posts_list) {
2204                         $this->error = new IXR_Error(500, __('Either there are no posts, or something went wrong.'));
2205                         return $this->error;
2206                 }
2207
2208                 set_current_user( 0, $user_login );
2209
2210                 foreach ($posts_list as $entry) {
2211                         if( !current_user_can( 'edit_post', $entry['ID'] ) )
2212                                 continue;
2213
2214                         $post_date = mysql2date('Ymd\TH:i:s', $entry['post_date']);
2215                         $post_date_gmt = mysql2date('Ymd\TH:i:s', $entry['post_date_gmt']);
2216
2217                         $struct[] = array(
2218                                 'dateCreated' => new IXR_Date($post_date),
2219                                 'userid' => $entry['post_author'],
2220                                 'postid' => $entry['ID'],
2221                                 'title' => $entry['post_title'],
2222                                 'date_created_gmt' => new IXR_Date($post_date_gmt)
2223                         );
2224
2225                 }
2226
2227                 $recent_posts = array();
2228                 for ($j=0; $j<count($struct); $j++) {
2229                         array_push($recent_posts, $struct[$j]);
2230                 }
2231
2232                 return $recent_posts;
2233         }
2234
2235
2236         /* mt.getCategoryList ...returns the list of categories on a given blog */
2237         function mt_getCategoryList($args) {
2238
2239                 $this->escape($args);
2240
2241                 $blog_ID     = (int) $args[0];
2242                 $user_login  = $args[1];
2243                 $user_pass   = $args[2];
2244
2245                 if (!$this->login_pass_ok($user_login, $user_pass)) {
2246                         return $this->error;
2247                 }
2248
2249                 set_current_user( 0, $user_login );
2250                 if( !current_user_can( 'edit_posts' ) )
2251                         return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this blog in order to view categories.' ) );
2252
2253                 do_action('xmlrpc_call', 'mt.getCategoryList');
2254
2255                 $categories_struct = array();
2256
2257                 if ( $cats = get_categories('hide_empty=0&hierarchical=0') ) {
2258                         foreach ($cats as $cat) {
2259                                 $struct['categoryId'] = $cat->term_id;
2260                                 $struct['categoryName'] = $cat->name;
2261
2262                                 $categories_struct[] = $struct;
2263                         }
2264                 }
2265
2266                 return $categories_struct;
2267         }
2268
2269
2270         /* mt.getPostCategories ...returns a post's categories */
2271         function mt_getPostCategories($args) {
2272
2273                 $this->escape($args);
2274
2275                 $post_ID     = (int) $args[0];
2276                 $user_login  = $args[1];
2277                 $user_pass   = $args[2];
2278
2279                 if (!$this->login_pass_ok($user_login, $user_pass)) {
2280                         return $this->error;
2281                 }
2282
2283                 set_current_user( 0, $user_login );
2284                 if( !current_user_can( 'edit_post', $post_ID ) )
2285                         return new IXR_Error( 401, __( 'Sorry, you can not edit this post.' ) );
2286
2287                 do_action('xmlrpc_call', 'mt.getPostCategories');
2288
2289                 $categories = array();
2290                 $catids = wp_get_post_categories(intval($post_ID));
2291                 // first listed category will be the primary category
2292                 $isPrimary = true;
2293                 foreach($catids as $catid) {
2294                         $categories[] = array(
2295                                 'categoryName' => get_cat_name($catid),
2296                                 'categoryId' => (string) $catid,
2297                                 'isPrimary' => $isPrimary
2298                         );
2299                         $isPrimary = false;
2300                 }
2301
2302                 return $categories;
2303         }
2304
2305
2306         /* mt.setPostCategories ...sets a post's categories */
2307         function mt_setPostCategories($args) {
2308
2309                 $this->escape($args);
2310
2311                 $post_ID     = (int) $args[0];
2312                 $user_login  = $args[1];
2313                 $user_pass   = $args[2];
2314                 $categories  = $args[3];
2315
2316                 if (!$this->login_pass_ok($user_login, $user_pass)) {
2317                         return $this->error;
2318                 }
2319
2320                 do_action('xmlrpc_call', 'mt.setPostCategories');
2321
2322                 set_current_user(0, $user_login);
2323                 if ( !current_user_can('edit_post', $post_ID) )
2324                         return new IXR_Error(401, __('Sorry, you can not edit this post.'));
2325
2326                 foreach($categories as $cat) {
2327                         $catids[] = $cat['categoryId'];
2328                 }
2329
2330                 wp_set_post_categories($post_ID, $catids);
2331
2332                 return true;
2333         }
2334
2335
2336         /* mt.supportedMethods ...returns an array of methods supported by this server */
2337         function mt_supportedMethods($args) {
2338
2339                 do_action('xmlrpc_call', 'mt.supportedMethods');
2340
2341                 $supported_methods = array();
2342                 foreach($this->methods as $key=>$value) {
2343                         $supported_methods[] = $key;
2344                 }
2345
2346                 return $supported_methods;
2347         }
2348
2349
2350         /* mt.supportedTextFilters ...returns an empty array because we don't
2351                  support per-post text filters yet */
2352         function mt_supportedTextFilters($args) {
2353                 do_action('xmlrpc_call', 'mt.supportedTextFilters');
2354                 return apply_filters('xmlrpc_text_filters', array());
2355         }
2356
2357
2358         /* mt.getTrackbackPings ...returns trackbacks sent to a given post */
2359         function mt_getTrackbackPings($args) {
2360
2361                 global $wpdb;
2362
2363                 $post_ID = intval($args);
2364
2365                 do_action('xmlrpc_call', 'mt.getTrackbackPings');
2366
2367                 $actual_post = wp_get_single_post($post_ID, ARRAY_A);
2368
2369                 if (!$actual_post) {
2370                         return new IXR_Error(404, __('Sorry, no such post.'));
2371                 }
2372
2373                 $comments = $wpdb->get_results( $wpdb->prepare("SELECT comment_author_url, comment_content, comment_author_IP, comment_type FROM $wpdb->comments WHERE comment_post_ID = %d", $post_ID) );
2374
2375                 if (!$comments) {
2376                         return array();
2377                 }
2378
2379                 $trackback_pings = array();
2380                 foreach($comments as $comment) {
2381                         if ( 'trackback' == $comment->comment_type ) {
2382                                 $content = $comment->comment_content;
2383                                 $title = substr($content, 8, (strpos($content, '</strong>') - 8));
2384                                 $trackback_pings[] = array(
2385                                         'pingTitle' => $title,
2386                                         'pingURL'   => $comment->comment_author_url,
2387                                         'pingIP'    => $comment->comment_author_IP
2388                                 );
2389                 }
2390                 }
2391
2392                 return $trackback_pings;
2393         }
2394
2395
2396         /* mt.publishPost ...sets a post's publish status to 'publish' */
2397         function mt_publishPost($args) {
2398
2399                 $this->escape($args);
2400
2401                 $post_ID     = (int) $args[0];
2402                 $user_login  = $args[1];
2403                 $user_pass   = $args[2];
2404
2405                 if (!$this->login_pass_ok($user_login, $user_pass)) {
2406                         return $this->error;
2407                 }
2408
2409                 do_action('xmlrpc_call', 'mt.publishPost');
2410
2411                 set_current_user(0, $user_login);
2412                 if ( !current_user_can('edit_post', $post_ID) )
2413                         return new IXR_Error(401, __('Sorry, you can not edit this post.'));
2414
2415                 $postdata = wp_get_single_post($post_ID,ARRAY_A);
2416
2417                 $postdata['post_status'] = 'publish';
2418
2419                 // retain old cats
2420                 $cats = wp_get_post_categories($post_ID);
2421                 $postdata['post_category'] = $cats;
2422                 $this->escape($postdata);
2423
2424                 $result = wp_update_post($postdata);
2425
2426                 return $result;
2427         }
2428
2429
2430
2431         /* PingBack functions
2432          * specs on www.hixie.ch/specs/pingback/pingback
2433          */
2434
2435         /* pingback.ping gets a pingback and registers it */
2436         function pingback_ping($args) {
2437                 global $wpdb;
2438
2439                 do_action('xmlrpc_call', 'pingback.ping');
2440
2441                 $this->escape($args);
2442
2443                 $pagelinkedfrom = $args[0];
2444                 $pagelinkedto   = $args[1];
2445
2446                 $title = '';
2447
2448                 $pagelinkedfrom = str_replace('&amp;', '&', $pagelinkedfrom);
2449                 $pagelinkedto = str_replace('&amp;', '&', $pagelinkedto);
2450                 $pagelinkedto = str_replace('&', '&amp;', $pagelinkedto);
2451
2452                 // Check if the page linked to is in our site
2453                 $pos1 = strpos($pagelinkedto, str_replace(array('http://www.','http://','https://www.','https://'), '', get_option('home')));
2454                 if( !$pos1 )
2455                         return new IXR_Error(0, __('Is there no link to us?'));
2456
2457                 // let's find which post is linked to
2458                 // FIXME: does url_to_postid() cover all these cases already?
2459                 //        if so, then let's use it and drop the old code.
2460                 $urltest = parse_url($pagelinkedto);
2461                 if ($post_ID = url_to_postid($pagelinkedto)) {
2462                         $way = 'url_to_postid()';
2463                 } elseif (preg_match('#p/[0-9]{1,}#', $urltest['path'], $match)) {
2464                         // the path defines the post_ID (archives/p/XXXX)
2465                         $blah = explode('/', $match[0]);
2466                         $post_ID = (int) $blah[1];
2467                         $way = 'from the path';
2468                 } elseif (preg_match('#p=[0-9]{1,}#', $urltest['query'], $match)) {
2469                         // the querystring defines the post_ID (?p=XXXX)
2470                         $blah = explode('=', $match[0]);
2471                         $post_ID = (int) $blah[1];
2472                         $way = 'from the querystring';
2473                 } elseif (isset($urltest['fragment'])) {
2474                         // an #anchor is there, it's either...
2475                         if (intval($urltest['fragment'])) {
2476                                 // ...an integer #XXXX (simpliest case)
2477                                 $post_ID = (int) $urltest['fragment'];
2478                                 $way = 'from the fragment (numeric)';
2479                         } elseif (preg_match('/post-[0-9]+/',$urltest['fragment'])) {
2480                                 // ...a post id in the form 'post-###'
2481                                 $post_ID = preg_replace('/[^0-9]+/', '', $urltest['fragment']);
2482                                 $way = 'from the fragment (post-###)';
2483                         } elseif (is_string($urltest['fragment'])) {
2484                                 // ...or a string #title, a little more complicated
2485                                 $title = preg_replace('/[^a-z0-9]/i', '.', $urltest['fragment']);
2486                                 $sql = $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE post_title RLIKE %s", $title);
2487                                 if (! ($post_ID = $wpdb->get_var($sql)) ) {
2488                                         // returning unknown error '0' is better than die()ing
2489                                         return new IXR_Error(0, '');
2490                                 }
2491                                 $way = 'from the fragment (title)';
2492                         }
2493                 } else {
2494                         // TODO: Attempt to extract a post ID from the given URL
2495                         return new IXR_Error(33, __('The specified target URL cannot be used as a target. It either doesn\'t exist, or it is not a pingback-enabled resource.'));
2496                 }
2497                 $post_ID = (int) $post_ID;
2498
2499
2500                 logIO("O","(PB) URL='$pagelinkedto' ID='$post_ID' Found='$way'");
2501
2502                 $post = get_post($post_ID);
2503
2504                 if ( !$post ) // Post_ID not found
2505                         return new IXR_Error(33, __('The specified target URL cannot be used as a target. It either doesn\'t exist, or it is not a pingback-enabled resource.'));
2506
2507                 if ( $post_ID == url_to_postid($pagelinkedfrom) )
2508                         return new IXR_Error(0, __('The source URL and the target URL cannot both point to the same resource.'));
2509
2510                 // Check if pings are on
2511                 if ( 'closed' == $post->ping_status )
2512                         return new IXR_Error(33, __('The specified target URL cannot be used as a target. It either doesn\'t exist, or it is not a pingback-enabled resource.'));
2513
2514                 // Let's check that the remote site didn't already pingback this entry
2515                 $wpdb->get_results( $wpdb->prepare("SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_author_url = %s", $post_ID, $pagelinkedfrom) );
2516
2517                 if ( $wpdb->num_rows ) // We already have a Pingback from this URL
2518                         return new IXR_Error(48, __('The pingback has already been registered.'));
2519
2520                 // very stupid, but gives time to the 'from' server to publish !
2521                 sleep(1);
2522
2523                 // Let's check the remote site
2524                 $linea = wp_remote_fopen( $pagelinkedfrom );
2525                 if ( !$linea )
2526                         return new IXR_Error(16, __('The source URL does not exist.'));
2527
2528                 $linea = apply_filters('pre_remote_source', $linea, $pagelinkedto);
2529
2530                 // Work around bug in strip_tags():
2531                 $linea = str_replace('<!DOC', '<DOC', $linea);
2532                 $linea = preg_replace( '/[\s\r\n\t]+/', ' ', $linea ); // normalize spaces
2533                 $linea = preg_replace( "/ <(h1|h2|h3|h4|h5|h6|p|th|td|li|dt|dd|pre|caption|input|textarea|button|body)[^>]*>/", "\n\n", $linea );
2534
2535                 preg_match('|<title>([^<]*?)</title>|is', $linea, $matchtitle);
2536                 $title = $matchtitle[1];
2537                 if ( empty( $title ) )
2538                         return new IXR_Error(32, __('We cannot find a title on that page.'));
2539
2540                 $linea = strip_tags( $linea, '<a>' ); // just keep the tag we need
2541
2542                 $p = explode( "\n\n", $linea );
2543
2544                 $preg_target = preg_quote($pagelinkedto);
2545
2546                 foreach ( $p as $para ) {
2547                         if ( strpos($para, $pagelinkedto) !== false ) { // it exists, but is it a link?
2548                                 preg_match("|<a[^>]+?".$preg_target."[^>]*>([^>]+?)</a>|", $para, $context);
2549
2550                                 // If the URL isn't in a link context, keep looking
2551                                 if ( empty($context) )
2552                                         continue;
2553
2554                                 // We're going to use this fake tag to mark the context in a bit
2555                                 // the marker is needed in case the link text appears more than once in the paragraph
2556                                 $excerpt = preg_replace('|\</?wpcontext\>|', '', $para);
2557
2558                                 // prevent really long link text
2559                                 if ( strlen($context[1]) > 100 )
2560                                         $context[1] = substr($context[1], 0, 100) . '...';
2561
2562                                 $marker = '<wpcontext>'.$context[1].'</wpcontext>';    // set up our marker
2563                                 $excerpt= str_replace($context[0], $marker, $excerpt); // swap out the link for our marker
2564                                 $excerpt = strip_tags($excerpt, '<wpcontext>');        // strip all tags but our context marker
2565                                 $excerpt = trim($excerpt);
2566                                 $preg_marker = preg_quote($marker);
2567                                 $excerpt = preg_replace("|.*?\s(.{0,100}$preg_marker.{0,100})\s.*|s", '$1', $excerpt);
2568                                 $excerpt = strip_tags($excerpt); // YES, again, to remove the marker wrapper
2569                                 break;
2570                         }
2571                 }
2572
2573                 if ( empty($context) ) // Link to target not found
2574                         return new IXR_Error(17, __('The source URL does not contain a link to the target URL, and so cannot be used as a source.'));
2575
2576                 $pagelinkedfrom = str_replace('&', '&amp;', $pagelinkedfrom);
2577
2578                 $context = '[...] ' . wp_specialchars( $excerpt ) . ' [...]';
2579                 $pagelinkedfrom = $wpdb->escape( $pagelinkedfrom );
2580
2581                 $comment_post_ID = (int) $post_ID;
2582                 $comment_author = $title;
2583                 $this->escape($comment_author);
2584                 $comment_author_url = $pagelinkedfrom;
2585                 $comment_content = $context;
2586                 $this->escape($comment_content);
2587                 $comment_type = 'pingback';
2588
2589                 $commentdata = compact('comment_post_ID', 'comment_author', 'comment_author_url', 'comment_content', 'comment_type');
2590
2591                 $comment_ID = wp_new_comment($commentdata);
2592                 do_action('pingback_post', $comment_ID);
2593
2594                 return sprintf(__('Pingback from %1$s to %2$s registered. Keep the web talking! :-)'), $pagelinkedfrom, $pagelinkedto);
2595         }
2596
2597
2598         /* pingback.extensions.getPingbacks returns an array of URLs
2599         that pingbacked the given URL
2600         specs on http://www.aquarionics.com/misc/archives/blogite/0198.html */
2601         function pingback_extensions_getPingbacks($args) {
2602
2603                 global $wpdb;
2604
2605                 do_action('xmlrpc_call', 'pingback.extensions.getPingsbacks');
2606
2607                 $this->escape($args);
2608
2609                 $url = $args;
2610
2611                 $post_ID = url_to_postid($url);
2612                 if (!$post_ID) {
2613                         // We aren't sure that the resource is available and/or pingback enabled
2614                         return new IXR_Error(33, __('The specified target URL cannot be used as a target. It either doesn\'t exist, or it is not a pingback-enabled resource.'));
2615                 }
2616
2617                 $actual_post = wp_get_single_post($post_ID, ARRAY_A);
2618
2619                 if (!$actual_post) {
2620                         // No such post = resource not found
2621                         return new IXR_Error(32, __('The specified target URL does not exist.'));
2622                 }
2623
2624                 $comments = $wpdb->get_results( $wpdb->prepare("SELECT comment_author_url, comment_content, comment_author_IP, comment_type FROM $wpdb->comments WHERE comment_post_ID = %d", $post_ID) );
2625
2626                 if (!$comments) {
2627                         return array();
2628                 }
2629
2630                 $pingbacks = array();
2631                 foreach($comments as $comment) {
2632                         if ( 'pingback' == $comment->comment_type )
2633                                 $pingbacks[] = $comment->comment_author_url;
2634                 }
2635
2636                 return $pingbacks;
2637         }
2638 }
2639
2640
2641 $wp_xmlrpc_server = new wp_xmlrpc_server();
2642
2643 ?>