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