Wordpress 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 compatability 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 ( strpos($meta['meta_key'], '_wp_') === 0 ) {
238                                 continue;
239                         }
240
241                         $custom_fields[] = array(
242                                 "id"    => $meta['meta_id'],
243                                 "key"   => $meta['meta_key'],
244                                 "value" => $meta['meta_value']
245                         );
246                 }
247
248                 return $custom_fields;
249         }
250
251         /**
252          * Set custom fields for post.
253          *
254          * @since 2.5.0
255          *
256          * @param int $post_id Post ID.
257          * @param array $fields Custom fields.
258          */
259         function set_custom_fields($post_id, $fields) {
260                 $post_id = (int) $post_id;
261
262                 foreach ( (array) $fields as $meta ) {
263                         if ( isset($meta['id']) ) {
264                                 $meta['id'] = (int) $meta['id'];
265
266                                 if ( isset($meta['key']) ) {
267                                         update_meta($meta['id'], $meta['key'], $meta['value']);
268                                 }
269                                 else {
270                                         delete_meta($meta['id']);
271                                 }
272                         }
273                         else {
274                                 $_POST['metakeyinput'] = $meta['key'];
275                                 $_POST['metavalue'] = $meta['value'];
276                                 add_meta($post_id);
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'                  => $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 formated 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 ( !current_user_can( 'edit_comment', $comment_ID ) )
1138                         return new IXR_Error( 403, __( 'You are not allowed to moderate comments on this site.' ) );
1139
1140                 do_action('xmlrpc_call', 'wp.deleteComment');
1141
1142                 if ( ! get_comment($comment_ID) )
1143                         return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
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 ( !current_user_can( 'edit_comment', $comment_ID ) )
1188                         return new IXR_Error( 403, __( 'You are not allowed to moderate comments on this site.' ) );
1189
1190                 do_action('xmlrpc_call', 'wp.editComment');
1191
1192                 if ( ! get_comment($comment_ID) )
1193                         return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
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. Assocciative 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           * Retrives 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                 // We've got all the data -- post it:
2398                 $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');
2399
2400                 $post_ID = wp_insert_post($postdata, true);
2401                 if ( is_wp_error( $post_ID ) )
2402                         return new IXR_Error(500, $post_ID->get_error_message());
2403
2404                 if ( !$post_ID )
2405                         return new IXR_Error(500, __('Sorry, your entry could not be posted. Something wrong happened.'));
2406
2407                 // Only posts can be sticky
2408                 if ( $post_type == 'post' && isset( $content_struct['sticky'] ) ) {
2409                         if ( $content_struct['sticky'] == true )
2410                                 stick_post( $post_ID );
2411                         elseif ( $content_struct['sticky'] == false )
2412                                 unstick_post( $post_ID );
2413                 }
2414
2415                 if ( isset($content_struct['custom_fields']) )
2416                         $this->set_custom_fields($post_ID, $content_struct['custom_fields']);
2417
2418                 // Handle enclosures
2419                 $thisEnclosure = isset($content_struct['enclosure']) ? $content_struct['enclosure'] : null;
2420                 $this->add_enclosure_if_new($post_ID, $thisEnclosure);
2421
2422                 $this->attach_uploads( $post_ID, $post_content );
2423
2424                 // Handle post formats if assigned, value is validated earlier
2425                 // in this function
2426                 if ( isset( $content_struct['wp_post_format'] ) )
2427                         wp_set_post_terms( $post_ID, array( 'post-format-' . $content_struct['wp_post_format'] ), 'post_format' );
2428
2429                 logIO('O', "Posted ! ID: $post_ID");
2430
2431                 return strval($post_ID);
2432         }
2433
2434         function add_enclosure_if_new($post_ID, $enclosure) {
2435                 if ( is_array( $enclosure ) && isset( $enclosure['url'] ) && isset( $enclosure['length'] ) && isset( $enclosure['type'] ) ) {
2436
2437                         $encstring = $enclosure['url'] . "\n" . $enclosure['length'] . "\n" . $enclosure['type'];
2438                         $found = false;
2439                         foreach ( (array) get_post_custom($post_ID) as $key => $val) {
2440                                 if ($key == 'enclosure') {
2441                                         foreach ( (array) $val as $enc ) {
2442                                                 if ($enc == $encstring) {
2443                                                         $found = true;
2444                                                         break 2;
2445                                                 }
2446                                         }
2447                                 }
2448                         }
2449                         if (!$found)
2450                                 add_post_meta( $post_ID, 'enclosure', $encstring );
2451                 }
2452         }
2453
2454         /**
2455          * Attach upload to a post.
2456          *
2457          * @since 2.1.0
2458          *
2459          * @param int $post_ID Post ID.
2460          * @param string $post_content Post Content for attachment.
2461          */
2462         function attach_uploads( $post_ID, $post_content ) {
2463                 global $wpdb;
2464
2465                 // find any unattached files
2466                 $attachments = $wpdb->get_results( "SELECT ID, guid FROM {$wpdb->posts} WHERE post_parent = '0' AND post_type = 'attachment'" );
2467                 if ( is_array( $attachments ) ) {
2468                         foreach ( $attachments as $file ) {
2469                                 if ( strpos( $post_content, $file->guid ) !== false )
2470                                         $wpdb->update($wpdb->posts, array('post_parent' => $post_ID), array('ID' => $file->ID) );
2471                         }
2472                 }
2473         }
2474
2475         /**
2476          * Edit a post.
2477          *
2478          * @since 1.5.0
2479          *
2480          * @param array $args Method parameters.
2481          * @return bool True on success.
2482          */
2483         function mw_editPost($args) {
2484
2485                 $this->escape($args);
2486
2487                 $post_ID     = (int) $args[0];
2488                 $username  = $args[1];
2489                 $password   = $args[2];
2490                 $content_struct = $args[3];
2491                 $publish     = $args[4];
2492
2493                 if ( !$user = $this->login($username, $password) )
2494                         return $this->error;
2495
2496                 do_action('xmlrpc_call', 'metaWeblog.editPost');
2497
2498                 $cap = ( $publish ) ? 'publish_posts' : 'edit_posts';
2499                 $error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
2500                 $post_type = 'post';
2501                 $page_template = '';
2502                 if ( !empty( $content_struct['post_type'] ) ) {
2503                         if ( $content_struct['post_type'] == 'page' ) {
2504                                 if ( $publish || 'publish' == $content_struct['page_status'] )
2505                                         $cap  = 'publish_pages';
2506                                 else
2507                                         $cap = 'edit_pages';
2508                                 $error_message = __( 'Sorry, you are not allowed to publish pages on this site.' );
2509                                 $post_type = 'page';
2510                                 if ( !empty( $content_struct['wp_page_template'] ) )
2511                                         $page_template = $content_struct['wp_page_template'];
2512                         } elseif ( $content_struct['post_type'] == 'post' ) {
2513                                 if ( $publish || 'publish' == $content_struct['post_status'] )
2514                                         $cap  = 'publish_posts';
2515                                 else
2516                                         $cap = 'edit_posts';
2517                                 $error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
2518                                 $post_type = 'post';
2519                         } else {
2520                                 // No other post_type values are allowed here
2521                                 return new IXR_Error( 401, __( 'Invalid post type.' ) );
2522                         }
2523                 } else {
2524                         if ( $publish || 'publish' == $content_struct['post_status'] )
2525                                 $cap  = 'publish_posts';
2526                         else
2527                                 $cap = 'edit_posts';
2528                         $error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
2529                         $post_type = 'post';
2530                 }
2531
2532                 if ( !current_user_can( $cap ) )
2533                         return new IXR_Error( 401, $error_message );
2534
2535                 // Check for a valid post format if one was given
2536                 if ( isset( $content_struct['wp_post_format'] ) ) {
2537                         $content_struct['wp_post_format'] = sanitize_key( $content_struct['wp_post_format'] );
2538                         if ( !array_key_exists( $content_struct['wp_post_format'], get_post_format_strings() ) ) {
2539                                 return new IXR_Error( 404, __( 'Invalid post format' ) );
2540                         }
2541                 }
2542
2543                 $postdata = wp_get_single_post($post_ID, ARRAY_A);
2544
2545                 // If there is no post data for the give post id, stop
2546                 // now and return an error.  Other wise a new post will be
2547                 // created (which was the old behavior).
2548                 if ( empty($postdata["ID"]) )
2549                         return(new IXR_Error(404, __('Invalid post ID.')));
2550
2551                 $this->escape($postdata);
2552                 extract($postdata, EXTR_SKIP);
2553
2554                 // Let WordPress manage slug if none was provided.
2555                 $post_name = "";
2556                 $post_name = $postdata['post_name'];
2557                 if ( isset($content_struct['wp_slug']) )
2558                         $post_name = $content_struct['wp_slug'];
2559
2560                 // Only use a password if one was given.
2561                 if ( isset($content_struct['wp_password']) )
2562                         $post_password = $content_struct['wp_password'];
2563
2564                 // Only set a post parent if one was given.
2565                 if ( isset($content_struct['wp_page_parent_id']) )
2566                         $post_parent = $content_struct['wp_page_parent_id'];
2567
2568                 // Only set the menu_order if it was given.
2569                 if ( isset($content_struct['wp_page_order']) )
2570                         $menu_order = $content_struct['wp_page_order'];
2571
2572                 $post_author = $postdata['post_author'];
2573
2574                 // Only set the post_author if one is set.
2575                 if ( isset($content_struct['wp_author_id']) && ($user->ID != $content_struct['wp_author_id']) ) {
2576                         switch ( $post_type ) {
2577                                 case 'post':
2578                                         if ( !current_user_can('edit_others_posts') )
2579                                                 return(new IXR_Error(401, __('You are not allowed to change the post author as this user.')));
2580                                         break;
2581                                 case 'page':
2582                                         if ( !current_user_can('edit_others_pages') )
2583                                                 return(new IXR_Error(401, __('You are not allowed to change the page author as this user.')));
2584                                         break;
2585                                 default:
2586                                         return(new IXR_Error(401, __('Invalid post type.')));
2587                                         break;
2588                         }
2589                         $post_author = $content_struct['wp_author_id'];
2590                 }
2591
2592                 if ( isset($content_struct['mt_allow_comments']) ) {
2593                         if ( !is_numeric($content_struct['mt_allow_comments']) ) {
2594                                 switch ( $content_struct['mt_allow_comments'] ) {
2595                                         case 'closed':
2596                                                 $comment_status = 'closed';
2597                                                 break;
2598                                         case 'open':
2599                                                 $comment_status = 'open';
2600                                                 break;
2601                                         default:
2602                                                 $comment_status = get_option('default_comment_status');
2603                                                 break;
2604                                 }
2605                         } else {
2606                                 switch ( (int) $content_struct['mt_allow_comments'] ) {
2607                                         case 0:
2608                                         case 2:
2609                                                 $comment_status = 'closed';
2610                                                 break;
2611                                         case 1:
2612                                                 $comment_status = 'open';
2613                                                 break;
2614                                         default:
2615                                                 $comment_status = get_option('default_comment_status');
2616                                                 break;
2617                                 }
2618                         }
2619                 }
2620
2621                 if ( isset($content_struct['mt_allow_pings']) ) {
2622                         if ( !is_numeric($content_struct['mt_allow_pings']) ) {
2623                                 switch ( $content_struct['mt_allow_pings'] ) {
2624                                         case 'closed':
2625                                                 $ping_status = 'closed';
2626                                                 break;
2627                                         case 'open':
2628                                                 $ping_status = 'open';
2629                                                 break;
2630                                         default:
2631                                                 $ping_status = get_option('default_ping_status');
2632                                                 break;
2633                                 }
2634                         } else {
2635                                 switch ( (int) $content_struct["mt_allow_pings"] ) {
2636                                         case 0:
2637                                                 $ping_status = 'closed';
2638                                                 break;
2639                                         case 1:
2640                                                 $ping_status = 'open';
2641                                                 break;
2642                                         default:
2643                                                 $ping_status = get_option('default_ping_status');
2644                                                 break;
2645                                 }
2646                         }
2647                 }
2648
2649                 $post_title = isset( $content_struct['title'] ) ? $content_struct['title'] : null;
2650                 $post_content = isset( $content_struct['description'] ) ? $content_struct['description'] : null;
2651
2652                 $post_category = array();
2653                 if ( isset( $content_struct['categories'] ) ) {
2654                         $catnames = $content_struct['categories'];
2655                         if ( is_array($catnames) ) {
2656                                 foreach ($catnames as $cat) {
2657                                         $post_category[] = get_cat_ID($cat);
2658                                 }
2659                         }
2660                 }
2661
2662                 $post_excerpt = isset( $content_struct['mt_excerpt'] ) ? $content_struct['mt_excerpt'] : null;
2663                 $post_more = isset( $content_struct['mt_text_more'] ) ? $content_struct['mt_text_more'] : null;
2664
2665                 $post_status = $publish ? 'publish' : 'draft';
2666                 if ( isset( $content_struct["{$post_type}_status"] ) ) {
2667                         switch( $content_struct["{$post_type}_status"] ) {
2668                                 case 'draft':
2669                                 case 'pending':
2670                                 case 'private':
2671                                 case 'publish':
2672                                         $post_status = $content_struct["{$post_type}_status"];
2673                                         break;
2674                                 default:
2675                                         $post_status = $publish ? 'publish' : 'draft';
2676                                         break;
2677                         }
2678                 }
2679
2680                 $tags_input = isset( $content_struct['mt_keywords'] ) ? $content_struct['mt_keywords'] : null;
2681
2682                 if ( ('publish' == $post_status) ) {
2683                         if ( ( 'page' == $post_type ) && !current_user_can('publish_pages') )
2684                                 return new IXR_Error(401, __('Sorry, you do not have the right to publish this page.'));
2685                         else if ( !current_user_can('publish_posts') )
2686                                 return new IXR_Error(401, __('Sorry, you do not have the right to publish this post.'));
2687                 }
2688
2689                 if ( $post_more )
2690                         $post_content = $post_content . "<!--more-->" . $post_more;
2691
2692                 $to_ping = null;
2693                 if ( isset( $content_struct['mt_tb_ping_urls'] ) ) {
2694                         $to_ping = $content_struct['mt_tb_ping_urls'];
2695                         if ( is_array($to_ping) )
2696                                 $to_ping = implode(' ', $to_ping);
2697                 }
2698
2699                 // Do some timestamp voodoo
2700                 if ( !empty( $content_struct['date_created_gmt'] ) )
2701                         $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
2702                 elseif ( !empty( $content_struct['dateCreated']) )
2703                         $dateCreated = $content_struct['dateCreated']->getIso();
2704
2705                 if ( !empty( $dateCreated ) ) {
2706                         $post_date = get_date_from_gmt(iso8601_to_datetime($dateCreated));
2707                         $post_date_gmt = iso8601_to_datetime($dateCreated, 'GMT');
2708                 } else {
2709                         $post_date     = $postdata['post_date'];
2710                         $post_date_gmt = $postdata['post_date_gmt'];
2711                 }
2712
2713                 // We've got all the data -- post it:
2714                 $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');
2715
2716                 $result = wp_update_post($newpost, true);
2717                 if ( is_wp_error( $result ) )
2718                         return new IXR_Error(500, $result->get_error_message());
2719
2720                 if ( !$result )
2721                         return new IXR_Error(500, __('Sorry, your entry could not be edited. Something wrong happened.'));
2722
2723                 // Only posts can be sticky
2724                 if ( $post_type == 'post' && isset( $content_struct['sticky'] ) ) {
2725                         if ( $content_struct['sticky'] == true )
2726                                 stick_post( $post_ID );
2727                         elseif ( $content_struct['sticky'] == false )
2728                                 unstick_post( $post_ID );
2729                 }
2730
2731                 if ( isset($content_struct['custom_fields']) )
2732                         $this->set_custom_fields($post_ID, $content_struct['custom_fields']);
2733
2734                 // Handle enclosures
2735                 $thisEnclosure = isset($content_struct['enclosure']) ? $content_struct['enclosure'] : null;
2736                 $this->add_enclosure_if_new($post_ID, $thisEnclosure);
2737
2738                 $this->attach_uploads( $ID, $post_content );
2739
2740                 // Handle post formats if assigned, validation is handled
2741                 // earlier in this function
2742                 if ( isset( $content_struct['wp_post_format'] ) )
2743                         wp_set_post_terms( $post_ID, array( 'post-format-' . $content_struct['wp_post_format'] ), 'post_format' );
2744
2745                 logIO('O',"(MW) Edited ! ID: $post_ID");
2746
2747                 return true;
2748         }
2749
2750         /**
2751          * Retrieve post.
2752          *
2753          * @since 1.5.0
2754          *
2755          * @param array $args Method parameters.
2756          * @return array
2757          */
2758         function mw_getPost($args) {
2759
2760                 $this->escape($args);
2761
2762                 $post_ID     = (int) $args[0];
2763                 $username  = $args[1];
2764                 $password   = $args[2];
2765
2766                 if ( !$user = $this->login($username, $password) )
2767                         return $this->error;
2768
2769                 if ( !current_user_can( 'edit_post', $post_ID ) )
2770                         return new IXR_Error( 401, __( 'Sorry, you cannot edit this post.' ) );
2771
2772                 do_action('xmlrpc_call', 'metaWeblog.getPost');
2773
2774                 $postdata = wp_get_single_post($post_ID, ARRAY_A);
2775
2776                 if ($postdata['post_date'] != '') {
2777                         $post_date = mysql2date('Ymd\TH:i:s', $postdata['post_date'], false);
2778                         $post_date_gmt = mysql2date('Ymd\TH:i:s', $postdata['post_date_gmt'], false);
2779
2780                         // For drafts use the GMT version of the post date
2781                         if ( $postdata['post_status'] == 'draft' )
2782                                 $post_date_gmt = get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $postdata['post_date'] ), 'Ymd\TH:i:s' );
2783
2784                         $categories = array();
2785                         $catids = wp_get_post_categories($post_ID);
2786                         foreach($catids as $catid)
2787                                 $categories[] = get_cat_name($catid);
2788
2789                         $tagnames = array();
2790                         $tags = wp_get_post_tags( $post_ID );
2791                         if ( !empty( $tags ) ) {
2792                                 foreach ( $tags as $tag )
2793                                         $tagnames[] = $tag->name;
2794                                 $tagnames = implode( ', ', $tagnames );
2795                         } else {
2796                                 $tagnames = '';
2797                         }
2798
2799                         $post = get_extended($postdata['post_content']);
2800                         $link = post_permalink($postdata['ID']);
2801
2802                         // Get the author info.
2803                         $author = get_userdata($postdata['post_author']);
2804
2805                         $allow_comments = ('open' == $postdata['comment_status']) ? 1 : 0;
2806                         $allow_pings = ('open' == $postdata['ping_status']) ? 1 : 0;
2807
2808                         // Consider future posts as published
2809                         if ( $postdata['post_status'] === 'future' )
2810                                 $postdata['post_status'] = 'publish';
2811
2812                         // Get post format
2813                         $post_format = get_post_format( $post_ID );
2814                         if ( empty( $post_format ) )
2815                                 $post_format = 'standard';
2816
2817                         $sticky = false;
2818                         if ( is_sticky( $post_ID ) )
2819                                 $sticky = true;
2820
2821                         $enclosure = array();
2822                         foreach ( (array) get_post_custom($post_ID) as $key => $val) {
2823                                 if ($key == 'enclosure') {
2824                                         foreach ( (array) $val as $enc ) {
2825                                                 $encdata = split("\n", $enc);
2826                                                 $enclosure['url'] = trim(htmlspecialchars($encdata[0]));
2827                                                 $enclosure['length'] = (int) trim($encdata[1]);
2828                                                 $enclosure['type'] = trim($encdata[2]);
2829                                                 break 2;
2830                                         }
2831                                 }
2832                         }
2833
2834                         $resp = array(
2835                                 'dateCreated' => new IXR_Date($post_date),
2836                                 'userid' => $postdata['post_author'],
2837                                 'postid' => $postdata['ID'],
2838                                 'description' => $post['main'],
2839                                 'title' => $postdata['post_title'],
2840                                 'link' => $link,
2841                                 'permaLink' => $link,
2842                                 // commented out because no other tool seems to use this
2843                                 //            'content' => $entry['post_content'],
2844                                 'categories' => $categories,
2845                                 'mt_excerpt' => $postdata['post_excerpt'],
2846                                 'mt_text_more' => $post['extended'],
2847                                 'mt_allow_comments' => $allow_comments,
2848                                 'mt_allow_pings' => $allow_pings,
2849                                 'mt_keywords' => $tagnames,
2850                                 'wp_slug' => $postdata['post_name'],
2851                                 'wp_password' => $postdata['post_password'],
2852                                 'wp_author_id' => $author->ID,
2853                                 'wp_author_display_name'        => $author->display_name,
2854                                 'date_created_gmt' => new IXR_Date($post_date_gmt),
2855                                 'post_status' => $postdata['post_status'],
2856                                 'custom_fields' => $this->get_custom_fields($post_ID),
2857                                 'wp_post_format' => $post_format,
2858                                 'sticky' => $sticky
2859                         );
2860
2861                         if ( !empty($enclosure) ) $resp['enclosure'] = $enclosure;
2862
2863                         return $resp;
2864                 } else {
2865                         return new IXR_Error(404, __('Sorry, no such post.'));
2866                 }
2867         }
2868
2869         /**
2870          * Retrieve list of recent posts.
2871          *
2872          * @since 1.5.0
2873          *
2874          * @param array $args Method parameters.
2875          * @return array
2876          */
2877         function mw_getRecentPosts($args) {
2878
2879                 $this->escape($args);
2880
2881                 $blog_ID     = (int) $args[0];
2882                 $username  = $args[1];
2883                 $password   = $args[2];
2884                 if ( isset( $args[3] ) )
2885                         $query = array( 'numberposts' => absint( $args[3] ) );
2886                 else
2887                         $query = array();
2888
2889                 if ( !$user = $this->login($username, $password) )
2890                         return $this->error;
2891
2892                 do_action('xmlrpc_call', 'metaWeblog.getRecentPosts');
2893
2894                 $posts_list = wp_get_recent_posts( $query );
2895
2896                 if ( !$posts_list )
2897                         return array( );
2898
2899                 foreach ($posts_list as $entry) {
2900                         if ( !current_user_can( 'edit_post', $entry['ID'] ) )
2901                                 continue;
2902
2903                         $post_date = mysql2date('Ymd\TH:i:s', $entry['post_date'], false);
2904                         $post_date_gmt = mysql2date('Ymd\TH:i:s', $entry['post_date_gmt'], false);
2905
2906                         // For drafts use the GMT version of the date
2907                         if ( $entry['post_status'] == 'draft' )
2908                                 $post_date_gmt = get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $entry['post_date'] ), 'Ymd\TH:i:s' );
2909
2910                         $categories = array();
2911                         $catids = wp_get_post_categories($entry['ID']);
2912                         foreach( $catids as $catid )
2913                                 $categories[] = get_cat_name($catid);
2914
2915                         $tagnames = array();
2916                         $tags = wp_get_post_tags( $entry['ID'] );
2917                         if ( !empty( $tags ) ) {
2918                                 foreach ( $tags as $tag ) {
2919                                         $tagnames[] = $tag->name;
2920                                 }
2921                                 $tagnames = implode( ', ', $tagnames );
2922                         } else {
2923                                 $tagnames = '';
2924                         }
2925
2926                         $post = get_extended($entry['post_content']);
2927                         $link = post_permalink($entry['ID']);
2928
2929                         // Get the post author info.
2930                         $author = get_userdata($entry['post_author']);
2931
2932                         $allow_comments = ('open' == $entry['comment_status']) ? 1 : 0;
2933                         $allow_pings = ('open' == $entry['ping_status']) ? 1 : 0;
2934
2935                         // Consider future posts as published
2936                         if ( $entry['post_status'] === 'future' )
2937                                 $entry['post_status'] = 'publish';
2938
2939                         // Get post format
2940                         $post_format = get_post_format( $entry['ID'] );
2941                         if ( empty( $post_format ) )
2942                                 $post_format = 'standard';
2943
2944                         $struct[] = array(
2945                                 'dateCreated' => new IXR_Date($post_date),
2946                                 'userid' => $entry['post_author'],
2947                                 'postid' => (string) $entry['ID'],
2948                                 'description' => $post['main'],
2949                                 'title' => $entry['post_title'],
2950                                 'link' => $link,
2951                                 'permaLink' => $link,
2952                                 // commented out because no other tool seems to use this
2953                                 // 'content' => $entry['post_content'],
2954                                 'categories' => $categories,
2955                                 'mt_excerpt' => $entry['post_excerpt'],
2956                                 'mt_text_more' => $post['extended'],
2957                                 'mt_allow_comments' => $allow_comments,
2958                                 'mt_allow_pings' => $allow_pings,
2959                                 'mt_keywords' => $tagnames,
2960                                 'wp_slug' => $entry['post_name'],
2961                                 'wp_password' => $entry['post_password'],
2962                                 'wp_author_id' => $author->ID,
2963                                 'wp_author_display_name' => $author->display_name,
2964                                 'date_created_gmt' => new IXR_Date($post_date_gmt),
2965                                 'post_status' => $entry['post_status'],
2966                                 'custom_fields' => $this->get_custom_fields($entry['ID']),
2967                                 'wp_post_format' => $post_format
2968                         );
2969
2970                 }
2971
2972                 $recent_posts = array();
2973                 for ( $j=0; $j<count($struct); $j++ ) {
2974                         array_push($recent_posts, $struct[$j]);
2975                 }
2976
2977                 return $recent_posts;
2978         }
2979
2980         /**
2981          * Retrieve the list of categories on a given blog.
2982          *
2983          * @since 1.5.0
2984          *
2985          * @param array $args Method parameters.
2986          * @return array
2987          */
2988         function mw_getCategories($args) {
2989
2990                 $this->escape($args);
2991
2992                 $blog_ID     = (int) $args[0];
2993                 $username  = $args[1];
2994                 $password   = $args[2];
2995
2996                 if ( !$user = $this->login($username, $password) )
2997                         return $this->error;
2998
2999                 if ( !current_user_can( 'edit_posts' ) )
3000                         return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
3001
3002                 do_action('xmlrpc_call', 'metaWeblog.getCategories');
3003
3004                 $categories_struct = array();
3005
3006                 if ( $cats = get_categories(array('get' => 'all')) ) {
3007                         foreach ( $cats as $cat ) {
3008                                 $struct['categoryId'] = $cat->term_id;
3009                                 $struct['parentId'] = $cat->parent;
3010                                 $struct['description'] = $cat->name;
3011                                 $struct['categoryDescription'] = $cat->description;
3012                                 $struct['categoryName'] = $cat->name;
3013                                 $struct['htmlUrl'] = esc_html(get_category_link($cat->term_id));
3014                                 $struct['rssUrl'] = esc_html(get_category_feed_link($cat->term_id, 'rss2'));
3015
3016                                 $categories_struct[] = $struct;
3017                         }
3018                 }
3019
3020                 return $categories_struct;
3021         }
3022
3023         /**
3024          * Uploads a file, following your settings.
3025          *
3026          * Adapted from a patch by Johann Richard.
3027          *
3028          * @link http://mycvs.org/archives/2004/06/30/file-upload-to-wordpress-in-ecto/
3029          *
3030          * @since 1.5.0
3031          *
3032          * @param array $args Method parameters.
3033          * @return array
3034          */
3035         function mw_newMediaObject($args) {
3036                 global $wpdb;
3037
3038                 $blog_ID     = (int) $args[0];
3039                 $username  = $wpdb->escape($args[1]);
3040                 $password   = $wpdb->escape($args[2]);
3041                 $data        = $args[3];
3042
3043                 $name = sanitize_file_name( $data['name'] );
3044                 $type = $data['type'];
3045                 $bits = $data['bits'];
3046
3047                 logIO('O', '(MW) Received '.strlen($bits).' bytes');
3048
3049                 if ( !$user = $this->login($username, $password) )
3050                         return $this->error;
3051
3052                 do_action('xmlrpc_call', 'metaWeblog.newMediaObject');
3053
3054                 if ( !current_user_can('upload_files') ) {
3055                         logIO('O', '(MW) User does not have upload_files capability');
3056                         $this->error = new IXR_Error(401, __('You are not allowed to upload files to this site.'));
3057                         return $this->error;
3058                 }
3059
3060                 if ( $upload_err = apply_filters( 'pre_upload_error', false ) )
3061                         return new IXR_Error(500, $upload_err);
3062
3063                 if ( !empty($data['overwrite']) && ($data['overwrite'] == true) ) {
3064                         // Get postmeta info on the object.
3065                         $old_file = $wpdb->get_row("
3066                                 SELECT ID
3067                                 FROM {$wpdb->posts}
3068                                 WHERE post_title = '{$name}'
3069                                         AND post_type = 'attachment'
3070                         ");
3071
3072                         // Delete previous file.
3073                         wp_delete_attachment($old_file->ID);
3074
3075                         // Make sure the new name is different by pre-pending the
3076                         // previous post id.
3077                         $filename = preg_replace('/^wpid\d+-/', '', $name);
3078                         $name = "wpid{$old_file->ID}-{$filename}";
3079                 }
3080
3081                 $upload = wp_upload_bits($name, NULL, $bits);
3082                 if ( ! empty($upload['error']) ) {
3083                         $errorString = sprintf(__('Could not write file %1$s (%2$s)'), $name, $upload['error']);
3084                         logIO('O', '(MW) ' . $errorString);
3085                         return new IXR_Error(500, $errorString);
3086                 }
3087                 // Construct the attachment array
3088                 // attach to post_id 0
3089                 $post_id = 0;
3090                 $attachment = array(
3091                         'post_title' => $name,
3092                         'post_content' => '',
3093                         'post_type' => 'attachment',
3094                         'post_parent' => $post_id,
3095                         'post_mime_type' => $type,
3096                         'guid' => $upload[ 'url' ]
3097                 );
3098
3099                 // Save the data
3100                 $id = wp_insert_attachment( $attachment, $upload[ 'file' ], $post_id );
3101                 wp_update_attachment_metadata( $id, wp_generate_attachment_metadata