Wordpress 3.0
[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', 'rpc') ?>" />
43       <api name="Movable Type" blogID="1" preferred="false" apiLink="<?php echo site_url('xmlrpc.php', 'rpc') ?>" />
44       <api name="MetaWeblog" blogID="1" preferred="false" apiLink="<?php echo site_url('xmlrpc.php', 'rpc') ?>" />
45       <api name="Blogger" blogID="1" preferred="false" apiLink="<?php echo site_url('xmlrpc.php', 'rpc') ?>" />
46       <api name="Atom" blogID="" preferred="false" apiLink="<?php echo apply_filters('atom_service_url', site_url('wp-app.php/service', 'rpc') ) ?>" />
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  * WordPress XMLRPC server implementation.
103  *
104  * Implements compatability for Blogger API, MetaWeblog API, MovableType, and
105  * pingback. Additional WordPress API for managing comments, pages, posts,
106  * options, etc.
107  *
108  * Since WordPress 2.6.0, WordPress XMLRPC server can be disabled in the
109  * administration panels.
110  *
111  * @package WordPress
112  * @subpackage Publishing
113  * @since 1.5.0
114  */
115 class wp_xmlrpc_server extends IXR_Server {
116
117         /**
118          * Register all of the XMLRPC methods that XMLRPC server understands.
119          *
120          * PHP4 constructor and sets up server and method property. Passes XMLRPC
121          * methods through the 'xmlrpc_methods' filter to allow plugins to extend
122          * or replace XMLRPC methods.
123          *
124          * @since 1.5.0
125          *
126          * @return wp_xmlrpc_server
127          */
128         function wp_xmlrpc_server() {
129                 $this->methods = array(
130                         // WordPress API
131                         'wp.getUsersBlogs'              => 'this:wp_getUsersBlogs',
132                         'wp.getPage'                    => 'this:wp_getPage',
133                         'wp.getPages'                   => 'this:wp_getPages',
134                         'wp.newPage'                    => 'this:wp_newPage',
135                         'wp.deletePage'                 => 'this:wp_deletePage',
136                         'wp.editPage'                   => 'this:wp_editPage',
137                         'wp.getPageList'                => 'this:wp_getPageList',
138                         'wp.getAuthors'                 => 'this:wp_getAuthors',
139                         'wp.getCategories'              => 'this:mw_getCategories',             // Alias
140                         'wp.getTags'                    => 'this:wp_getTags',
141                         'wp.newCategory'                => 'this:wp_newCategory',
142                         'wp.deleteCategory'             => 'this:wp_deleteCategory',
143                         'wp.suggestCategories'  => 'this:wp_suggestCategories',
144                         'wp.uploadFile'                 => 'this:mw_newMediaObject',    // Alias
145                         'wp.getCommentCount'    => 'this:wp_getCommentCount',
146                         'wp.getPostStatusList'  => 'this:wp_getPostStatusList',
147                         'wp.getPageStatusList'  => 'this:wp_getPageStatusList',
148                         'wp.getPageTemplates'   => 'this:wp_getPageTemplates',
149                         'wp.getOptions'                 => 'this:wp_getOptions',
150                         'wp.setOptions'                 => 'this:wp_setOptions',
151                         'wp.getComment'                 => 'this:wp_getComment',
152                         'wp.getComments'                => 'this:wp_getComments',
153                         'wp.deleteComment'              => 'this:wp_deleteComment',
154                         'wp.editComment'                => 'this:wp_editComment',
155                         'wp.newComment'                 => 'this:wp_newComment',
156                         'wp.getCommentStatusList' => 'this:wp_getCommentStatusList',
157
158                         // Blogger API
159                         'blogger.getUsersBlogs' => 'this:blogger_getUsersBlogs',
160                         'blogger.getUserInfo' => 'this:blogger_getUserInfo',
161                         'blogger.getPost' => 'this:blogger_getPost',
162                         'blogger.getRecentPosts' => 'this:blogger_getRecentPosts',
163                         'blogger.getTemplate' => 'this:blogger_getTemplate',
164                         'blogger.setTemplate' => 'this:blogger_setTemplate',
165                         'blogger.newPost' => 'this:blogger_newPost',
166                         'blogger.editPost' => 'this:blogger_editPost',
167                         'blogger.deletePost' => 'this:blogger_deletePost',
168
169                         // MetaWeblog API (with MT extensions to structs)
170                         'metaWeblog.newPost' => 'this:mw_newPost',
171                         'metaWeblog.editPost' => 'this:mw_editPost',
172                         'metaWeblog.getPost' => 'this:mw_getPost',
173                         'metaWeblog.getRecentPosts' => 'this:mw_getRecentPosts',
174                         'metaWeblog.getCategories' => 'this:mw_getCategories',
175                         'metaWeblog.newMediaObject' => 'this:mw_newMediaObject',
176
177                         // MetaWeblog API aliases for Blogger API
178                         // see http://www.xmlrpc.com/stories/storyReader$2460
179                         'metaWeblog.deletePost' => 'this:blogger_deletePost',
180                         'metaWeblog.getTemplate' => 'this:blogger_getTemplate',
181                         'metaWeblog.setTemplate' => 'this:blogger_setTemplate',
182                         'metaWeblog.getUsersBlogs' => 'this:blogger_getUsersBlogs',
183
184                         // MovableType API
185                         'mt.getCategoryList' => 'this:mt_getCategoryList',
186                         'mt.getRecentPostTitles' => 'this:mt_getRecentPostTitles',
187                         'mt.getPostCategories' => 'this:mt_getPostCategories',
188                         'mt.setPostCategories' => 'this:mt_setPostCategories',
189                         'mt.supportedMethods' => 'this:mt_supportedMethods',
190                         'mt.supportedTextFilters' => 'this:mt_supportedTextFilters',
191                         'mt.getTrackbackPings' => 'this:mt_getTrackbackPings',
192                         'mt.publishPost' => 'this:mt_publishPost',
193
194                         // PingBack
195                         'pingback.ping' => 'this:pingback_ping',
196                         'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks',
197
198                         'demo.sayHello' => 'this:sayHello',
199                         'demo.addTwoNumbers' => 'this:addTwoNumbers'
200                 );
201
202                 $this->initialise_blog_option_info( );
203                 $this->methods = apply_filters('xmlrpc_methods', $this->methods);
204         }
205
206         function serve_request() {
207                 $this->IXR_Server($this->methods);
208         }
209
210         /**
211          * Test XMLRPC API by saying, "Hello!" to client.
212          *
213          * @since 1.5.0
214          *
215          * @param array $args Method Parameters.
216          * @return string
217          */
218         function sayHello($args) {
219                 return 'Hello!';
220         }
221
222         /**
223          * Test XMLRPC API by adding two numbers for client.
224          *
225          * @since 1.5.0
226          *
227          * @param array $args Method Parameters.
228          * @return int
229          */
230         function addTwoNumbers($args) {
231                 $number1 = $args[0];
232                 $number2 = $args[1];
233                 return $number1 + $number2;
234         }
235
236         /**
237          * Check user's credentials.
238          *
239          * @since 1.5.0
240          *
241          * @param string $user_login User's username.
242          * @param string $user_pass User's password.
243          * @return bool Whether authentication passed.
244          * @deprecated use wp_xmlrpc_server::login
245          * @see wp_xmlrpc_server::login
246          */
247         function login_pass_ok($user_login, $user_pass) {
248                 if ( !get_option( 'enable_xmlrpc' ) ) {
249                         $this->error = new IXR_Error( 405, sprintf( __( 'XML-RPC services are disabled on this site.  An admin user can enable them at %s'),  admin_url('options-writing.php') ) );
250                         return false;
251                 }
252
253                 if (!user_pass_ok($user_login, $user_pass)) {
254                         $this->error = new IXR_Error(403, __('Bad login/pass combination.'));
255                         return false;
256                 }
257                 return true;
258         }
259
260         /**
261          * Log user in.
262          *
263          * @since 2.8
264          *
265          * @param string $username User's username.
266          * @param string $password User's password.
267          * @return mixed WP_User object if authentication passed, false otherwise
268          */
269         function login($username, $password) {
270                 if ( !get_option( 'enable_xmlrpc' ) ) {
271                         $this->error = new IXR_Error( 405, sprintf( __( 'XML-RPC services are disabled on this site.  An admin user can enable them at %s'),  admin_url('options-writing.php') ) );
272                         return false;
273                 }
274
275                 $user = wp_authenticate($username, $password);
276
277                 if (is_wp_error($user)) {
278                         $this->error = new IXR_Error(403, __('Bad login/pass combination.'));
279                         return false;
280                 }
281
282                 wp_set_current_user( $user->ID );
283                 return $user;
284         }
285
286         /**
287          * Sanitize string or array of strings for database.
288          *
289          * @since 1.5.2
290          *
291          * @param string|array $array Sanitize single string or array of strings.
292          * @return string|array Type matches $array and sanitized for the database.
293          */
294         function escape(&$array) {
295                 global $wpdb;
296
297                 if (!is_array($array)) {
298                         return($wpdb->escape($array));
299                 } else {
300                         foreach ( (array) $array as $k => $v ) {
301                                 if ( is_array($v) ) {
302                                         $this->escape($array[$k]);
303                                 } else if ( is_object($v) ) {
304                                         //skip
305                                 } else {
306                                         $array[$k] = $wpdb->escape($v);
307                                 }
308                         }
309                 }
310         }
311
312         /**
313          * Retrieve custom fields for post.
314          *
315          * @since 2.5.0
316          *
317          * @param int $post_id Post ID.
318          * @return array Custom fields, if exist.
319          */
320         function get_custom_fields($post_id) {
321                 $post_id = (int) $post_id;
322
323                 $custom_fields = array();
324
325                 foreach ( (array) has_meta($post_id) as $meta ) {
326                         // Don't expose protected fields.
327                         if ( strpos($meta['meta_key'], '_wp_') === 0 ) {
328                                 continue;
329                         }
330
331                         $custom_fields[] = array(
332                                 "id"    => $meta['meta_id'],
333                                 "key"   => $meta['meta_key'],
334                                 "value" => $meta['meta_value']
335                         );
336                 }
337
338                 return $custom_fields;
339         }
340
341         /**
342          * Set custom fields for post.
343          *
344          * @since 2.5.0
345          *
346          * @param int $post_id Post ID.
347          * @param array $fields Custom fields.
348          */
349         function set_custom_fields($post_id, $fields) {
350                 $post_id = (int) $post_id;
351
352                 foreach ( (array) $fields as $meta ) {
353                         if ( isset($meta['id']) ) {
354                                 $meta['id'] = (int) $meta['id'];
355
356                                 if ( isset($meta['key']) ) {
357                                         update_meta($meta['id'], $meta['key'], $meta['value']);
358                                 }
359                                 else {
360                                         delete_meta($meta['id']);
361                                 }
362                         }
363                         else {
364                                 $_POST['metakeyinput'] = $meta['key'];
365                                 $_POST['metavalue'] = $meta['value'];
366                                 add_meta($post_id);
367                         }
368                 }
369         }
370
371         /**
372          * Set up blog options property.
373          *
374          * Passes property through 'xmlrpc_blog_options' filter.
375          *
376          * @since 2.6.0
377          */
378         function initialise_blog_option_info( ) {
379                 global $wp_version;
380
381                 $this->blog_options = array(
382                         // Read only options
383                         'software_name'         => array(
384                                 'desc'                  => __( 'Software Name' ),
385                                 'readonly'              => true,
386                                 'value'                 => 'WordPress'
387                         ),
388                         'software_version'      => array(
389                                 'desc'                  => __( 'Software Version' ),
390                                 'readonly'              => true,
391                                 'value'                 => $wp_version
392                         ),
393                         'blog_url'                      => array(
394                                 'desc'                  => __( 'Site URL' ),
395                                 'readonly'              => true,
396                                 'option'                => 'siteurl'
397                         ),
398
399                         // Updatable options
400                         'time_zone'                     => array(
401                                 'desc'                  => __( 'Time Zone' ),
402                                 'readonly'              => false,
403                                 'option'                => 'gmt_offset'
404                         ),
405                         'blog_title'            => array(
406                                 'desc'                  => __( 'Site Title' ),
407                                 'readonly'              => false,
408                                 'option'                        => 'blogname'
409                         ),
410                         'blog_tagline'          => array(
411                                 'desc'                  => __( 'Site Tagline' ),
412                                 'readonly'              => false,
413                                 'option'                => 'blogdescription'
414                         ),
415                         'date_format'           => array(
416                                 'desc'                  => __( 'Date Format' ),
417                                 'readonly'              => false,
418                                 'option'                => 'date_format'
419                         ),
420                         'time_format'           => array(
421                                 'desc'                  => __( 'Time Format' ),
422                                 'readonly'              => false,
423                                 'option'                => 'time_format'
424                         ),
425                         'users_can_register'    => array(
426                                 'desc'                  => __( 'Allow new users to sign up' ),
427                                 'readonly'              => false,
428                                 'option'                => 'users_can_register'
429                         )
430                 );
431
432                 $this->blog_options = apply_filters( 'xmlrpc_blog_options', $this->blog_options );
433         }
434
435         /**
436          * Retrieve the blogs of the user.
437          *
438          * @since 2.6.0
439          *
440          * @param array $args Method parameters.
441          * @return array
442          */
443         function wp_getUsersBlogs( $args ) {
444                 global $current_site;
445                 // If this isn't on WPMU then just use blogger_getUsersBlogs
446                 if ( !is_multisite() ) {
447                         array_unshift( $args, 1 );
448                         return $this->blogger_getUsersBlogs( $args );
449                 }
450
451                 $this->escape( $args );
452
453                 $username = $args[0];
454                 $password = $args[1];
455
456                 if ( !$user = $this->login($username, $password) )
457                         return $this->error;
458
459
460                 do_action( 'xmlrpc_call', 'wp.getUsersBlogs' );
461
462                 $blogs = (array) get_blogs_of_user( $user->ID );
463                 $struct = array( );
464
465                 foreach ( $blogs as $blog ) {
466                         // Don't include blogs that aren't hosted at this site
467                         if ( $blog->site_id != $current_site->id )
468                                 continue;
469
470                         $blog_id = $blog->userblog_id;
471                         switch_to_blog($blog_id);
472                         $is_admin = current_user_can('manage_options');
473
474                         $struct[] = array(
475                                 'isAdmin'               => $is_admin,
476                                 'url'                   => get_option( 'home' ) . '/',
477                                 'blogid'                => $blog_id,
478                                 'blogName'              => get_option( 'blogname' ),
479                                 'xmlrpc'                => site_url( 'xmlrpc.php' )
480                         );
481
482                         restore_current_blog( );
483                 }
484
485                 return $struct;
486         }
487
488         /**
489          * Retrieve page.
490          *
491          * @since 2.2.0
492          *
493          * @param array $args Method parameters.
494          * @return array
495          */
496         function wp_getPage($args) {
497                 $this->escape($args);
498
499                 $blog_id        = (int) $args[0];
500                 $page_id        = (int) $args[1];
501                 $username       = $args[2];
502                 $password       = $args[3];
503
504                 if ( !$user = $this->login($username, $password) ) {
505                         return $this->error;
506                 }
507
508                 if ( !current_user_can( 'edit_page', $page_id ) )
509                         return new IXR_Error( 401, __( 'Sorry, you cannot edit this page.' ) );
510
511                 do_action('xmlrpc_call', 'wp.getPage');
512
513                 // Lookup page info.
514                 $page = get_page($page_id);
515
516                 // If we found the page then format the data.
517                 if ( $page->ID && ($page->post_type == "page") ) {
518                         // Get all of the page content and link.
519                         $full_page = get_extended($page->post_content);
520                         $link = post_permalink($page->ID);
521
522                         // Get info the page parent if there is one.
523                         $parent_title = "";
524                         if ( !empty($page->post_parent) ) {
525                                 $parent = get_page($page->post_parent);
526                                 $parent_title = $parent->post_title;
527                         }
528
529                         // Determine comment and ping settings.
530                         $allow_comments = comments_open($page->ID) ? 1 : 0;
531                         $allow_pings = pings_open($page->ID) ? 1 : 0;
532
533                         // Format page date.
534                         $page_date = mysql2date("Ymd\TH:i:s", $page->post_date, false);
535                         $page_date_gmt = mysql2date("Ymd\TH:i:s", $page->post_date_gmt, false);
536
537                         // For drafts use the GMT version of the date
538                         if ( $page->post_status == 'draft' )
539                                 $page_date_gmt = get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $page->post_date ), 'Ymd\TH:i:s' );
540
541                         // Pull the categories info together.
542                         $categories = array();
543                         foreach ( wp_get_post_categories($page->ID) as $cat_id ) {
544                                 $categories[] = get_cat_name($cat_id);
545                         }
546
547                         // Get the author info.
548                         $author = get_userdata($page->post_author);
549
550                         $page_template = get_post_meta( $page->ID, '_wp_page_template', true );
551                         if ( empty( $page_template ) )
552                                 $page_template = 'default';
553
554                         $page_struct = array(
555                                 "dateCreated"                   => new IXR_Date($page_date),
556                                 "userid"                                => $page->post_author,
557                                 "page_id"                               => $page->ID,
558                                 "page_status"                   => $page->post_status,
559                                 "description"                   => $full_page["main"],
560                                 "title"                                 => $page->post_title,
561                                 "link"                                  => $link,
562                                 "permaLink"                             => $link,
563                                 "categories"                    => $categories,
564                                 "excerpt"                               => $page->post_excerpt,
565                                 "text_more"                             => $full_page["extended"],
566                                 "mt_allow_comments"             => $allow_comments,
567                                 "mt_allow_pings"                => $allow_pings,
568                                 "wp_slug"                               => $page->post_name,
569                                 "wp_password"                   => $page->post_password,
570                                 "wp_author"                             => $author->display_name,
571                                 "wp_page_parent_id"             => $page->post_parent,
572                                 "wp_page_parent_title"  => $parent_title,
573                                 "wp_page_order"                 => $page->menu_order,
574                                 "wp_author_id"                  => $author->ID,
575                                 "wp_author_display_name"        => $author->display_name,
576                                 "date_created_gmt"              => new IXR_Date($page_date_gmt),
577                                 "custom_fields"                 => $this->get_custom_fields($page_id),
578                                 "wp_page_template"              => $page_template
579                         );
580
581                         return($page_struct);
582                 }
583                 // If the page doesn't exist indicate that.
584                 else {
585                         return(new IXR_Error(404, __("Sorry, no such page.")));
586                 }
587         }
588
589         /**
590          * Retrieve Pages.
591          *
592          * @since 2.2.0
593          *
594          * @param array $args Method parameters.
595          * @return array
596          */
597         function wp_getPages($args) {
598                 $this->escape($args);
599
600                 $blog_id        = (int) $args[0];
601                 $username       = $args[1];
602                 $password       = $args[2];
603                 $num_pages      = isset($args[3]) ? (int) $args[3] : 10;
604
605                 if ( !$user = $this->login($username, $password) )
606                         return $this->error;
607
608                 if ( !current_user_can( 'edit_pages' ) )
609                         return new IXR_Error( 401, __( 'Sorry, you cannot edit pages.' ) );
610
611                 do_action('xmlrpc_call', 'wp.getPages');
612
613                 $pages = get_posts( array('post_type' => 'page', 'post_status' => 'any', 'numberposts' => $num_pages) );
614                 $num_pages = count($pages);
615
616                 // If we have pages, put together their info.
617                 if ( $num_pages >= 1 ) {
618                         $pages_struct = array();
619
620                         for ( $i = 0; $i < $num_pages; $i++ ) {
621                                 $page = wp_xmlrpc_server::wp_getPage(array(
622                                         $blog_id, $pages[$i]->ID, $username, $password
623                                 ));
624                                 $pages_struct[] = $page;
625                         }
626
627                         return($pages_struct);
628                 }
629                 // If no pages were found return an error.
630                 else {
631                         return(array());
632                 }
633         }
634
635         /**
636          * Create new page.
637          *
638          * @since 2.2.0
639          *
640          * @param array $args Method parameters.
641          * @return unknown
642          */
643         function wp_newPage($args) {
644                 // Items not escaped here will be escaped in newPost.
645                 $username       = $this->escape($args[1]);
646                 $password       = $this->escape($args[2]);
647                 $page           = $args[3];
648                 $publish        = $args[4];
649
650                 if ( !$user = $this->login($username, $password) )
651                         return $this->error;
652
653                 do_action('xmlrpc_call', 'wp.newPage');
654
655                 // Make sure the user is allowed to add new pages.
656                 if ( !current_user_can("publish_pages") )
657                         return(new IXR_Error(401, __("Sorry, you cannot add new pages.")));
658
659                 // Mark this as content for a page.
660                 $args[3]["post_type"] = "page";
661
662                 // Let mw_newPost do all of the heavy lifting.
663                 return($this->mw_newPost($args));
664         }
665
666         /**
667          * Delete page.
668          *
669          * @since 2.2.0
670          *
671          * @param array $args Method parameters.
672          * @return bool True, if success.
673          */
674         function wp_deletePage($args) {
675                 $this->escape($args);
676
677                 $blog_id        = (int) $args[0];
678                 $username       = $args[1];
679                 $password       = $args[2];
680                 $page_id        = (int) $args[3];
681
682                 if ( !$user = $this->login($username, $password) )
683                         return $this->error;
684
685                 do_action('xmlrpc_call', 'wp.deletePage');
686
687                 // Get the current page based on the page_id and
688                 // make sure it is a page and not a post.
689                 $actual_page = wp_get_single_post($page_id, ARRAY_A);
690                 if ( !$actual_page || ($actual_page["post_type"] != "page") )
691                         return(new IXR_Error(404, __("Sorry, no such page.")));
692
693                 // Make sure the user can delete pages.
694                 if ( !current_user_can("delete_page", $page_id) )
695                         return(new IXR_Error(401, __("Sorry, you do not have the right to delete this page.")));
696
697                 // Attempt to delete the page.
698                 $result = wp_delete_post($page_id);
699                 if ( !$result )
700                         return(new IXR_Error(500, __("Failed to delete the page.")));
701
702                 return(true);
703         }
704
705         /**
706          * Edit page.
707          *
708          * @since 2.2.0
709          *
710          * @param array $args Method parameters.
711          * @return unknown
712          */
713         function wp_editPage($args) {
714                 // Items not escaped here will be escaped in editPost.
715                 $blog_id        = (int) $args[0];
716                 $page_id        = (int) $this->escape($args[1]);
717                 $username       = $this->escape($args[2]);
718                 $password       = $this->escape($args[3]);
719                 $content        = $args[4];
720                 $publish        = $args[5];
721
722                 if ( !$user = $this->login($username, $password) )
723                         return $this->error;
724
725                 do_action('xmlrpc_call', 'wp.editPage');
726
727                 // Get the page data and make sure it is a page.
728                 $actual_page = wp_get_single_post($page_id, ARRAY_A);
729                 if ( !$actual_page || ($actual_page["post_type"] != "page") )
730                         return(new IXR_Error(404, __("Sorry, no such page.")));
731
732                 // Make sure the user is allowed to edit pages.
733                 if ( !current_user_can("edit_page", $page_id) )
734                         return(new IXR_Error(401, __("Sorry, you do not have the right to edit this page.")));
735
736                 // Mark this as content for a page.
737                 $content["post_type"] = "page";
738
739                 // Arrange args in the way mw_editPost understands.
740                 $args = array(
741                         $page_id,
742                         $username,
743                         $password,
744                         $content,
745                         $publish
746                 );
747
748                 // Let mw_editPost do all of the heavy lifting.
749                 return($this->mw_editPost($args));
750         }
751
752         /**
753          * Retrieve page list.
754          *
755          * @since 2.2.0
756          *
757          * @param array $args Method parameters.
758          * @return unknown
759          */
760         function wp_getPageList($args) {
761                 global $wpdb;
762
763                 $this->escape($args);
764
765                 $blog_id                                = (int) $args[0];
766                 $username                               = $args[1];
767                 $password                               = $args[2];
768
769                 if ( !$user = $this->login($username, $password) )
770                         return $this->error;
771
772                 if ( !current_user_can( 'edit_pages' ) )
773                         return new IXR_Error( 401, __( 'Sorry, you cannot edit pages.' ) );
774
775                 do_action('xmlrpc_call', 'wp.getPageList');
776
777                 // Get list of pages ids and titles
778                 $page_list = $wpdb->get_results("
779                         SELECT ID page_id,
780                                 post_title page_title,
781                                 post_parent page_parent_id,
782                                 post_date_gmt,
783                                 post_date,
784                                 post_status
785                         FROM {$wpdb->posts}
786                         WHERE post_type = 'page'
787                         ORDER BY ID
788                 ");
789
790                 // The date needs to be formated properly.
791                 $num_pages = count($page_list);
792                 for ( $i = 0; $i < $num_pages; $i++ ) {
793                         $post_date = mysql2date("Ymd\TH:i:s", $page_list[$i]->post_date, false);
794                         $post_date_gmt = mysql2date("Ymd\TH:i:s", $page_list[$i]->post_date_gmt, false);
795
796                         $page_list[$i]->dateCreated = new IXR_Date($post_date);
797                         $page_list[$i]->date_created_gmt = new IXR_Date($post_date_gmt);
798
799                         // For drafts use the GMT version of the date
800                         if ( $page_list[$i]->post_status == 'draft' ) {
801                                 $page_list[$i]->date_created_gmt = get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $page_list[$i]->post_date ), 'Ymd\TH:i:s' );
802                                 $page_list[$i]->date_created_gmt = new IXR_Date( $page_list[$i]->date_created_gmt );
803                         }
804
805                         unset($page_list[$i]->post_date_gmt);
806                         unset($page_list[$i]->post_date);
807                         unset($page_list[$i]->post_status);
808                 }
809
810                 return($page_list);
811         }
812
813         /**
814          * Retrieve authors list.
815          *
816          * @since 2.2.0
817          *
818          * @param array $args Method parameters.
819          * @return array
820          */
821         function wp_getAuthors($args) {
822
823                 $this->escape($args);
824
825                 $blog_id        = (int) $args[0];
826                 $username       = $args[1];
827                 $password       = $args[2];
828
829                 if ( !$user = $this->login($username, $password) )
830                         return $this->error;
831
832                 if ( !current_user_can("edit_posts") )
833                         return(new IXR_Error(401, __("Sorry, you cannot edit posts on this site.")));
834
835                 do_action('xmlrpc_call', 'wp.getAuthors');
836
837                 $authors = array();
838                 foreach ( (array) get_users_of_blog() as $row ) {
839                         $authors[] = array(
840                                 "user_id"       => $row->user_id,
841                                 "user_login"    => $row->user_login,
842                                 "display_name"  => $row->display_name
843                         );
844                 }
845
846                 return($authors);
847         }
848
849         /**
850          * Get list of all tags
851          *
852          * @since 2.7
853          *
854          * @param array $args Method parameters.
855          * @return array
856          */
857         function wp_getTags( $args ) {
858                 $this->escape( $args );
859
860                 $blog_id                = (int) $args[0];
861                 $username               = $args[1];
862                 $password               = $args[2];
863
864                 if ( !$user = $this->login($username, $password) )
865                         return $this->error;
866
867                 if ( !current_user_can( 'edit_posts' ) )
868                         return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view tags.' ) );
869
870                 do_action( 'xmlrpc_call', 'wp.getKeywords' );
871
872                 $tags = array( );
873
874                 if ( $all_tags = get_tags() ) {
875                         foreach( (array) $all_tags as $tag ) {
876                                 $struct['tag_id']                       = $tag->term_id;
877                                 $struct['name']                         = $tag->name;
878                                 $struct['count']                        = $tag->count;
879                                 $struct['slug']                         = $tag->slug;
880                                 $struct['html_url']                     = esc_html( get_tag_link( $tag->term_id ) );
881                                 $struct['rss_url']                      = esc_html( get_tag_feed_link( $tag->term_id ) );
882
883                                 $tags[] = $struct;
884                         }
885                 }
886
887                 return $tags;
888         }
889
890         /**
891          * Create new category.
892          *
893          * @since 2.2.0
894          *
895          * @param array $args Method parameters.
896          * @return int Category ID.
897          */
898         function wp_newCategory($args) {
899                 $this->escape($args);
900
901                 $blog_id                                = (int) $args[0];
902                 $username                               = $args[1];
903                 $password                               = $args[2];
904                 $category                               = $args[3];
905
906                 if ( !$user = $this->login($username, $password) )
907                         return $this->error;
908
909                 do_action('xmlrpc_call', 'wp.newCategory');
910
911                 // Make sure the user is allowed to add a category.
912                 if ( !current_user_can("manage_categories") )
913                         return(new IXR_Error(401, __("Sorry, you do not have the right to add a category.")));
914
915                 // If no slug was provided make it empty so that
916                 // WordPress will generate one.
917                 if ( empty($category["slug"]) )
918                         $category["slug"] = "";
919
920                 // If no parent_id was provided make it empty
921                 // so that it will be a top level page (no parent).
922                 if ( !isset($category["parent_id"]) )
923                         $category["parent_id"] = "";
924
925                 // If no description was provided make it empty.
926                 if ( empty($category["description"]) )
927                         $category["description"] = "";
928
929                 $new_category = array(
930                         "cat_name"                              => $category["name"],
931                         "category_nicename"             => $category["slug"],
932                         "category_parent"               => $category["parent_id"],
933                         "category_description"  => $category["description"]
934                 );
935
936                 $cat_id = wp_insert_category($new_category);
937                 if ( !$cat_id )
938                         return(new IXR_Error(500, __("Sorry, the new category failed.")));
939
940                 return($cat_id);
941         }
942
943         /**
944          * Remove category.
945          *
946          * @since 2.5.0
947          *
948          * @param array $args Method parameters.
949          * @return mixed See {@link wp_delete_category()} for return info.
950          */
951         function wp_deleteCategory($args) {
952                 $this->escape($args);
953
954                 $blog_id                = (int) $args[0];
955                 $username               = $args[1];
956                 $password               = $args[2];
957                 $category_id    = (int) $args[3];
958
959                 if ( !$user = $this->login($username, $password) )
960                         return $this->error;
961
962                 do_action('xmlrpc_call', 'wp.deleteCategory');
963
964                 if ( !current_user_can("manage_categories") )
965                         return new IXR_Error( 401, __( "Sorry, you do not have the right to delete a category." ) );
966
967                 return wp_delete_category( $category_id );
968         }
969
970         /**
971          * Retrieve category list.
972          *
973          * @since 2.2.0
974          *
975          * @param array $args Method parameters.
976          * @return array
977          */
978         function wp_suggestCategories($args) {
979                 $this->escape($args);
980
981                 $blog_id                                = (int) $args[0];
982                 $username                               = $args[1];
983                 $password                               = $args[2];
984                 $category                               = $args[3];
985                 $max_results                    = (int) $args[4];
986
987                 if ( !$user = $this->login($username, $password) )
988                         return $this->error;
989
990                 if ( !current_user_can( 'edit_posts' ) )
991                         return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts to this site in order to view categories.' ) );
992
993                 do_action('xmlrpc_call', 'wp.suggestCategories');
994
995                 $category_suggestions = array();
996                 $args = array('get' => 'all', 'number' => $max_results, 'name__like' => $category);
997                 foreach ( (array) get_categories($args) as $cat ) {
998                         $category_suggestions[] = array(
999                                 "category_id"   => $cat->cat_ID,
1000                                 "category_name" => $cat->cat_name
1001                         );
1002                 }
1003
1004                 return($category_suggestions);
1005         }
1006
1007         /**
1008          * Retrieve comment.
1009          *
1010          * @since 2.7.0
1011          *
1012          * @param array $args Method parameters.
1013          * @return array
1014          */
1015         function wp_getComment($args) {
1016                 $this->escape($args);
1017
1018                 $blog_id        = (int) $args[0];
1019                 $username       = $args[1];
1020                 $password       = $args[2];
1021                 $comment_id     = (int) $args[3];
1022
1023                 if ( !$user = $this->login($username, $password) )
1024                         return $this->error;
1025
1026                 if ( !current_user_can( 'moderate_comments' ) )
1027                         return new IXR_Error( 403, __( 'You are not allowed to moderate comments on this site.' ) );
1028
1029                 do_action('xmlrpc_call', 'wp.getComment');
1030
1031                 if ( ! $comment = get_comment($comment_id) )
1032                         return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
1033
1034                 // Format page date.
1035                 $comment_date = mysql2date("Ymd\TH:i:s", $comment->comment_date, false);
1036                 $comment_date_gmt = mysql2date("Ymd\TH:i:s", $comment->comment_date_gmt, false);
1037
1038                 if ( '0' == $comment->comment_approved )
1039                         $comment_status = 'hold';
1040                 else if ( 'spam' == $comment->comment_approved )
1041                         $comment_status = 'spam';
1042                 else if ( '1' == $comment->comment_approved )
1043                         $comment_status = 'approve';
1044                 else
1045                         $comment_status = $comment->comment_approved;
1046
1047                 $link = get_comment_link($comment);
1048
1049                 $comment_struct = array(
1050                         "date_created_gmt"              => new IXR_Date($comment_date_gmt),
1051                         "user_id"                               => $comment->user_id,
1052                         "comment_id"                    => $comment->comment_ID,
1053                         "parent"                                => $comment->comment_parent,
1054                         "status"                                => $comment_status,
1055                         "content"                               => $comment->comment_content,
1056                         "link"                                  => $link,
1057                         "post_id"                               => $comment->comment_post_ID,
1058                         "post_title"                    => get_the_title($comment->comment_post_ID),
1059                         "author"                                => $comment->comment_author,
1060                         "author_url"                    => $comment->comment_author_url,
1061                         "author_email"                  => $comment->comment_author_email,
1062                         "author_ip"                             => $comment->comment_author_IP,
1063                         "type"                                  => $comment->comment_type,
1064                 );
1065
1066                 return $comment_struct;
1067         }
1068
1069         /**
1070          * Retrieve comments.
1071          *
1072          * @since 2.7.0
1073          *
1074          * @param array $args Method parameters.
1075          * @return array
1076          */
1077         function wp_getComments($args) {
1078                 $raw_args = $args;
1079                 $this->escape($args);
1080
1081                 $blog_id        = (int) $args[0];
1082                 $username       = $args[1];
1083                 $password       = $args[2];
1084                 $struct         = $args[3];
1085
1086                 if ( !$user = $this->login($username, $password) )
1087                         return $this->error;
1088
1089                 if ( !current_user_can( 'moderate_comments' ) )
1090                         return new IXR_Error( 401, __( 'Sorry, you cannot edit comments.' ) );
1091
1092                 do_action('xmlrpc_call', 'wp.getComments');
1093
1094                 if ( isset($struct['status']) )
1095                         $status = $struct['status'];
1096                 else
1097                         $status = '';
1098
1099                 $post_id = '';
1100                 if ( isset($struct['post_id']) )
1101                         $post_id = absint($struct['post_id']);
1102
1103                 $offset = 0;
1104                 if ( isset($struct['offset']) )
1105                         $offset = absint($struct['offset']);
1106
1107                 $number = 10;
1108                 if ( isset($struct['number']) )
1109                         $number = absint($struct['number']);
1110
1111                 $comments = get_comments( array('status' => $status, 'post_id' => $post_id, 'offset' => $offset, 'number' => $number ) );
1112                 $num_comments = count($comments);
1113
1114                 if ( ! $num_comments )
1115                         return array();
1116
1117                 $comments_struct = array();
1118
1119                 for ( $i = 0; $i < $num_comments; $i++ ) {
1120                         $comment = wp_xmlrpc_server::wp_getComment(array(
1121                                 $raw_args[0], $raw_args[1], $raw_args[2], $comments[$i]->comment_ID,
1122                         ));
1123                         $comments_struct[] = $comment;
1124                 }
1125
1126                 return $comments_struct;
1127         }
1128
1129         /**
1130          * Remove comment.
1131          *
1132          * @since 2.7.0
1133          *
1134          * @param array $args Method parameters.
1135          * @return mixed {@link wp_delete_comment()}
1136          */
1137         function wp_deleteComment($args) {
1138                 $this->escape($args);
1139
1140                 $blog_id        = (int) $args[0];
1141                 $username       = $args[1];
1142                 $password       = $args[2];
1143                 $comment_ID     = (int) $args[3];
1144
1145                 if ( !$user = $this->login($username, $password) )
1146                         return $this->error;
1147
1148                 if ( !current_user_can( 'moderate_comments' ) )
1149                         return new IXR_Error( 403, __( 'You are not allowed to moderate comments on this site.' ) );
1150
1151                 do_action('xmlrpc_call', 'wp.deleteComment');
1152
1153                 if ( ! get_comment($comment_ID) )
1154                         return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
1155
1156                 return wp_delete_comment($comment_ID);
1157         }
1158
1159         /**
1160          * Edit comment.
1161          *
1162          * @since 2.7.0
1163          *
1164          * @param array $args Method parameters.
1165          * @return bool True, on success.
1166          */
1167         function wp_editComment($args) {
1168                 $this->escape($args);
1169
1170                 $blog_id        = (int) $args[0];
1171                 $username       = $args[1];
1172                 $password       = $args[2];
1173                 $comment_ID     = (int) $args[3];
1174                 $content_struct = $args[4];
1175
1176                 if ( !$user = $this->login($username, $password) )
1177                         return $this->error;
1178
1179                 if ( !current_user_can( 'moderate_comments' ) )
1180                         return new IXR_Error( 403, __( 'You are not allowed to moderate comments on this site.' ) );
1181
1182                 do_action('xmlrpc_call', 'wp.editComment');
1183
1184                 if ( ! get_comment($comment_ID) )
1185                         return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
1186
1187                 if ( isset($content_struct['status']) ) {
1188                         $statuses = get_comment_statuses();
1189                         $statuses = array_keys($statuses);
1190
1191                         if ( ! in_array($content_struct['status'], $statuses) )
1192                                 return new IXR_Error( 401, __( 'Invalid comment status.' ) );
1193                         $comment_approved = $content_struct['status'];
1194                 }
1195
1196                 // Do some timestamp voodoo
1197                 if ( !empty( $content_struct['date_created_gmt'] ) ) {
1198                         $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
1199                         $comment_date = get_date_from_gmt(iso8601_to_datetime($dateCreated));
1200                         $comment_date_gmt = iso8601_to_datetime($dateCreated, GMT);
1201                 }
1202
1203                 if ( isset($content_struct['content']) )
1204                         $comment_content = $content_struct['content'];
1205
1206                 if ( isset($content_struct['author']) )
1207                         $comment_author = $content_struct['author'];
1208
1209                 if ( isset($content_struct['author_url']) )
1210                         $comment_author_url = $content_struct['author_url'];
1211
1212                 if ( isset($content_struct['author_email']) )
1213                         $comment_author_email = $content_struct['author_email'];
1214
1215                 // We've got all the data -- post it:
1216                 $comment = compact('comment_ID', 'comment_content', 'comment_approved', 'comment_date', 'comment_date_gmt', 'comment_author', 'comment_author_email', 'comment_author_url');
1217
1218                 $result = wp_update_comment($comment);
1219                 if ( is_wp_error( $result ) )
1220                         return new IXR_Error(500, $result->get_error_message());
1221
1222                 if ( !$result )
1223                         return new IXR_Error(500, __('Sorry, the comment could not be edited. Something wrong happened.'));
1224
1225                 return true;
1226         }
1227
1228         /**
1229          * Create new comment.
1230          *
1231          * @since 2.7.0
1232          *
1233          * @param array $args Method parameters.
1234          * @return mixed {@link wp_new_comment()}
1235          */
1236         function wp_newComment($args) {
1237                 global $wpdb;
1238
1239                 $this->escape($args);
1240
1241                 $blog_id        = (int) $args[0];
1242                 $username       = $args[1];
1243                 $password       = $args[2];
1244                 $post           = $args[3];
1245                 $content_struct = $args[4];
1246
1247                 $allow_anon = apply_filters('xmlrpc_allow_anonymous_comments', false);
1248
1249                 $user = $this->login($username, $password);
1250
1251                 if ( !$user ) {
1252                         $logged_in = false;
1253                         if ( $allow_anon && get_option('comment_registration') )
1254                                 return new IXR_Error( 403, __( 'You must be registered to comment' ) );
1255                         else if ( !$allow_anon )
1256                                 return $this->error;
1257                 } else {
1258                         $logged_in = true;
1259                 }
1260
1261                 if ( is_numeric($post) )
1262                         $post_id = absint($post);
1263                 else
1264                         $post_id = url_to_postid($post);
1265
1266                 if ( ! $post_id )
1267                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1268
1269                 if ( ! get_post($post_id) )
1270                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1271
1272                 $comment['comment_post_ID'] = $post_id;
1273
1274                 if ( $logged_in ) {
1275                         $comment['comment_author'] = $wpdb->escape( $user->display_name );
1276                         $comment['comment_author_email'] = $wpdb->escape( $user->user_email );
1277                         $comment['comment_author_url'] = $wpdb->escape( $user->user_url );
1278                         $comment['user_ID'] = $user->ID;
1279                 } else {
1280                         $comment['comment_author'] = '';
1281                         if ( isset($content_struct['author']) )
1282                                 $comment['comment_author'] = $content_struct['author'];
1283
1284                         $comment['comment_author_email'] = '';
1285                         if ( isset($content_struct['author_email']) )
1286                                 $comment['comment_author_email'] = $content_struct['author_email'];
1287
1288                         $comment['comment_author_url'] = '';
1289                         if ( isset($content_struct['author_url']) )
1290                                 $comment['comment_author_url'] = $content_struct['author_url'];
1291
1292                         $comment['user_ID'] = 0;
1293
1294                         if ( get_option('require_name_email') ) {
1295                                 if ( 6 > strlen($comment['comment_author_email']) || '' == $comment['comment_author'] )
1296                                         return new IXR_Error( 403, __( 'Comment author name and email are required' ) );
1297                                 elseif ( !is_email($comment['comment_author_email']) )
1298                                         return new IXR_Error( 403, __( 'A valid email address is required' ) );
1299                         }
1300                 }
1301
1302                 $comment['comment_parent'] = isset($content_struct['comment_parent']) ? absint($content_struct['comment_parent']) : 0;
1303
1304                 $comment['comment_content'] = $content_struct['content'];
1305
1306                 do_action('xmlrpc_call', 'wp.newComment');
1307
1308                 return wp_new_comment($comment);
1309         }
1310
1311         /**
1312          * Retrieve all of the comment status.
1313          *
1314          * @since 2.7.0
1315          *
1316          * @param array $args Method parameters.
1317          * @return array
1318          */
1319         function wp_getCommentStatusList($args) {
1320                 $this->escape( $args );
1321
1322                 $blog_id        = (int) $args[0];
1323                 $username       = $args[1];
1324                 $password       = $args[2];
1325
1326                 if ( !$user = $this->login($username, $password) )
1327                         return $this->error;
1328
1329                 if ( !current_user_can( 'moderate_comments' ) )
1330                         return new IXR_Error( 403, __( 'You are not allowed access to details about this site.' ) );
1331
1332                 do_action('xmlrpc_call', 'wp.getCommentStatusList');
1333
1334                 return get_comment_statuses( );
1335         }
1336
1337         /**
1338          * Retrieve comment count.
1339          *
1340          * @since 2.5.0
1341          *
1342          * @param array $args Method parameters.
1343          * @return array
1344          */
1345         function wp_getCommentCount( $args ) {
1346                 $this->escape($args);
1347
1348                 $blog_id        = (int) $args[0];
1349                 $username       = $args[1];
1350                 $password       = $args[2];
1351                 $post_id        = (int) $args[3];
1352
1353                 if ( !$user = $this->login($username, $password) )
1354                         return $this->error;
1355
1356                 if ( !current_user_can( 'edit_posts' ) )
1357                         return new IXR_Error( 403, __( 'You are not allowed access to details about comments.' ) );
1358
1359                 do_action('xmlrpc_call', 'wp.getCommentCount');
1360
1361                 $count = wp_count_comments( $post_id );
1362                 return array(
1363                         "approved" => $count->approved,
1364                         "awaiting_moderation" => $count->moderated,
1365                         "spam" => $count->spam,
1366                         "total_comments" => $count->total_comments
1367                 );
1368         }
1369
1370         /**
1371          * Retrieve post statuses.
1372          *
1373          * @since 2.5.0
1374          *
1375          * @param array $args Method parameters.
1376          * @return array
1377          */
1378         function wp_getPostStatusList( $args ) {
1379                 $this->escape( $args );
1380
1381                 $blog_id        = (int) $args[0];
1382                 $username       = $args[1];
1383                 $password       = $args[2];
1384
1385                 if ( !$user = $this->login($username, $password) )
1386                         return $this->error;
1387
1388                 if ( !current_user_can( 'edit_posts' ) )
1389                         return new IXR_Error( 403, __( 'You are not allowed access to details about this site.' ) );
1390
1391                 do_action('xmlrpc_call', 'wp.getPostStatusList');
1392
1393                 return get_post_statuses( );
1394         }
1395
1396         /**
1397          * Retrieve page statuses.
1398          *
1399          * @since 2.5.0
1400          *
1401          * @param array $args Method parameters.
1402          * @return array
1403          */
1404         function wp_getPageStatusList( $args ) {
1405                 $this->escape( $args );
1406
1407                 $blog_id        = (int) $args[0];
1408                 $username       = $args[1];
1409                 $password       = $args[2];
1410
1411                 if ( !$user = $this->login($username, $password) )
1412                         return $this->error;
1413
1414                 if ( !current_user_can( 'edit_posts' ) )
1415                         return new IXR_Error( 403, __( 'You are not allowed access to details about this site.' ) );
1416
1417                 do_action('xmlrpc_call', 'wp.getPageStatusList');
1418
1419                 return get_page_statuses( );
1420         }
1421
1422         /**
1423          * Retrieve page templates.
1424          *
1425          * @since 2.6.0
1426          *
1427          * @param array $args Method parameters.
1428          * @return array
1429          */
1430         function wp_getPageTemplates( $args ) {
1431                 $this->escape( $args );
1432
1433                 $blog_id        = (int) $args[0];
1434                 $username       = $args[1];
1435                 $password       = $args[2];
1436
1437                 if ( !$user = $this->login($username, $password) )
1438                         return $this->error;
1439
1440                 if ( !current_user_can( 'edit_pages' ) )
1441                         return new IXR_Error( 403, __( 'You are not allowed access to details about this site.' ) );
1442
1443                 $templates = get_page_templates( );
1444                 $templates['Default'] = 'default';
1445
1446                 return $templates;
1447         }
1448
1449         /**
1450          * Retrieve blog options.
1451          *
1452          * @since 2.6.0
1453          *
1454          * @param array $args Method parameters.
1455          * @return array
1456          */
1457         function wp_getOptions( $args ) {
1458                 $this->escape( $args );
1459
1460                 $blog_id        = (int) $args[0];
1461                 $username       = $args[1];
1462                 $password       = $args[2];
1463                 $options        = (array) $args[3];
1464
1465                 if ( !$user = $this->login($username, $password) )
1466                         return $this->error;
1467
1468                 // If no specific options where asked for, return all of them
1469                 if ( count( $options ) == 0 )
1470                         $options = array_keys($this->blog_options);
1471
1472                 return $this->_getOptions($options);
1473         }
1474
1475         /**
1476          * Retrieve blog options value from list.
1477          *
1478          * @since 2.6.0
1479          *
1480          * @param array $options Options to retrieve.
1481          * @return array
1482          */
1483         function _getOptions($options) {
1484                 $data = array( );
1485                 foreach ( $options as $option ) {
1486                         if ( array_key_exists( $option, $this->blog_options ) ) {
1487                                 $data[$option] = $this->blog_options[$option];
1488                                 //Is the value static or dynamic?
1489                                 if ( isset( $data[$option]['option'] ) ) {
1490                                         $data[$option]['value'] = get_option( $data[$option]['option'] );
1491                                         unset($data[$option]['option']);
1492                                 }
1493                         }
1494                 }
1495
1496                 return $data;
1497         }
1498
1499         /**
1500          * Update blog options.
1501          *
1502          * @since 2.6.0
1503          *
1504          * @param array $args Method parameters.
1505          * @return unknown
1506          */
1507         function wp_setOptions( $args ) {
1508                 $this->escape( $args );
1509
1510                 $blog_id        = (int) $args[0];
1511                 $username       = $args[1];
1512                 $password       = $args[2];
1513                 $options        = (array) $args[3];
1514
1515                 if ( !$user = $this->login($username, $password) )
1516                         return $this->error;
1517
1518                 if ( !current_user_can( 'manage_options' ) )
1519                         return new IXR_Error( 403, __( 'You are not allowed to update options.' ) );
1520
1521                 foreach ( $options as $o_name => $o_value ) {
1522                         $option_names[] = $o_name;
1523                         if ( !array_key_exists( $o_name, $this->blog_options ) )
1524                                 continue;
1525
1526                         if ( $this->blog_options[$o_name]['readonly'] == true )
1527                                 continue;
1528
1529                         update_option( $this->blog_options[$o_name]['option'], $o_value );
1530                 }
1531
1532                 //Now return the updated values
1533                 return $this->_getOptions($option_names);
1534         }
1535
1536         /* Blogger API functions.
1537          * specs on http://plant.blogger.com/api and http://groups.yahoo.com/group/bloggerDev/
1538          */
1539
1540         /**
1541          * Retrieve blogs that user owns.
1542          *
1543          * Will make more sense once we support multiple blogs.
1544          *
1545          * @since 1.5.0
1546          *
1547          * @param array $args Method parameters.
1548          * @return array
1549          */
1550         function blogger_getUsersBlogs($args) {
1551                 if ( is_multisite() )
1552                         return $this->_multisite_getUsersBlogs($args);
1553
1554                 $this->escape($args);
1555
1556                 $username = $args[1];
1557                 $password  = $args[2];
1558
1559                 if ( !$user = $this->login($username, $password) )
1560                         return $this->error;
1561
1562                 do_action('xmlrpc_call', 'blogger.getUsersBlogs');
1563
1564                 $is_admin = current_user_can('manage_options');
1565
1566                 $struct = array(
1567                         'isAdmin'  => $is_admin,
1568                         'url'      => get_option('home') . '/',
1569                         'blogid'   => '1',
1570                         'blogName' => get_option('blogname'),
1571                         'xmlrpc'   => site_url( 'xmlrpc.php' )
1572                 );
1573
1574                 return array($struct);
1575         }
1576
1577         /**
1578          * Private function for retrieving a users blogs for multisite setups
1579          *
1580          * @access protected
1581          */
1582         function _multisite_getUsersBlogs($args) {
1583                 global $current_blog;
1584                 $domain = $current_blog->domain;
1585                 $path = $current_blog->path . 'xmlrpc.php';
1586                 $protocol = is_ssl() ? 'https' : 'http';
1587
1588                 $rpc = new IXR_Client("$protocol://{$domain}{$path}");
1589                 $rpc->query('wp.getUsersBlogs', $args[1], $args[2]);
1590                 $blogs = $rpc->getResponse();
1591
1592                 if ( isset($blogs['faultCode']) )
1593                         return new IXR_Error($blogs['faultCode'], $blogs['faultString']);
1594
1595                 if ( $_SERVER['HTTP_HOST'] == $domain && $_SERVER['REQUEST_URI'] == $path ) {
1596                         return $blogs;
1597                 } else {
1598                         foreach ( (array) $blogs as $blog ) {
1599                                 if ( strpos($blog['url'], $_SERVER['HTTP_HOST']) )
1600                                         return array($blog);
1601                         }
1602                         return array();
1603                 }
1604         }
1605
1606         /**
1607          * Retrieve user's data.
1608          *
1609          * Gives your client some info about you, so you don't have to.
1610          *
1611          * @since 1.5.0
1612          *
1613          * @param array $args Method parameters.
1614          * @return array
1615          */
1616         function blogger_getUserInfo($args) {
1617
1618                 $this->escape($args);
1619
1620                 $username = $args[1];
1621                 $password  = $args[2];
1622
1623                 if ( !$user = $this->login($username, $password) )
1624                         return $this->error;
1625
1626                 if ( !current_user_can( 'edit_posts' ) )
1627                         return new IXR_Error( 401, __( 'Sorry, you do not have access to user data on this site.' ) );
1628
1629                 do_action('xmlrpc_call', 'blogger.getUserInfo');
1630
1631                 $struct = array(
1632                         'nickname'  => $user->nickname,
1633                         'userid'    => $user->ID,
1634                         'url'       => $user->user_url,
1635                         'lastname'  => $user->last_name,
1636                         'firstname' => $user->first_name
1637                 );
1638
1639                 return $struct;
1640         }
1641
1642         /**
1643          * Retrieve post.
1644          *
1645          * @since 1.5.0
1646          *
1647          * @param array $args Method parameters.
1648          * @return array
1649          */
1650         function blogger_getPost($args) {
1651
1652                 $this->escape($args);
1653
1654                 $post_ID    = (int) $args[1];
1655                 $username = $args[2];
1656                 $password  = $args[3];
1657
1658                 if ( !$user = $this->login($username, $password) )
1659                         return $this->error;
1660
1661                 if ( !current_user_can( 'edit_post', $post_ID ) )
1662                         return new IXR_Error( 401, __( 'Sorry, you cannot edit this post.' ) );
1663
1664                 do_action('xmlrpc_call', 'blogger.getPost');
1665
1666                 $post_data = wp_get_single_post($post_ID, ARRAY_A);
1667
1668                 $categories = implode(',', wp_get_post_categories($post_ID));
1669
1670                 $content  = '<title>'.stripslashes($post_data['post_title']).'</title>';
1671                 $content .= '<category>'.$categories.'</category>';
1672                 $content .= stripslashes($post_data['post_content']);
1673
1674                 $struct = array(
1675                         'userid'    => $post_data['post_author'],
1676                         'dateCreated' => new IXR_Date(mysql2date('Ymd\TH:i:s', $post_data['post_date'], false)),
1677                         'content'     => $content,
1678                         'postid'  => $post_data['ID']
1679                 );
1680
1681                 return $struct;
1682         }
1683
1684         /**
1685          * Retrieve list of recent posts.
1686          *
1687          * @since 1.5.0
1688          *
1689          * @param array $args Method parameters.
1690          * @return array
1691          */
1692         function blogger_getRecentPosts($args) {
1693
1694                 $this->escape($args);
1695
1696                 $blog_ID    = (int) $args[1]; /* though we don't use it yet */
1697                 $username = $args[2];
1698                 $password  = $args[3];
1699                 $num_posts  = $args[4];
1700
1701                 if ( !$user = $this->login($username, $password) )
1702                         return $this->error;
1703
1704                 do_action('xmlrpc_call', 'blogger.getRecentPosts');
1705
1706                 $posts_list = wp_get_recent_posts($num_posts);
1707
1708                 if ( !$posts_list ) {
1709                         $this->error = new IXR_Error(500, __('Either there are no posts, or something went wrong.'));
1710                         return $this->error;
1711                 }
1712
1713                 foreach ($posts_list as $entry) {
1714                         if ( !current_user_can( 'edit_post', $entry['ID'] ) )
1715                                 continue;
1716
1717                         $post_date = mysql2date('Ymd\TH:i:s', $entry['post_date'], false);
1718                         $categories = implode(',', wp_get_post_categories($entry['ID']));
1719
1720                         $content  = '<title>'.stripslashes($entry['post_title']).'</title>';
1721                         $content .= '<category>'.$categories.'</category>';
1722                         $content .= stripslashes($entry['post_content']);
1723
1724                         $struct[] = array(
1725                                 'userid' => $entry['post_author'],
1726                                 'dateCreated' => new IXR_Date($post_date),
1727                                 'content' => $content,
1728                                 'postid' => $entry['ID'],
1729                         );
1730
1731                 }
1732
1733                 $recent_posts = array();
1734                 for ( $j=0; $j<count($struct); $j++ ) {
1735                         array_push($recent_posts, $struct[$j]);
1736                 }
1737
1738                 return $recent_posts;
1739         }
1740
1741         /**
1742          * Retrieve blog_filename content.
1743          *
1744          * @since 1.5.0
1745          *
1746          * @param array $args Method parameters.
1747          * @return string
1748          */
1749         function blogger_getTemplate($args) {
1750
1751                 $this->escape($args);
1752
1753                 $blog_ID    = (int) $args[1];
1754                 $username = $args[2];
1755                 $password  = $args[3];
1756                 $template   = $args[4]; /* could be 'main' or 'archiveIndex', but we don't use it */
1757
1758                 if ( !$user = $this->login($username, $password) )
1759                         return $this->error;
1760
1761                 do_action('xmlrpc_call', 'blogger.getTemplate');
1762
1763                 if ( !current_user_can('edit_themes') )
1764                         return new IXR_Error(401, __('Sorry, this user can not edit the template.'));
1765
1766                 /* warning: here we make the assumption that the blog's URL is on the same server */
1767                 $filename = get_option('home') . '/';
1768                 $filename = preg_replace('#https?://.+?/#', $_SERVER['DOCUMENT_ROOT'].'/', $filename);
1769
1770                 $f = fopen($filename, 'r');
1771                 $content = fread($f, filesize($filename));
1772                 fclose($f);
1773
1774                 /* so it is actually editable with a windows/mac client */
1775                 // 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);
1776
1777                 return $content;
1778         }
1779
1780         /**
1781          * Updates the content of blog_filename.
1782          *
1783          * @since 1.5.0
1784          *
1785          * @param array $args Method parameters.
1786          * @return bool True when done.
1787          */
1788         function blogger_setTemplate($args) {
1789
1790                 $this->escape($args);
1791
1792                 $blog_ID    = (int) $args[1];
1793                 $username = $args[2];
1794                 $password  = $args[3];
1795                 $content    = $args[4];
1796                 $template   = $args[5]; /* could be 'main' or 'archiveIndex', but we don't use it */
1797
1798                 if ( !$user = $this->login($username, $password) )
1799                         return $this->error;
1800
1801                 do_action('xmlrpc_call', 'blogger.setTemplate');
1802
1803                 if ( !current_user_can('edit_themes') )
1804                         return new IXR_Error(401, __('Sorry, this user cannot edit the template.'));
1805
1806                 /* warning: here we make the assumption that the blog's URL is on the same server */
1807                 $filename = get_option('home') . '/';
1808                 $filename = preg_replace('#https?://.+?/#', $_SERVER['DOCUMENT_ROOT'].'/', $filename);
1809
1810                 if ($f = fopen($filename, 'w+')) {
1811                         fwrite($f, $content);
1812                         fclose($f);
1813                 } else {
1814                         return new IXR_Error(500, __('Either the file is not writable, or something wrong happened. The file has not been updated.'));
1815                 }
1816
1817                 return true;
1818         }
1819
1820         /**
1821          * Create new post.
1822          *
1823          * @since 1.5.0
1824          *
1825          * @param array $args Method parameters.
1826          * @return int
1827          */
1828         function blogger_newPost($args) {
1829
1830                 $this->escape($args);
1831
1832                 $blog_ID    = (int) $args[1]; /* though we don't use it yet */
1833                 $username = $args[2];
1834                 $password  = $args[3];
1835                 $content    = $args[4];
1836                 $publish    = $args[5];
1837
1838                 if ( !$user = $this->login($username, $password) )
1839                         return $this->error;
1840
1841                 do_action('xmlrpc_call', 'blogger.newPost');
1842
1843                 $cap = ($publish) ? 'publish_posts' : 'edit_posts';
1844                 if ( !current_user_can($cap) )
1845                         return new IXR_Error(401, __('Sorry, you are not allowed to post on this site.'));
1846
1847                 $post_status = ($publish) ? 'publish' : 'draft';
1848
1849                 $post_author = $user->ID;
1850
1851                 $post_title = xmlrpc_getposttitle($content);
1852                 $post_category = xmlrpc_getpostcategory($content);
1853                 $post_content = xmlrpc_removepostdata($content);
1854
1855                 $post_date = current_time('mysql');
1856                 $post_date_gmt = current_time('mysql', 1);
1857
1858                 $post_data = compact('blog_ID', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status');
1859
1860                 $post_ID = wp_insert_post($post_data);
1861                 if ( is_wp_error( $post_ID ) )
1862                         return new IXR_Error(500, $post_ID->get_error_message());
1863
1864                 if ( !$post_ID )
1865                         return new IXR_Error(500, __('Sorry, your entry could not be posted. Something wrong happened.'));
1866
1867                 $this->attach_uploads( $post_ID, $post_content );
1868
1869                 logIO('O', "Posted ! ID: $post_ID");
1870
1871                 return $post_ID;
1872         }
1873
1874         /**
1875          * Edit a post.
1876          *
1877          * @since 1.5.0
1878          *
1879          * @param array $args Method parameters.
1880          * @return bool true when done.
1881          */
1882         function blogger_editPost($args) {
1883
1884                 $this->escape($args);
1885
1886                 $post_ID     = (int) $args[1];
1887                 $username  = $args[2];
1888                 $password   = $args[3];
1889                 $content     = $args[4];
1890                 $publish     = $args[5];
1891
1892                 if ( !$user = $this->login($username, $password) )
1893                         return $this->error;
1894
1895                 do_action('xmlrpc_call', 'blogger.editPost');
1896
1897                 $actual_post = wp_get_single_post($post_ID,ARRAY_A);
1898
1899                 if ( !$actual_post || $actual_post['post_type'] != 'post' )
1900                         return new IXR_Error(404, __('Sorry, no such post.'));
1901
1902                 $this->escape($actual_post);
1903
1904                 if ( !current_user_can('edit_post', $post_ID) )
1905                         return new IXR_Error(401, __('Sorry, you do not have the right to edit this post.'));
1906
1907                 extract($actual_post, EXTR_SKIP);
1908
1909                 if ( ('publish' == $post_status) && !current_user_can('publish_posts') )
1910                         return new IXR_Error(401, __('Sorry, you do not have the right to publish this post.'));
1911
1912                 $post_title = xmlrpc_getposttitle($content);
1913                 $post_category = xmlrpc_getpostcategory($content);
1914                 $post_content = xmlrpc_removepostdata($content);
1915
1916                 $postdata = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt');
1917
1918                 $result = wp_update_post($postdata);
1919
1920                 if ( !$result )
1921                         return new IXR_Error(500, __('For some strange yet very annoying reason, this post could not be edited.'));
1922
1923                 $this->attach_uploads( $ID, $post_content );
1924
1925                 return true;
1926         }
1927
1928         /**
1929          * Remove a post.
1930          *
1931          * @since 1.5.0
1932          *
1933          * @param array $args Method parameters.
1934          * @return bool True when post is deleted.
1935          */
1936         function blogger_deletePost($args) {
1937                 $this->escape($args);
1938
1939                 $post_ID     = (int) $args[1];
1940                 $username  = $args[2];
1941                 $password   = $args[3];
1942                 $publish     = $args[4];
1943
1944                 if ( !$user = $this->login($username, $password) )
1945                         return $this->error;
1946
1947                 do_action('xmlrpc_call', 'blogger.deletePost');
1948
1949                 $actual_post = wp_get_single_post($post_ID,ARRAY_A);
1950
1951                 if ( !$actual_post || $actual_post['post_type'] != 'post' )
1952                         return new IXR_Error(404, __('Sorry, no such post.'));
1953
1954                 if ( !current_user_can('edit_post', $post_ID) )
1955                         return new IXR_Error(401, __('Sorry, you do not have the right to delete this post.'));
1956
1957                 $result = wp_delete_post($post_ID);
1958
1959                 if ( !$result )
1960                         return new IXR_Error(500, __('For some strange yet very annoying reason, this post could not be deleted.'));
1961
1962                 return true;
1963         }
1964
1965         /* MetaWeblog API functions
1966          * specs on wherever Dave Winer wants them to be
1967          */
1968
1969         /**
1970          * Create a new post.
1971          *
1972          * @since 1.5.0
1973          *
1974          * @param array $args Method parameters.
1975          * @return int
1976          */
1977         function mw_newPost($args) {
1978                 $this->escape($args);
1979
1980                 $blog_ID     = (int) $args[0]; // we will support this in the near future
1981                 $username  = $args[1];
1982                 $password   = $args[2];
1983                 $content_struct = $args[3];
1984                 $publish     = $args[4];
1985
1986                 if ( !$user = $this->login($username, $password) )
1987                         return $this->error;
1988
1989                 do_action('xmlrpc_call', 'metaWeblog.newPost');
1990
1991                 $cap = ( $publish ) ? 'publish_posts' : 'edit_posts';
1992                 $error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
1993                 $post_type = 'post';
1994                 $page_template = '';
1995                 if ( !empty( $content_struct['post_type'] ) ) {
1996                         if ( $content_struct['post_type'] == 'page' ) {
1997                                 $cap = ( $publish ) ? 'publish_pages' : 'edit_pages';
1998                                 $error_message = __( 'Sorry, you are not allowed to publish pages on this site.' );
1999                                 $post_type = 'page';
2000                                 if ( !empty( $content_struct['wp_page_template'] ) )
2001                                         $page_template = $content_struct['wp_page_template'];
2002                         } elseif ( $content_struct['post_type'] == 'post' ) {
2003                                 // This is the default, no changes needed
2004                         } else {
2005                                 // No other post_type values are allowed here
2006                                 return new IXR_Error( 401, __( 'Invalid post type.' ) );
2007                         }
2008                 }
2009
2010                 if ( !current_user_can( $cap ) )
2011                         return new IXR_Error( 401, $error_message );
2012
2013                 // Let WordPress generate the post_name (slug) unless
2014                 // one has been provided.
2015                 $post_name = "";
2016                 if ( isset($content_struct["wp_slug"]) )
2017                         $post_name = $content_struct["wp_slug"];
2018
2019                 // Only use a password if one was given.
2020                 if ( isset($content_struct["wp_password"]) )
2021                         $post_password = $content_struct["wp_password"];
2022
2023                 // Only set a post parent if one was provided.
2024                 if ( isset($content_struct["wp_page_parent_id"]) )
2025                         $post_parent = $content_struct["wp_page_parent_id"];
2026
2027                 // Only set the menu_order if it was provided.
2028                 if ( isset($content_struct["wp_page_order"]) )
2029                         $menu_order = $content_struct["wp_page_order"];
2030
2031                 $post_author = $user->ID;
2032
2033                 // If an author id was provided then use it instead.
2034                 if ( isset($content_struct["wp_author_id"]) && ($user->ID != $content_struct["wp_author_id"]) ) {
2035                         switch ( $post_type ) {
2036                                 case "post":
2037                                         if ( !current_user_can("edit_others_posts") )
2038                                                 return(new IXR_Error(401, __("You are not allowed to post as this user")));
2039                                         break;
2040                                 case "page":
2041                                         if ( !current_user_can("edit_others_pages") )
2042                                                 return(new IXR_Error(401, __("You are not allowed to create pages as this user")));
2043                                         break;
2044                                 default:
2045                                         return(new IXR_Error(401, __("Invalid post type.")));
2046                                         break;
2047                         }
2048                         $post_author = $content_struct["wp_author_id"];
2049                 }
2050
2051                 $post_title = $content_struct['title'];
2052                 $post_content = $content_struct['description'];
2053
2054                 $post_status = $publish ? 'publish' : 'draft';
2055
2056                 if ( isset( $content_struct["{$post_type}_status"] ) ) {
2057                         switch ( $content_struct["{$post_type}_status"] ) {
2058                                 case 'draft':
2059                                 case 'private':
2060                                 case 'publish':
2061                                         $post_status = $content_struct["{$post_type}_status"];
2062                                         break;
2063                                 case 'pending':
2064                                         // Pending is only valid for posts, not pages.
2065                                         if ( $post_type === 'post' )
2066                                                 $post_status = $content_struct["{$post_type}_status"];
2067                                         break;
2068                                 default:
2069                                         $post_status = $publish ? 'publish' : 'draft';
2070                                         break;
2071                         }
2072                 }
2073
2074                 $post_excerpt = $content_struct['mt_excerpt'];
2075                 $post_more = $content_struct['mt_text_more'];
2076
2077                 $tags_input = $content_struct['mt_keywords'];
2078
2079                 if ( isset($content_struct["mt_allow_comments"]) ) {
2080                         if ( !is_numeric($content_struct["mt_allow_comments"]) ) {
2081                                 switch ( $content_struct["mt_allow_comments"] ) {
2082                                         case "closed":
2083                                                 $comment_status = "closed";
2084                                                 break;
2085                                         case "open":
2086                                                 $comment_status = "open";
2087                                                 break;
2088                                         default:
2089                                                 $comment_status = get_option("default_comment_status");
2090                                                 break;
2091                                 }
2092                         } else {
2093                                 switch ( (int) $content_struct["mt_allow_comments"] ) {
2094                                         case 0:
2095                                         case 2:
2096                                                 $comment_status = "closed";
2097                                                 break;
2098                                         case 1:
2099                                                 $comment_status = "open";
2100                                                 break;
2101                                         default:
2102                                                 $comment_status = get_option("default_comment_status");
2103                                                 break;
2104                                 }
2105                         }
2106                 } else {
2107                         $comment_status = get_option("default_comment_status");
2108                 }
2109
2110                 if ( isset($content_struct["mt_allow_pings"]) ) {
2111                         if ( !is_numeric($content_struct["mt_allow_pings"]) ) {
2112                                 switch ( $content_struct['mt_allow_pings'] ) {
2113                                         case "closed":
2114                                                 $ping_status = "closed";
2115                                                 break;
2116                                         case "open":
2117                                                 $ping_status = "open";
2118                                                 break;
2119                                         default:
2120                                                 $ping_status = get_option("default_ping_status");
2121                                                 break;
2122                                 }
2123                         } else {
2124                                 switch ( (int) $content_struct["mt_allow_pings"] ) {
2125                                         case 0:
2126                                                 $ping_status = "closed";
2127                                                 break;
2128                                         case 1:
2129                                                 $ping_status = "open";
2130                                                 break;
2131                                         default:
2132                                                 $ping_status = get_option("default_ping_status");
2133                                                 break;
2134                                 }
2135                         }
2136                 } else {
2137                         $ping_status = get_option("default_ping_status");
2138                 }
2139
2140                 if ( $post_more )
2141                         $post_content = $post_content . "<!--more-->" . $post_more;
2142
2143                 $to_ping = $content_struct['mt_tb_ping_urls'];
2144                 if ( is_array($to_ping) )
2145                         $to_ping = implode(' ', $to_ping);
2146
2147                 // Do some timestamp voodoo
2148                 if ( !empty( $content_struct['date_created_gmt'] ) )
2149                         $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
2150                 elseif ( !empty( $content_struct['dateCreated']) )
2151                         $dateCreated = $content_struct['dateCreated']->getIso();
2152
2153                 if ( !empty( $dateCreated ) ) {
2154                         $post_date = get_date_from_gmt(iso8601_to_datetime($dateCreated));
2155                         $post_date_gmt = iso8601_to_datetime($dateCreated, GMT);
2156                 } else {
2157                         $post_date = current_time('mysql');
2158                         $post_date_gmt = current_time('mysql', 1);
2159                 }
2160
2161                 $catnames = $content_struct['categories'];
2162                 logIO('O', 'Post cats: ' . var_export($catnames,true));
2163                 $post_category = array();
2164
2165                 if ( is_array($catnames) ) {
2166                         foreach ($catnames as $cat) {
2167                                 $post_category[] = get_cat_ID($cat);
2168                         }
2169                 }
2170
2171                 // We've got all the data -- post it:
2172                 $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');
2173
2174                 $post_ID = wp_insert_post($postdata, true);
2175                 if ( is_wp_error( $post_ID ) )
2176                         return new IXR_Error(500, $post_ID->get_error_message());
2177
2178                 if ( !$post_ID )
2179                         return new IXR_Error(500, __('Sorry, your entry could not be posted. Something wrong happened.'));
2180
2181                 // Only posts can be sticky
2182                 if ( $post_type == 'post' && isset( $content_struct['sticky'] ) ) {
2183                         if ( $content_struct['sticky'] == true )
2184                                 stick_post( $post_ID );
2185                         elseif ( $content_struct['sticky'] == false )
2186                                 unstick_post( $post_ID );
2187                 }
2188
2189                 if ( isset($content_struct['custom_fields']) )
2190                         $this->set_custom_fields($post_ID, $content_struct['custom_fields']);
2191
2192                 // Handle enclosures
2193                 $this->add_enclosure_if_new($post_ID, $content_struct['enclosure']);
2194
2195                 $this->attach_uploads( $post_ID, $post_content );
2196
2197                 logIO('O', "Posted ! ID: $post_ID");
2198
2199                 return strval($post_ID);
2200         }
2201
2202         function add_enclosure_if_new($post_ID, $enclosure) {
2203                 if ( is_array( $enclosure ) && isset( $enclosure['url'] ) && isset( $enclosure['length'] ) && isset( $enclosure['type'] ) ) {
2204
2205                         $encstring = $enclosure['url'] . "\n" . $enclosure['length'] . "\n" . $enclosure['type'];
2206                         $found = false;
2207                         foreach ( (array) get_post_custom($post_ID) as $key => $val) {
2208                                 if ($key == 'enclosure') {
2209                                         foreach ( (array) $val as $enc ) {
2210                                                 if ($enc == $encstring) {
2211                                                         $found = true;
2212                                                         break 2;
2213                                                 }
2214                                         }
2215                                 }
2216                         }
2217                         if (!$found)
2218                                 add_post_meta( $post_ID, 'enclosure', $encstring );
2219                 }
2220         }
2221
2222         /**
2223          * Attach upload to a post.
2224          *
2225          * @since 2.1.0
2226          *
2227          * @param int $post_ID Post ID.
2228          * @param string $post_content Post Content for attachment.
2229          */
2230         function attach_uploads( $post_ID, $post_content ) {
2231                 global $wpdb;
2232
2233                 // find any unattached files
2234                 $attachments = $wpdb->get_results( "SELECT ID, guid FROM {$wpdb->posts} WHERE post_parent = '0' AND post_type = 'attachment'" );
2235                 if ( is_array( $attachments ) ) {
2236                         foreach ( $attachments as $file ) {
2237                                 if ( strpos( $post_content, $file->guid ) !== false )
2238                                         $wpdb->update($wpdb->posts, array('post_parent' => $post_ID), array('ID' => $file->ID) );
2239                         }
2240                 }
2241         }
2242
2243         /**
2244          * Edit a post.
2245          *
2246          * @since 1.5.0
2247          *
2248          * @param array $args Method parameters.
2249          * @return bool True on success.
2250          */
2251         function mw_editPost($args) {
2252
2253                 $this->escape($args);
2254
2255                 $post_ID     = (int) $args[0];
2256                 $username  = $args[1];
2257                 $password   = $args[2];
2258                 $content_struct = $args[3];
2259                 $publish     = $args[4];
2260
2261                 if ( !$user = $this->login($username, $password) )
2262                         return $this->error;
2263
2264                 do_action('xmlrpc_call', 'metaWeblog.editPost');
2265
2266                 $cap = ( $publish ) ? 'publish_posts' : 'edit_posts';
2267                 $error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
2268                 $post_type = 'post';
2269                 $page_template = '';
2270                 if ( !empty( $content_struct['post_type'] ) ) {
2271                         if ( $content_struct['post_type'] == 'page' ) {
2272                                 $cap = ( $publish ) ? 'publish_pages' : 'edit_pages';
2273                                 $error_message = __( 'Sorry, you are not allowed to publish pages on this site.' );
2274                                 $post_type = 'page';
2275                                 if ( !empty( $content_struct['wp_page_template'] ) )
2276                                         $page_template = $content_struct['wp_page_template'];
2277                         } elseif ( $content_struct['post_type'] == 'post' ) {
2278                                 // This is the default, no changes needed
2279                         } else {
2280                                 // No other post_type values are allowed here
2281                                 return new IXR_Error( 401, __( 'Invalid post type.' ) );
2282                         }
2283                 }
2284
2285                 if ( !current_user_can( $cap ) )
2286                         return new IXR_Error( 401, $error_message );
2287
2288                 $postdata = wp_get_single_post($post_ID, ARRAY_A);
2289
2290                 // If there is no post data for the give post id, stop
2291                 // now and return an error.  Other wise a new post will be
2292                 // created (which was the old behavior).
2293                 if ( empty($postdata["ID"]) )
2294                         return(new IXR_Error(404, __("Invalid post ID.")));
2295
2296                 $this->escape($postdata);
2297                 extract($postdata, EXTR_SKIP);
2298
2299                 // Let WordPress manage slug if none was provided.
2300                 $post_name = "";
2301                 if ( isset($content_struct["wp_slug"]) )
2302                         $post_name = $content_struct["wp_slug"];
2303
2304                 // Only use a password if one was given.
2305                 if ( isset($content_struct["wp_password"]) )
2306                         $post_password = $content_struct["wp_password"];
2307
2308                 // Only set a post parent if one was given.
2309                 if ( isset($content_struct["wp_page_parent_id"]) )
2310                         $post_parent = $content_struct["wp_page_parent_id"];
2311
2312                 // Only set the menu_order if it was given.
2313                 if ( isset($content_struct["wp_page_order"]) )
2314                         $menu_order = $content_struct["wp_page_order"];
2315
2316                 $post_author = $postdata["post_author"];
2317
2318                 // Only set the post_author if one is set.
2319                 if ( isset($content_struct["wp_author_id"]) && ($user->ID != $content_struct["wp_author_id"]) ) {
2320                         switch ( $post_type ) {
2321                                 case "post":
2322                                         if ( !current_user_can("edit_others_posts") )
2323                                                 return(new IXR_Error(401, __("You are not allowed to change the post author as this user.")));
2324                                         break;
2325                                 case "page":
2326                                         if ( !current_user_can("edit_others_pages") )
2327                                                 return(new IXR_Error(401, __("You are not allowed to change the page author as this user.")));
2328                                         break;
2329                                 default:
2330                                         return(new IXR_Error(401, __("Invalid post type.")));
2331                                         break;
2332                         }
2333                         $post_author = $content_struct["wp_author_id"];
2334                 }
2335
2336                 if ( isset($content_struct["mt_allow_comments"]) ) {
2337                         if ( !is_numeric($content_struct["mt_allow_comments"]) ) {
2338                                 switch ( $content_struct["mt_allow_comments"] ) {
2339                                         case "closed":
2340                                                 $comment_status = "closed";
2341                                                 break;
2342                                         case "open":
2343                                                 $comment_status = "open";
2344                                                 break;
2345                                         default:
2346                                                 $comment_status = get_option("default_comment_status");
2347                                                 break;
2348                                 }
2349                         } else {
2350                                 switch ( (int) $content_struct["mt_allow_comments"] ) {
2351                                         case 0:
2352                                         case 2:
2353                                                 $comment_status = "closed";
2354                                                 break;
2355                                         case 1:
2356                                                 $comment_status = "open";
2357                                                 break;
2358                                         default:
2359                                                 $comment_status = get_option("default_comment_status");
2360                                                 break;
2361                                 }
2362                         }
2363                 }
2364
2365                 if ( isset($content_struct["mt_allow_pings"]) ) {
2366                         if ( !is_numeric($content_struct["mt_allow_pings"]) ) {
2367                                 switch ( $content_struct["mt_allow_pings"] ) {
2368                                         case "closed":
2369                                                 $ping_status = "closed";
2370                                                 break;
2371                                         case "open":
2372                                                 $ping_status = "open";
2373                                                 break;
2374                                         default:
2375                                                 $ping_status = get_option("default_ping_status");
2376                                                 break;
2377                                 }
2378                         } else {
2379                                 switch ( (int) $content_struct["mt_allow_pings"] ) {
2380                                         case 0:
2381                                                 $ping_status = "closed";
2382                                                 break;
2383                                         case 1:
2384                                                 $ping_status = "open";
2385                                                 break;
2386                                         default:
2387                                                 $ping_status = get_option("default_ping_status");
2388                                                 break;
2389                                 }
2390                         }
2391                 }
2392
2393                 $post_title = $content_struct['title'];
2394                 $post_content = $content_struct['description'];
2395                 $catnames = $content_struct['categories'];
2396
2397                 $post_category = array();
2398
2399                 if ( is_array($catnames) ) {
2400                         foreach ($catnames as $cat) {
2401                                 $post_category[] = get_cat_ID($cat);
2402                         }
2403                 }
2404
2405                 $post_excerpt = $content_struct['mt_excerpt'];
2406                 $post_more = $content_struct['mt_text_more'];
2407
2408                 $post_status = $publish ? 'publish' : 'draft';
2409                 if ( isset( $content_struct["{$post_type}_status"] ) ) {
2410                         switch( $content_struct["{$post_type}_status"] ) {
2411                                 case 'draft':
2412                                 case 'private':
2413                                 case 'publish':
2414                                         $post_status = $content_struct["{$post_type}_status"];
2415                                         break;
2416                                 case 'pending':
2417                                         // Pending is only valid for posts, not pages.
2418                                         if ( $post_type === 'post' )
2419                                                 $post_status = $content_struct["{$post_type}_status"];
2420                                         break;
2421                                 default:
2422                                         $post_status = $publish ? 'publish' : 'draft';
2423                                         break;
2424                         }
2425                 }
2426
2427                 $tags_input = $content_struct['mt_keywords'];
2428
2429                 if ( ('publish' == $post_status) ) {
2430                         if ( ( 'page' == $post_type ) && !current_user_can('publish_pages') )
2431                                 return new IXR_Error(401, __('Sorry, you do not have the right to publish this page.'));
2432                         else if ( !current_user_can('publish_posts') )
2433                                 return new IXR_Error(401, __('Sorry, you do not have the right to publish this post.'));
2434                 }
2435
2436                 if ( $post_more )
2437                         $post_content = $post_content . "<!--more-->" . $post_more;
2438
2439                 $to_ping = $content_struct['mt_tb_ping_urls'];
2440                 if ( is_array($to_ping) )
2441                         $to_ping = implode(' ', $to_ping);
2442
2443                 // Do some timestamp voodoo
2444                 if ( !empty( $content_struct['date_created_gmt'] ) )
2445                         $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
2446                 elseif ( !empty( $content_struct['dateCreated']) )
2447                         $dateCreated = $content_struct['dateCreated']->getIso();
2448
2449                 if ( !empty( $dateCreated ) ) {
2450                         $post_date = get_date_from_gmt(iso8601_to_datetime($dateCreated));
2451                         $post_date_gmt = iso8601_to_datetime($dateCreated, GMT);
2452                 } else {
2453                         $post_date     = $postdata['post_date'];
2454                         $post_date_gmt = $postdata['post_date_gmt'];
2455                 }
2456
2457                 // We've got all the data -- post it:
2458                 $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');
2459
2460                 $result = wp_update_post($newpost, true);
2461                 if ( is_wp_error( $result ) )
2462                         return new IXR_Error(500, $result->get_error_message());
2463
2464                 if ( !$result )
2465                         return new IXR_Error(500, __('Sorry, your entry could not be edited. Something wrong happened.'));
2466
2467                 // Only posts can be sticky
2468                 if ( $post_type == 'post' && isset( $content_struct['sticky'] ) ) {
2469                         if ( $content_struct['sticky'] == true )
2470                                 stick_post( $post_ID );
2471                         elseif ( $content_struct['sticky'] == false )
2472                                 unstick_post( $post_ID );
2473                 }
2474
2475                 if ( isset($content_struct['custom_fields']) )
2476                         $this->set_custom_fields($post_ID, $content_struct['custom_fields']);
2477
2478                 // Handle enclosures
2479                 $this->add_enclosure_if_new($post_ID, $content_struct['enclosure']);
2480
2481                 $this->attach_uploads( $ID, $post_content );
2482
2483                 logIO('O',"(MW) Edited ! ID: $post_ID");
2484
2485                 return true;
2486         }
2487
2488         /**
2489          * Retrieve post.
2490          *
2491          * @since 1.5.0
2492          *
2493          * @param array $args Method parameters.
2494          * @return array
2495          */
2496         function mw_getPost($args) {
2497
2498                 $this->escape($args);
2499
2500                 $post_ID     = (int) $args[0];
2501                 $username  = $args[1];
2502                 $password   = $args[2];
2503
2504                 if ( !$user = $this->login($username, $password) )
2505                         return $this->error;
2506
2507                 if ( !current_user_can( 'edit_post', $post_ID ) )
2508                         return new IXR_Error( 401, __( 'Sorry, you cannot edit this post.' ) );
2509
2510                 do_action('xmlrpc_call', 'metaWeblog.getPost');
2511
2512                 $postdata = wp_get_single_post($post_ID, ARRAY_A);
2513
2514                 if ($postdata['post_date'] != '') {
2515                         $post_date = mysql2date('Ymd\TH:i:s', $postdata['post_date'], false);
2516                         $post_date_gmt = mysql2date('Ymd\TH:i:s', $postdata['post_date_gmt'], false);
2517
2518                         // For drafts use the GMT version of the post date
2519                         if ( $postdata['post_status'] == 'draft' )
2520                                 $post_date_gmt = get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $postdata['post_date'] ), 'Ymd\TH:i:s' );
2521
2522                         $categories = array();
2523                         $catids = wp_get_post_categories($post_ID);
2524                         foreach($catids as $catid)
2525                                 $categories[] = get_cat_name($catid);
2526
2527                         $tagnames = array();
2528                         $tags = wp_get_post_tags( $post_ID );
2529                         if ( !empty( $tags ) ) {
2530                                 foreach ( $tags as $tag )
2531                                         $tagnames[] = $tag->name;
2532                                 $tagnames = implode( ', ', $tagnames );
2533                         } else {
2534                                 $tagnames = '';
2535                         }
2536
2537                         $post = get_extended($postdata['post_content']);
2538                         $link = post_permalink($postdata['ID']);
2539
2540                         // Get the author info.
2541                         $author = get_userdata($postdata['post_author']);
2542
2543                         $allow_comments = ('open' == $postdata['comment_status']) ? 1 : 0;
2544                         $allow_pings = ('open' == $postdata['ping_status']) ? 1 : 0;
2545
2546                         // Consider future posts as published
2547                         if ( $postdata['post_status'] === 'future' )
2548                                 $postdata['post_status'] = 'publish';
2549
2550                         $sticky = false;
2551                         if ( is_sticky( $post_ID ) )
2552                                 $sticky = true;
2553
2554                         $enclosure = array();
2555                         foreach ( (array) get_post_custom($post_ID) as $key => $val) {
2556                                 if ($key == 'enclosure') {
2557                                         foreach ( (array) $val as $enc ) {
2558                                                 $encdata = split("\n", $enc);
2559                                                 $enclosure['url'] = trim(htmlspecialchars($encdata[0]));
2560                                                 $enclosure['length'] = (int) trim($encdata[1]);
2561                                                 $enclosure['type'] = trim($encdata[2]);
2562                                                 break 2;
2563                                         }
2564                                 }
2565                         }
2566
2567                         $resp = array(
2568                                 'dateCreated' => new IXR_Date($post_date),
2569                                 'userid' => $postdata['post_author'],
2570                                 'postid' => $postdata['ID'],
2571                                 'description' => $post['main'],
2572                                 'title' => $postdata['post_title'],
2573                                 'link' => $link,
2574                                 'permaLink' => $link,
2575                                 // commented out because no other tool seems to use this
2576                                 //            'content' => $entry['post_content'],
2577                                 'categories' => $categories,
2578                                 'mt_excerpt' => $postdata['post_excerpt'],
2579                                 'mt_text_more' => $post['extended'],
2580                                 'mt_allow_comments' => $allow_comments,
2581                                 'mt_allow_pings' => $allow_pings,
2582                                 'mt_keywords' => $tagnames,
2583                                 'wp_slug' => $postdata['post_name'],
2584                                 'wp_password' => $postdata['post_password'],
2585                                 'wp_author_id' => $author->ID,
2586                                 'wp_author_display_name'        => $author->display_name,
2587                                 'date_created_gmt' => new IXR_Date($post_date_gmt),
2588                                 'post_status' => $postdata['post_status'],
2589                                 'custom_fields' => $this->get_custom_fields($post_ID),
2590                                 'sticky' => $sticky
2591                         );
2592
2593                         if ( !empty($enclosure) ) $resp['enclosure'] = $enclosure;
2594
2595                         return $resp;
2596                 } else {
2597                         return new IXR_Error(404, __('Sorry, no such post.'));
2598                 }
2599         }
2600
2601         /**
2602          * Retrieve list of recent posts.
2603          *
2604          * @since 1.5.0
2605          *
2606          * @param array $args Method parameters.
2607          * @return array
2608          */
2609         function mw_getRecentPosts($args) {
2610
2611                 $this->escape($args);
2612
2613                 $blog_ID     = (int) $args[0];
2614                 $username  = $args[1];
2615                 $password   = $args[2];
2616                 $num_posts   = (int) $args[3];
2617
2618                 if ( !$user = $this->login($username, $password) )
2619                         return $this->error;
2620
2621                 do_action('xmlrpc_call', 'metaWeblog.getRecentPosts');
2622
2623                 $posts_list = wp_get_recent_posts($num_posts);
2624
2625                 if ( !$posts_list )
2626                         return array( );
2627
2628                 foreach ($posts_list as $entry) {
2629                         if ( !current_user_can( 'edit_post', $entry['ID'] ) )
2630                                 continue;
2631
2632                         $post_date = mysql2date('Ymd\TH:i:s', $entry['post_date'], false);
2633                         $post_date_gmt = mysql2date('Ymd\TH:i:s', $entry['post_date_gmt'], false);
2634
2635                         // For drafts use the GMT version of the date
2636                         if ( $entry['post_status'] == 'draft' )
2637                                 $post_date_gmt = get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $entry['post_date'] ), 'Ymd\TH:i:s' );
2638
2639                         $categories = array();
2640                         $catids = wp_get_post_categories($entry['ID']);
2641                         foreach( $catids as $catid )
2642                                 $categories[] = get_cat_name($catid);
2643
2644                         $tagnames = array();
2645                         $tags = wp_get_post_tags( $entry['ID'] );
2646                         if ( !empty( $tags ) ) {
2647                                 foreach ( $tags as $tag ) {
2648                                         $tagnames[] = $tag->name;
2649                                 }
2650                                 $tagnames = implode( ', ', $tagnames );
2651                         } else {
2652                                 $tagnames = '';
2653                         }
2654
2655                         $post = get_extended($entry['post_content']);
2656                         $link = post_permalink($entry['ID']);
2657
2658                         // Get the post author info.
2659                         $author = get_userdata($entry['post_author']);
2660
2661                         $allow_comments = ('open' == $entry['comment_status']) ? 1 : 0;
2662                         $allow_pings = ('open' == $entry['ping_status']) ? 1 : 0;
2663
2664                         // Consider future posts as published
2665                         if ( $entry['post_status'] === 'future' )
2666                                 $entry['post_status'] = 'publish';
2667
2668                         $struct[] = array(
2669                                 'dateCreated' => new IXR_Date($post_date),
2670                                 'userid' => $entry['post_author'],
2671                                 'postid' => $entry['ID'],
2672                                 'description' => $post['main'],
2673                                 'title' => $entry['post_title'],
2674                                 'link' => $link,
2675                                 'permaLink' => $link,
2676                                 // commented out because no other tool seems to use this
2677                                 // 'content' => $entry['post_content'],
2678                                 'categories' => $categories,
2679                                 'mt_excerpt' => $entry['post_excerpt'],
2680                                 'mt_text_more' => $post['extended'],
2681                                 'mt_allow_comments' => $allow_comments,
2682                                 'mt_allow_pings' => $allow_pings,
2683                                 'mt_keywords' => $tagnames,
2684                                 'wp_slug' => $entry['post_name'],
2685                                 'wp_password' => $entry['post_password'],
2686                                 'wp_author_id' => $author->ID,
2687                                 'wp_author_display_name' => $author->display_name,
2688                                 'date_created_gmt' => new IXR_Date($post_date_gmt),
2689                                 'post_status' => $entry['post_status'],
2690                                 'custom_fields' => $this->get_custom_fields($entry['ID'])
2691                         );
2692
2693                 }
2694
2695                 $recent_posts = array();
2696                 for ( $j=0; $j<count($struct); $j++ ) {
2697                         array_push($recent_posts, $struct[$j]);
2698                 }
2699
2700                 return $recent_posts;
2701         }
2702
2703         /**
2704          * Retrieve the list of categories on a given blog.
2705          *
2706          * @since 1.5.0
2707          *
2708          * @param array $args Method parameters.
2709          * @return array
2710          */
2711         function mw_getCategories($args) {
2712
2713                 $this->escape($args);
2714
2715                 $blog_ID     = (int) $args[0];
2716                 $username  = $args[1];
2717                 $password   = $args[2];
2718
2719                 if ( !$user = $this->login($username, $password) )
2720                         return $this->error;
2721
2722                 if ( !current_user_can( 'edit_posts' ) )
2723                         return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
2724
2725                 do_action('xmlrpc_call', 'metaWeblog.getCategories');
2726
2727                 $categories_struct = array();
2728
2729                 if ( $cats = get_categories(array('get' => 'all')) ) {
2730                         foreach ( $cats as $cat ) {
2731                                 $struct['categoryId'] = $cat->term_id;
2732                                 $struct['parentId'] = $cat->parent;
2733                                 $struct['description'] = $cat->name;
2734                                 $struct['categoryDescription'] = $cat->description;
2735                                 $struct['categoryName'] = $cat->name;
2736                                 $struct['htmlUrl'] = esc_html(get_category_link($cat->term_id));
2737                                 $struct['rssUrl'] = esc_html(get_category_feed_link($cat->term_id, 'rss2'));
2738
2739                                 $categories_struct[] = $struct;
2740                         }
2741                 }
2742
2743                 return $categories_struct;
2744         }
2745
2746         /**
2747          * Uploads a file, following your settings.
2748          *
2749          * Adapted from a patch by Johann Richard.
2750          *
2751          * @link http://mycvs.org/archives/2004/06/30/file-upload-to-wordpress-in-ecto/
2752          *
2753          * @since 1.5.0
2754          *
2755          * @param array $args Method parameters.
2756          * @return array
2757          */
2758         function mw_newMediaObject($args) {
2759                 global $wpdb;
2760
2761                 $blog_ID     = (int) $args[0];
2762                 $username  = $wpdb->escape($args[1]);
2763                 $password   = $wpdb->escape($args[2]);
2764                 $data        = $args[3];
2765
2766                 $name = sanitize_file_name( $data['name'] );
2767                 $type = $data['type'];
2768                 $bits = $data['bits'];
2769
2770                 logIO('O', '(MW) Received '.strlen($bits).' bytes');
2771
2772                 if ( !$user = $this->login($username, $password) )
2773                         return $this->error;
2774
2775                 do_action('xmlrpc_call', 'metaWeblog.newMediaObject');
2776
2777                 if ( !current_user_can('upload_files') ) {
2778                         logIO('O', '(MW) User does not have upload_files capability');
2779                         $this->error = new IXR_Error(401, __('You are not allowed to upload files to this site.'));
2780                         return $this->error;
2781                 }
2782
2783                 if ( $upload_err = apply_filters( "pre_upload_error", false ) )
2784                         return new IXR_Error(500, $upload_err);
2785
2786                 if ( !empty($data["overwrite"]) && ($data["overwrite"] == true) ) {
2787                         // Get postmeta info on the object.
2788                         $old_file = $wpdb->get_row("
2789                                 SELECT ID
2790                                 FROM {$wpdb->posts}
2791                                 WHERE post_title = '{$name}'
2792                                         AND post_type = 'attachment'
2793                         ");
2794
2795                         // Delete previous file.
2796                         wp_delete_attachment($old_file->ID);
2797
2798                         // Make sure the new name is different by pre-pending the
2799                         // previous post id.
2800                         $filename = preg_replace("/^wpid\d+-/", "", $name);
2801                         $name = "wpid{$old_file->ID}-{$filename}";
2802                 }
2803
2804                 $upload = wp_upload_bits($name, $type, $bits);
2805                 if ( ! empty($upload['error']) ) {
2806                         $errorString = sprintf(__('Could not write file %1$s (%2$s)'), $name, $upload['error']);
2807                         logIO('O', '(MW) ' . $errorString);
2808                         return new IXR_Error(500, $errorString);
2809                 }
2810                 // Construct the attachment array
2811                 // attach to post_id 0
2812                 $post_id = 0;
2813                 $attachment = array(
2814                         'post_title' => $name,
2815                         'post_content' => '',
2816                         'post_type' => 'attachment',
2817                         'post_parent' => $post_id,
2818                         'post_mime_type' => $type,
2819                         'guid' => $upload[ 'url' ]
2820                 );
2821
2822                 // Save the data
2823                 $id = wp_insert_attachment( $attachment, $upload[ 'file' ], $post_id );
2824                 wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $upload['file'] ) );
2825
2826                 return apply_filters( 'wp_handle_upload', array( 'file' => $name, 'url' => $upload[ 'url' ], 'type' => $type ), 'upload' );
2827         }
2828
2829         /* MovableType API functions
2830          * specs on http://www.movabletype.org/docs/mtmanual_programmatic.html
2831          */
2832
2833         /**
2834          * Retrieve the post titles of recent posts.
2835          *
2836          * @since 1.5.0
2837          *
2838          * @param array $args Method parameters.
2839          * @return array
2840          */
2841         function mt_getRecentPostTitles($args) {
2842
2843                 $this->escape($args);
2844
2845                 $blog_ID     = (int) $args[0];
2846                 $username  = $args[1];
2847                 $password   = $args[2];
2848                 $num_posts   = (int) $args[3];
2849
2850                 if ( !$user = $this->login($username, $password) )
2851                         return $this->error;
2852
2853                 do_action('xmlrpc_call', 'mt.getRecentPostTitles');
2854
2855                 $posts_list = wp_get_recent_posts($num_posts);
2856
2857                 if ( !$posts_list ) {
2858                         $this->error = new IXR_Error(500, __('Either there are no posts, or something went wrong.'));
2859                         return $this->error;
2860                 }
2861
2862                 foreach ($posts_list as $entry) {
2863                         if ( !current_user_can( 'edit_post', $entry['ID'] ) )
2864                                 continue;
2865
2866                         $post_date = mysql2date('Ymd\TH:i:s', $entry['post_date'], false);
2867                         $post_date_gmt = mysql2date('Ymd\TH:i:s', $entry['post_date_gmt'], false);
2868
2869                         // For drafts use the GMT version of the date
2870                         if ( $entry['post_status'] == 'draft' )
2871                                 $post_date_gmt = get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $entry['post_date'] ), 'Ymd\TH:i:s' );
2872
2873                         $struct[] = array(
2874                                 'dateCreated' => new IXR_Date($post_date),
2875                                 'userid' => $entry['post_author'],
2876                                 'postid' => $entry['ID'],
2877                                 'title' => $entry['post_title'],
2878                                 'date_created_gmt' => new IXR_Date($post_date_gmt)
2879                         );
2880
2881                 }
2882
2883                 $recent_posts = array();
2884                 for ( $j=0; $j<count($struct); $j++ ) {
2885                         array_push($recent_posts, $struct[$j]);
2886                 }
2887
2888                 return $recent_posts;
2889         }
2890
2891         /**
2892          * Retrieve list of all categories on blog.
2893          *
2894          * @since 1.5.0
2895          *
2896          * @param array $args Method parameters.
2897          * @return array
2898          */
2899         function mt_getCategoryList($args) {
2900
2901                 $this->escape($args);
2902
2903                 $blog_ID     = (int) $args[0];
2904                 $username  = $args[1];
2905                 $password   = $args[2];
2906
2907                 if ( !$user = $this->login($username, $password) )
2908                         return $this->error;
2909
2910                 if ( !current_user_can( 'edit_posts' ) )
2911                         return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
2912
2913                 do_action('xmlrpc_call', 'mt.getCategoryList');
2914
2915                 $categories_struct = array();
2916
2917                 if ( $cats = get_categories(array('hide_empty' => 0, 'hierarchical' => 0)) ) {
2918                         foreach ( $cats as $cat ) {
2919                                 $struct['categoryId'] = $cat->term_id;
2920                                 $struct['categoryName'] = $cat->name;
2921
2922                                 $categories_struct[] = $struct;
2923                         }
2924                 }
2925
2926                 return $categories_struct;
2927         }
2928
2929         /**
2930          * Retrieve post categories.
2931          *
2932          * @since 1.5.0
2933          *
2934          * @param array $args Method parameters.
2935          * @return array
2936          */
2937         function mt_getPostCategories($args) {
2938
2939                 $this->escape($args);
2940
2941                 $post_ID     = (int) $args[0];
2942                 $username  = $args[1];
2943                 $password   = $args[2];
2944
2945                 if ( !$user = $this->login($username, $password) )
2946                         return $this->error;
2947
2948                 if ( !current_user_can( 'edit_post', $post_ID ) )
2949                         return new IXR_Error( 401, __( 'Sorry, you can not edit this post.' ) );
2950
2951                 do_action('xmlrpc_call', 'mt.getPostCategories');
2952
2953                 $categories = array();
2954                 $catids = wp_get_post_categories(intval($post_ID));
2955                 // first listed category will be the primary category
2956                 $isPrimary = true;
2957                 foreach ( $catids as $catid ) {
2958                         $categories[] = array(
2959                                 'categoryName' => get_cat_name($catid),
2960                                 'categoryId' => (string) $catid,
2961                                 'isPrimary' => $isPrimary
2962                         );
2963                         $isPrimary = false;
2964                 }
2965
2966                 return $categories;
2967         }
2968
2969         /**
2970          * Sets categories for a post.
2971          *
2972          * @since 1.5.0
2973          *
2974          * @param array $args Method parameters.
2975          * @return bool True on success.
2976          */
2977         function mt_setPostCategories($args) {
2978
2979                 $this->escape($args);
2980
2981                 $post_ID     = (int) $args[0];
2982                 $username  = $args[1];
2983                 $password   = $args[2];
2984                 $categories  = $args[3];
2985
2986                 if ( !$user = $this->login($username, $password) )
2987                         return $this->error;
2988
2989                 do_action('xmlrpc_call', 'mt.setPostCategories');
2990
2991                 if ( !current_user_can('edit_post', $post_ID) )
2992                         return new IXR_Error(401, __('Sorry, you cannot edit this post.'));
2993
2994                 foreach ( $categories as $cat ) {
2995                         $catids[] = $cat['categoryId'];
2996                 }
2997
2998                 wp_set_post_categories($post_ID, $catids);
2999
3000                 return true;
3001         }
3002
3003         /**
3004          * Retrieve an array of methods supported by this server.
3005          *
3006          * @since 1.5.0
3007          *
3008          * @param array $args Method parameters.
3009          * @return array
3010          */
3011         function mt_supportedMethods($args) {
3012
3013                 do_action('xmlrpc_call', 'mt.supportedMethods');
3014
3015                 $supported_methods = array();
3016                 foreach ( $this->methods as $key => $value ) {
3017                         $supported_methods[] = $key;
3018                 }
3019
3020                 return $supported_methods;
3021         }
3022
3023         /**
3024          * Retrieve an empty array because we don't support per-post text filters.
3025          *
3026          * @since 1.5.0
3027          *
3028          * @param array $args Method parameters.
3029          */
3030         function mt_supportedTextFilters($args) {
3031                 do_action('xmlrpc_call', 'mt.supportedTextFilters');
3032                 return apply_filters('xmlrpc_text_filters', array());
3033         }
3034
3035         /**
3036          * Retrieve trackbacks sent to a given post.
3037          *
3038          * @since 1.5.0
3039          *
3040          * @param array $args Method parameters.
3041          * @return mixed
3042          */
3043         function mt_getTrackbackPings($args) {
3044
3045                 global $wpdb;
3046
3047                 $post_ID = intval($args);
3048
3049                 do_action('xmlrpc_call', 'mt.getTrackbackPings');
3050
3051                 $actual_post = wp_get_single_post($post_ID, ARRAY_A);
3052
3053                 if ( !$actual_post )
3054                         return new IXR_Error(404, __('Sorry, no such post.'));
3055
3056                 $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) );
3057
3058                 if ( !$comments )
3059                         return array();
3060
3061                 $trackback_pings = array();
3062                 foreach ( $comments as $comment ) {
3063                         if ( 'trackback' == $comment->comment_type ) {
3064                                 $content = $comment->comment_content;
3065                                 $title = substr($content, 8, (strpos($content, '</strong>') - 8));
3066                                 $trackback_pings[] = array(
3067                                         'pingTitle' => $title,
3068                                         'pingURL'   => $comment->comment_author_url,
3069                                         'pingIP'    => $comment->comment_author_IP
3070                                 );
3071                         }
3072                 }
3073
3074                 return $trackback_pings;
3075         }
3076
3077         /**
3078          * Sets a post's publish status to 'publish'.
3079          *
3080          * @since 1.5.0
3081          *
3082          * @param array $args Method parameters.
3083          * @return int
3084          */
3085         function mt_publishPost($args) {
3086
3087                 $this->escape($args);
3088
3089                 $post_ID     = (int) $args[0];
3090                 $username  = $args[1];
3091                 $password   = $args[2];
3092
3093                 if ( !$user = $this->login($username, $password) )
3094                         return $this->error;
3095
3096                 do_action('xmlrpc_call', 'mt.publishPost');
3097
3098                 if ( !current_user_can('edit_post', $post_ID) )
3099                         return new IXR_Error(401, __('Sorry, you cannot edit this post.'));
3100
3101                 $postdata = wp_get_single_post($post_ID,ARRAY_A);
3102
3103                 $postdata['post_status'] = 'publish';
3104
3105                 // retain old cats
3106                 $cats = wp_get_post_categories($post_ID);
3107                 $postdata['post_category'] = $cats;
3108                 $this->escape($postdata);
3109
3110                 $result = wp_update_post($postdata);
3111
3112                 return $result;
3113         }
3114
3115         /* PingBack functions
3116          * specs on www.hixie.ch/specs/pingback/pingback
3117          */
3118
3119         /**
3120          * Retrieves a pingback and registers it.
3121          *
3122          * @since 1.5.0
3123          *
3124          * @param array $args Method parameters.
3125          * @return array
3126          */
3127         function pingback_ping($args) {
3128                 global $wpdb;
3129
3130                 do_action('xmlrpc_call', 'pingback.ping');
3131
3132                 $this->escape($args);
3133
3134                 $pagelinkedfrom = $args[0];
3135                 $pagelinkedto   = $args[1];
3136
3137                 $title = '';
3138
3139                 $pagelinkedfrom = str_replace('&amp;', '&', $pagelinkedfrom);
3140                 $pagelinkedto = str_replace('&amp;', '&', $pagelinkedto);
3141                 $pagelinkedto = str_replace('&', '&amp;', $pagelinkedto);
3142
3143                 // Check if the page linked to is in our site
3144                 $pos1 = strpos($pagelinkedto, str_replace(array('http://www.','http://','https://www.','https://'), '', get_option('home')));
3145                 if ( !$pos1 )
3146                         return new IXR_Error(0, __('Is there no link to us?'));
3147
3148                 // let's find which post is linked to
3149                 // FIXME: does url_to_postid() cover all these cases already?
3150                 //        if so, then let's use it and drop the old code.
3151                 $urltest = parse_url($pagelinkedto);
3152                 if ( $post_ID = url_to_postid($pagelinkedto) ) {
3153                         $way = 'url_to_postid()';
3154                 } elseif ( preg_match('#p/[0-9]{1,}#', $urltest['path'], $match) ) {
3155                         // the path defines the post_ID (archives/p/XXXX)
3156                         $blah = explode('/', $match[0]);
3157                         $post_ID = (int) $blah[1];
3158                         $way = 'from the path';
3159                 } elseif ( preg_match('#p=[0-9]{1,}#', $urltest['query'], $match) ) {
3160                         // the querystring defines the post_ID (?p=XXXX)
3161                         $blah = explode('=', $match[0]);
3162                         $post_ID = (int) $blah[1];
3163                         $way = 'from the querystring';
3164                 } elseif ( isset($urltest['fragment']) ) {
3165                         // an #anchor is there, it's either...
3166                         if ( intval($urltest['fragment']) ) {
3167                                 // ...an integer #XXXX (simpliest case)
3168                                 $post_ID = (int) $urltest['fragment'];
3169                                 $way = 'from the fragment (numeric)';
3170                         } elseif ( preg_match('/post-[0-9]+/',$urltest['fragment']) ) {
3171                                 // ...a post id in the form 'post-###'
3172                                 $post_ID = preg_replace('/[^0-9]+/', '', $urltest['fragment']);
3173                                 $way = 'from the fragment (post-###)';
3174                         } elseif ( is_string($urltest['fragment']) ) {
3175                                 // ...or a string #title, a little more complicated
3176                                 $title = preg_replace('/[^a-z0-9]/i', '.', $urltest['fragment']);
3177                                 $sql = $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE post_title RLIKE %s", $title);
3178                                 if (! ($post_ID = $wpdb->get_var($sql)) ) {
3179                                         // returning unknown error '0' is better than die()ing
3180                                         return new IXR_Error(0, '');
3181                                 }
3182                                 $way = 'from the fragment (title)';
3183                         }
3184                 } else {
3185                         // TODO: Attempt to extract a post ID from the given URL
3186                         return new IXR_Error(33, __('The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.'));
3187                 }
3188                 $post_ID = (int) $post_ID;
3189
3190
3191                 logIO("O","(PB) URL='$pagelinkedto' ID='$post_ID' Found='$way'");
3192
3193                 $post = get_post($post_ID);
3194
3195                 if ( !$post ) // Post_ID not found
3196                         return new IXR_Error(33, __('The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.'));
3197
3198                 if ( $post_ID == url_to_postid($pagelinkedfrom) )
3199                         return new IXR_Error(0, __('The source URL and the target URL cannot both point to the same resource.'));
3200
3201                 // Check if pings are on
3202                 if ( !pings_open($post) )
3203                         return new IXR_Error(33, __('The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.'));
3204
3205                 // Let's check that the remote site didn't already pingback this entry
3206                 if ( $wpdb->get_results( $wpdb->prepare("SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_author_url = %s", $post_ID, $pagelinkedfrom) ) )
3207                         return new IXR_Error( 48, __( 'The pingback has already been registered.' ) );
3208
3209                 // very stupid, but gives time to the 'from' server to publish !
3210                 sleep(1);
3211
3212                 // Let's check the remote site
3213                 $linea = wp_remote_fopen( $pagelinkedfrom );
3214                 if ( !$linea )
3215                         return new IXR_Error(16, __('The source URL does not exist.'));
3216
3217                 $linea = apply_filters('pre_remote_source', $linea, $pagelinkedto);
3218
3219                 // Work around bug in strip_tags():
3220                 $linea = str_replace('<!DOC', '<DOC', $linea);
3221                 $linea = preg_replace( '/[\s\r\n\t]+/', ' ', $linea ); // normalize spaces
3222                 $linea = preg_replace( "/ <(h1|h2|h3|h4|h5|h6|p|th|td|li|dt|dd|pre|caption|input|textarea|button|body)[^>]*>/", "\n\n", $linea );
3223
3224                 preg_match('|<title>([^<]*?)</title>|is', $linea, $matchtitle);
3225                 $title = $matchtitle[1];
3226                 if ( empty( $title ) )
3227                         return new IXR_Error(32, __('We cannot find a title on that page.'));
3228
3229                 $linea = strip_tags( $linea, '<a>' ); // just keep the tag we need
3230
3231                 $p = explode( "\n\n", $linea );
3232
3233                 $preg_target = preg_quote($pagelinkedto, '|');
3234
3235                 foreach ( $p as $para ) {
3236                         if ( strpos($para, $pagelinkedto) !== false ) { // it exists, but is it a link?
3237                                 preg_match("|<a[^>]+?".$preg_target."[^>]*>([^>]+?)</a>|", $para, $context);
3238
3239                                 // If the URL isn't in a link context, keep looking
3240                                 if ( empty($context) )
3241                                         continue;
3242
3243                                 // We're going to use this fake tag to mark the context in a bit
3244                                 // the marker is needed in case the link text appears more than once in the paragraph
3245                                 $excerpt = preg_replace('|\</?wpcontext\>|', '', $para);
3246
3247                                 // prevent really long link text
3248                                 if ( strlen($context[1]) > 100 )
3249                                         $context[1] = substr($context[1], 0, 100) . '...';
3250
3251                                 $marker = '<wpcontext>'.$context[1].'</wpcontext>';    // set up our marker
3252                                 $excerpt= str_replace($context[0], $marker, $excerpt); // swap out the link for our marker
3253                                 $excerpt = strip_tags($excerpt, '<wpcontext>');        // strip all tags but our context marker
3254                                 $excerpt = trim($excerpt);
3255                                 $preg_marker = preg_quote($marker, '|');
3256                                 $excerpt = preg_replace("|.*?\s(.{0,100}$preg_marker.{0,100})\s.*|s", '$1', $excerpt);
3257                                 $excerpt = strip_tags($excerpt); // YES, again, to remove the marker wrapper
3258                                 break;
3259                         }
3260                 }
3261
3262                 if ( empty($context) ) // Link to target not found
3263                         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.'));
3264
3265                 $pagelinkedfrom = str_replace('&', '&amp;', $pagelinkedfrom);
3266
3267                 $context = '[...] ' . esc_html( $excerpt ) . ' [...]';
3268                 $pagelinkedfrom = $wpdb->escape( $pagelinkedfrom );
3269
3270                 $comment_post_ID = (int) $post_ID;
3271                 $comment_author = $title;
3272                 $this->escape($comment_author);
3273                 $comment_author_url = $pagelinkedfrom;
3274                 $comment_content = $context;
3275                 $this->escape($comment_content);
3276                 $comment_type = 'pingback';
3277
3278                 $commentdata = compact('comment_post_ID', 'comment_author', 'comment_author_url', 'comment_content', 'comment_type');
3279
3280                 $comment_ID = wp_new_comment($commentdata);
3281                 do_action('pingback_post', $comment_ID);
3282
3283                 return sprintf(__('Pingback from %1$s to %2$s registered. Keep the web talking! :-)'), $pagelinkedfrom, $pagelinkedto);
3284         }
3285
3286         /**
3287          * Retrieve array of URLs that pingbacked the given URL.
3288          *
3289          * Specs on http://www.aquarionics.com/misc/archives/blogite/0198.html
3290          *
3291          * @since 1.5.0
3292          *
3293          * @param array $args Method parameters.
3294          * @return array
3295          */
3296         function pingback_extensions_getPingbacks($args) {
3297
3298                 global $wpdb;
3299
3300                 do_action('xmlrpc_call', 'pingback.extensions.getPingbacks');
3301
3302                 $this->escape($args);
3303
3304                 $url = $args;
3305
3306                 $post_ID = url_to_postid($url);
3307                 if ( !$post_ID ) {
3308                         // We aren't sure that the resource is available and/or pingback enabled
3309                         return new IXR_Error(33, __('The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.'));
3310                 }
3311
3312                 $actual_post = wp_get_single_post($post_ID, ARRAY_A);
3313
3314                 if ( !$actual_post ) {
3315                         // No such post = resource not found
3316                         return new IXR_Error(32, __('The specified target URL does not exist.'));
3317                 }
3318
3319                 $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) );
3320
3321                 if ( !$comments )
3322                         return array();
3323
3324                 $pingbacks = array();
3325                 foreach ( $comments as $comment ) {
3326                         if ( 'pingback' == $comment->comment_type )
3327                                 $pingbacks[] = $comment->comment_author_url;
3328                 }
3329
3330                 return $pingbacks;
3331         }
3332 }
3333
3334 $wp_xmlrpc_server = new wp_xmlrpc_server();
3335 $wp_xmlrpc_server->serve_request();
3336 ?>