]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/class-wp-xmlrpc-server.php
WordPress 4.5.1
[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  * @subpackage Publishing
7  */
8
9 /**
10  * WordPress XMLRPC server implementation.
11  *
12  * Implements compatibility for Blogger API, MetaWeblog API, MovableType, and
13  * pingback. Additional WordPress API for managing comments, pages, posts,
14  * options, etc.
15  *
16  * As of WordPress 3.5.0, XML-RPC is enabled by default. It can be disabled
17  * via the xmlrpc_enabled filter found in wp_xmlrpc_server::login().
18  *
19  * @package WordPress
20  * @subpackage Publishing
21  * @since 1.5.0
22  */
23 class wp_xmlrpc_server extends IXR_Server {
24         /**
25          * Methods.
26          *
27          * @access public
28          * @var array
29          */
30         public $methods;
31
32         /**
33          * Blog options.
34          *
35          * @access public
36          * @var array
37          */
38         public $blog_options;
39
40         /**
41          * IXR_Error instance.
42          *
43          * @access public
44          * @var IXR_Error
45          */
46         public $error;
47
48         /**
49          * Flags that the user authentication has failed in this instance of wp_xmlrpc_server.
50          *
51          * @access protected
52          * @var bool
53          */
54         protected $auth_failed = false;
55
56         /**
57          * Register all of the XMLRPC methods that XMLRPC server understands.
58          *
59          * Sets up server and method property. Passes XMLRPC
60          * methods through the 'xmlrpc_methods' filter to allow plugins to extend
61          * or replace XMLRPC methods.
62          *
63          * @since 1.5.0
64          */
65         public function __construct() {
66                 $this->methods = array(
67                         // WordPress API
68                         'wp.getUsersBlogs'              => 'this:wp_getUsersBlogs',
69                         'wp.newPost'                    => 'this:wp_newPost',
70                         'wp.editPost'                   => 'this:wp_editPost',
71                         'wp.deletePost'                 => 'this:wp_deletePost',
72                         'wp.getPost'                    => 'this:wp_getPost',
73                         'wp.getPosts'                   => 'this:wp_getPosts',
74                         'wp.newTerm'                    => 'this:wp_newTerm',
75                         'wp.editTerm'                   => 'this:wp_editTerm',
76                         'wp.deleteTerm'                 => 'this:wp_deleteTerm',
77                         'wp.getTerm'                    => 'this:wp_getTerm',
78                         'wp.getTerms'                   => 'this:wp_getTerms',
79                         'wp.getTaxonomy'                => 'this:wp_getTaxonomy',
80                         'wp.getTaxonomies'              => 'this:wp_getTaxonomies',
81                         'wp.getUser'                    => 'this:wp_getUser',
82                         'wp.getUsers'                   => 'this:wp_getUsers',
83                         'wp.getProfile'                 => 'this:wp_getProfile',
84                         'wp.editProfile'                => 'this:wp_editProfile',
85                         'wp.getPage'                    => 'this:wp_getPage',
86                         'wp.getPages'                   => 'this:wp_getPages',
87                         'wp.newPage'                    => 'this:wp_newPage',
88                         'wp.deletePage'                 => 'this:wp_deletePage',
89                         'wp.editPage'                   => 'this:wp_editPage',
90                         'wp.getPageList'                => 'this:wp_getPageList',
91                         'wp.getAuthors'                 => 'this:wp_getAuthors',
92                         'wp.getCategories'              => 'this:mw_getCategories',             // Alias
93                         'wp.getTags'                    => 'this:wp_getTags',
94                         'wp.newCategory'                => 'this:wp_newCategory',
95                         'wp.deleteCategory'             => 'this:wp_deleteCategory',
96                         'wp.suggestCategories'  => 'this:wp_suggestCategories',
97                         'wp.uploadFile'                 => 'this:mw_newMediaObject',    // Alias
98                         'wp.deleteFile'                 => 'this:wp_deletePost',                // Alias
99                         'wp.getCommentCount'    => 'this:wp_getCommentCount',
100                         'wp.getPostStatusList'  => 'this:wp_getPostStatusList',
101                         'wp.getPageStatusList'  => 'this:wp_getPageStatusList',
102                         'wp.getPageTemplates'   => 'this:wp_getPageTemplates',
103                         'wp.getOptions'                 => 'this:wp_getOptions',
104                         'wp.setOptions'                 => 'this:wp_setOptions',
105                         'wp.getComment'                 => 'this:wp_getComment',
106                         'wp.getComments'                => 'this:wp_getComments',
107                         'wp.deleteComment'              => 'this:wp_deleteComment',
108                         'wp.editComment'                => 'this:wp_editComment',
109                         'wp.newComment'                 => 'this:wp_newComment',
110                         'wp.getCommentStatusList' => 'this:wp_getCommentStatusList',
111                         'wp.getMediaItem'               => 'this:wp_getMediaItem',
112                         'wp.getMediaLibrary'    => 'this:wp_getMediaLibrary',
113                         'wp.getPostFormats'     => 'this:wp_getPostFormats',
114                         'wp.getPostType'                => 'this:wp_getPostType',
115                         'wp.getPostTypes'               => 'this:wp_getPostTypes',
116                         'wp.getRevisions'               => 'this:wp_getRevisions',
117                         'wp.restoreRevision'    => 'this:wp_restoreRevision',
118
119                         // Blogger API
120                         'blogger.getUsersBlogs' => 'this:blogger_getUsersBlogs',
121                         'blogger.getUserInfo' => 'this:blogger_getUserInfo',
122                         'blogger.getPost' => 'this:blogger_getPost',
123                         'blogger.getRecentPosts' => 'this:blogger_getRecentPosts',
124                         'blogger.newPost' => 'this:blogger_newPost',
125                         'blogger.editPost' => 'this:blogger_editPost',
126                         'blogger.deletePost' => 'this:blogger_deletePost',
127
128                         // MetaWeblog API (with MT extensions to structs)
129                         'metaWeblog.newPost' => 'this:mw_newPost',
130                         'metaWeblog.editPost' => 'this:mw_editPost',
131                         'metaWeblog.getPost' => 'this:mw_getPost',
132                         'metaWeblog.getRecentPosts' => 'this:mw_getRecentPosts',
133                         'metaWeblog.getCategories' => 'this:mw_getCategories',
134                         'metaWeblog.newMediaObject' => 'this:mw_newMediaObject',
135
136                         // MetaWeblog API aliases for Blogger API
137                         // see http://www.xmlrpc.com/stories/storyReader$2460
138                         'metaWeblog.deletePost' => 'this:blogger_deletePost',
139                         'metaWeblog.getUsersBlogs' => 'this:blogger_getUsersBlogs',
140
141                         // MovableType API
142                         'mt.getCategoryList' => 'this:mt_getCategoryList',
143                         'mt.getRecentPostTitles' => 'this:mt_getRecentPostTitles',
144                         'mt.getPostCategories' => 'this:mt_getPostCategories',
145                         'mt.setPostCategories' => 'this:mt_setPostCategories',
146                         'mt.supportedMethods' => 'this:mt_supportedMethods',
147                         'mt.supportedTextFilters' => 'this:mt_supportedTextFilters',
148                         'mt.getTrackbackPings' => 'this:mt_getTrackbackPings',
149                         'mt.publishPost' => 'this:mt_publishPost',
150
151                         // PingBack
152                         'pingback.ping' => 'this:pingback_ping',
153                         'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks',
154
155                         'demo.sayHello' => 'this:sayHello',
156                         'demo.addTwoNumbers' => 'this:addTwoNumbers'
157                 );
158
159                 $this->initialise_blog_option_info();
160
161                 /**
162                  * Filter the methods exposed by the XML-RPC server.
163                  *
164                  * This filter can be used to add new methods, and remove built-in methods.
165                  *
166                  * @since 1.5.0
167                  *
168                  * @param array $methods An array of XML-RPC methods.
169                  */
170                 $this->methods = apply_filters( 'xmlrpc_methods', $this->methods );
171         }
172
173         /**
174          * Make private/protected methods readable for backwards compatibility.
175          *
176          * @since 4.0.0
177          * @access public
178          *
179          * @param callable $name      Method to call.
180          * @param array    $arguments Arguments to pass when calling.
181          * @return array|IXR_Error|false Return value of the callback, false otherwise.
182          */
183         public function __call( $name, $arguments ) {
184                 if ( '_multisite_getUsersBlogs' === $name ) {
185                         return call_user_func_array( array( $this, $name ), $arguments );
186                 }
187                 return false;
188         }
189
190         /**
191          * Serves the XML-RPC request.
192          *
193          * @since 2.9.0
194          * @access public
195          */
196         public function serve_request() {
197                 $this->IXR_Server($this->methods);
198         }
199
200         /**
201          * Test XMLRPC API by saying, "Hello!" to client.
202          *
203          * @since 1.5.0
204          *
205          * @return string Hello string response.
206          */
207         public function sayHello() {
208                 return 'Hello!';
209         }
210
211         /**
212          * Test XMLRPC API by adding two numbers for client.
213          *
214          * @since 1.5.0
215          *
216          * @param array  $args {
217          *     Method arguments. Note: arguments must be ordered as documented.
218          *
219          *     @type int $number1 A number to add.
220          *     @type int $number2 A second number to add.
221          * }
222          * @return int Sum of the two given numbers.
223          */
224         public function addTwoNumbers( $args ) {
225                 $number1 = $args[0];
226                 $number2 = $args[1];
227                 return $number1 + $number2;
228         }
229
230         /**
231          * Log user in.
232          *
233          * @since 2.8.0
234          *
235          * @param string $username User's username.
236          * @param string $password User's password.
237          * @return WP_User|bool WP_User object if authentication passed, false otherwise
238          */
239         public function login( $username, $password ) {
240                 /*
241                  * Respect old get_option() filters left for back-compat when the 'enable_xmlrpc'
242                  * option was deprecated in 3.5.0. Use the 'xmlrpc_enabled' hook instead.
243                  */
244                 $enabled = apply_filters( 'pre_option_enable_xmlrpc', false );
245                 if ( false === $enabled ) {
246                         $enabled = apply_filters( 'option_enable_xmlrpc', true );
247                 }
248
249                 /**
250                  * Filter whether XML-RPC methods requiring authentication are enabled.
251                  *
252                  * Contrary to the way it's named, this filter does not control whether XML-RPC is *fully*
253                  * enabled, rather, it only controls whether XML-RPC methods requiring authentication - such
254                  * as for publishing purposes - are enabled.
255                  *
256                  * Further, the filter does not control whether pingbacks or other custom endpoints that don't
257                  * require authentication are enabled. This behavior is expected, and due to how parity was matched
258                  * with the `enable_xmlrpc` UI option the filter replaced when it was introduced in 3.5.
259                  *
260                  * To disable XML-RPC methods that require authentication, use:
261                  *
262                  *     add_filter( 'xmlrpc_enabled', '__return_false' );
263                  *
264                  * For more granular control over all XML-RPC methods and requests, see the {@see 'xmlrpc_methods'}
265                  * and {@see 'xmlrpc_element_limit'} hooks.
266                  *
267                  * @since 3.5.0
268                  *
269                  * @param bool $enabled Whether XML-RPC is enabled. Default true.
270                  */
271                 $enabled = apply_filters( 'xmlrpc_enabled', $enabled );
272
273                 if ( ! $enabled ) {
274                         $this->error = new IXR_Error( 405, sprintf( __( 'XML-RPC services are disabled on this site.' ) ) );
275                         return false;
276                 }
277
278                 if ( $this->auth_failed ) {
279                         $user = new WP_Error( 'login_prevented' );
280                 } else {
281                         $user = wp_authenticate( $username, $password );
282                 }
283
284                 if ( is_wp_error( $user ) ) {
285                         $this->error = new IXR_Error( 403, __( 'Incorrect username or password.' ) );
286
287                         // Flag that authentication has failed once on this wp_xmlrpc_server instance
288                         $this->auth_failed = true;
289
290                         /**
291                          * Filter the XML-RPC user login error message.
292                          *
293                          * @since 3.5.0
294                          *
295                          * @param string  $error The XML-RPC error message.
296                          * @param WP_User $user  WP_User object.
297                          */
298                         $this->error = apply_filters( 'xmlrpc_login_error', $this->error, $user );
299                         return false;
300                 }
301
302                 wp_set_current_user( $user->ID );
303                 return $user;
304         }
305
306         /**
307          * Check user's credentials. Deprecated.
308          *
309          * @since 1.5.0
310          * @deprecated 2.8.0 Use wp_xmlrpc_server::login()
311          * @see wp_xmlrpc_server::login()
312          *
313          * @param string $username User's username.
314          * @param string $password User's password.
315          * @return bool Whether authentication passed.
316          */
317         public function login_pass_ok( $username, $password ) {
318                 return (bool) $this->login( $username, $password );
319         }
320
321         /**
322          * Escape string or array of strings for database.
323          *
324          * @since 1.5.2
325          *
326          * @param string|array $data Escape single string or array of strings.
327          * @return string|void Returns with string is passed, alters by-reference
328          *                     when array is passed.
329          */
330         public function escape( &$data ) {
331                 if ( ! is_array( $data ) )
332                         return wp_slash( $data );
333
334                 foreach ( $data as &$v ) {
335                         if ( is_array( $v ) )
336                                 $this->escape( $v );
337                         elseif ( ! is_object( $v ) )
338                                 $v = wp_slash( $v );
339                 }
340         }
341
342         /**
343          * Retrieve custom fields for post.
344          *
345          * @since 2.5.0
346          *
347          * @param int $post_id Post ID.
348          * @return array Custom fields, if exist.
349          */
350         public function get_custom_fields($post_id) {
351                 $post_id = (int) $post_id;
352
353                 $custom_fields = array();
354
355                 foreach ( (array) has_meta($post_id) as $meta ) {
356                         // Don't expose protected fields.
357                         if ( ! current_user_can( 'edit_post_meta', $post_id , $meta['meta_key'] ) )
358                                 continue;
359
360                         $custom_fields[] = array(
361                                 "id"    => $meta['meta_id'],
362                                 "key"   => $meta['meta_key'],
363                                 "value" => $meta['meta_value']
364                         );
365                 }
366
367                 return $custom_fields;
368         }
369
370         /**
371          * Set custom fields for post.
372          *
373          * @since 2.5.0
374          *
375          * @param int $post_id Post ID.
376          * @param array $fields Custom fields.
377          */
378         public function set_custom_fields($post_id, $fields) {
379                 $post_id = (int) $post_id;
380
381                 foreach ( (array) $fields as $meta ) {
382                         if ( isset($meta['id']) ) {
383                                 $meta['id'] = (int) $meta['id'];
384                                 $pmeta = get_metadata_by_mid( 'post', $meta['id'] );
385                                 if ( isset($meta['key']) ) {
386                                         $meta['key'] = wp_unslash( $meta['key'] );
387                                         if ( $meta['key'] !== $pmeta->meta_key )
388                                                 continue;
389                                         $meta['value'] = wp_unslash( $meta['value'] );
390                                         if ( current_user_can( 'edit_post_meta', $post_id, $meta['key'] ) )
391                                                 update_metadata_by_mid( 'post', $meta['id'], $meta['value'] );
392                                 } elseif ( current_user_can( 'delete_post_meta', $post_id, $pmeta->meta_key ) ) {
393                                         delete_metadata_by_mid( 'post', $meta['id'] );
394                                 }
395                         } elseif ( current_user_can( 'add_post_meta', $post_id, wp_unslash( $meta['key'] ) ) ) {
396                                 add_post_meta( $post_id, $meta['key'], $meta['value'] );
397                         }
398                 }
399         }
400
401         /**
402          * Set up blog options property.
403          *
404          * Passes property through {@see 'xmlrpc_blog_options'} filter.
405          *
406          * @since 2.6.0
407          *
408          * @global string $wp_version
409          */
410         public function initialise_blog_option_info() {
411                 global $wp_version;
412
413                 $this->blog_options = array(
414                         // Read only options
415                         'software_name'     => array(
416                                 'desc'          => __( 'Software Name' ),
417                                 'readonly'      => true,
418                                 'value'         => 'WordPress'
419                         ),
420                         'software_version'  => array(
421                                 'desc'          => __( 'Software Version' ),
422                                 'readonly'      => true,
423                                 'value'         => $wp_version
424                         ),
425                         'blog_url'          => array(
426                                 'desc'          => __( 'WordPress Address (URL)' ),
427                                 'readonly'      => true,
428                                 'option'        => 'siteurl'
429                         ),
430                         'home_url'          => array(
431                                 'desc'          => __( 'Site Address (URL)' ),
432                                 'readonly'      => true,
433                                 'option'        => 'home'
434                         ),
435                         'login_url'          => array(
436                                 'desc'          => __( 'Login Address (URL)' ),
437                                 'readonly'      => true,
438                                 'value'         => wp_login_url( )
439                         ),
440                         'admin_url'          => array(
441                                 'desc'          => __( 'The URL to the admin area' ),
442                                 'readonly'      => true,
443                                 'value'         => get_admin_url( )
444                         ),
445                         'image_default_link_type' => array(
446                                 'desc'          => __( 'Image default link type' ),
447                                 'readonly'      => true,
448                                 'option'        => 'image_default_link_type'
449                         ),
450                         'image_default_size' => array(
451                                 'desc'          => __( 'Image default size' ),
452                                 'readonly'      => true,
453                                 'option'        => 'image_default_size'
454                         ),
455                         'image_default_align' => array(
456                                 'desc'          => __( 'Image default align' ),
457                                 'readonly'      => true,
458                                 'option'        => 'image_default_align'
459                         ),
460                         'template'          => array(
461                                 'desc'          => __( 'Template' ),
462                                 'readonly'      => true,
463                                 'option'        => 'template'
464                         ),
465                         'stylesheet'        => array(
466                                 'desc'          => __( 'Stylesheet' ),
467                                 'readonly'      => true,
468                                 'option'        => 'stylesheet'
469                         ),
470                         'post_thumbnail'    => array(
471                                 'desc'          => __('Post Thumbnail'),
472                                 'readonly'      => true,
473                                 'value'         => current_theme_supports( 'post-thumbnails' )
474                         ),
475
476                         // Updatable options
477                         'time_zone'         => array(
478                                 'desc'          => __( 'Time Zone' ),
479                                 'readonly'      => false,
480                                 'option'        => 'gmt_offset'
481                         ),
482                         'blog_title'        => array(
483                                 'desc'          => __( 'Site Title' ),
484                                 'readonly'      => false,
485                                 'option'        => 'blogname'
486                         ),
487                         'blog_tagline'      => array(
488                                 'desc'          => __( 'Site Tagline' ),
489                                 'readonly'      => false,
490                                 'option'        => 'blogdescription'
491                         ),
492                         'date_format'       => array(
493                                 'desc'          => __( 'Date Format' ),
494                                 'readonly'      => false,
495                                 'option'        => 'date_format'
496                         ),
497                         'time_format'       => array(
498                                 'desc'          => __( 'Time Format' ),
499                                 'readonly'      => false,
500                                 'option'        => 'time_format'
501                         ),
502                         'users_can_register' => array(
503                                 'desc'          => __( 'Allow new users to sign up' ),
504                                 'readonly'      => false,
505                                 'option'        => 'users_can_register'
506                         ),
507                         'thumbnail_size_w'  => array(
508                                 'desc'          => __( 'Thumbnail Width' ),
509                                 'readonly'      => false,
510                                 'option'        => 'thumbnail_size_w'
511                         ),
512                         'thumbnail_size_h'  => array(
513                                 'desc'          => __( 'Thumbnail Height' ),
514                                 'readonly'      => false,
515                                 'option'        => 'thumbnail_size_h'
516                         ),
517                         'thumbnail_crop'    => array(
518                                 'desc'          => __( 'Crop thumbnail to exact dimensions' ),
519                                 'readonly'      => false,
520                                 'option'        => 'thumbnail_crop'
521                         ),
522                         'medium_size_w'     => array(
523                                 'desc'          => __( 'Medium size image width' ),
524                                 'readonly'      => false,
525                                 'option'        => 'medium_size_w'
526                         ),
527                         'medium_size_h'     => array(
528                                 'desc'          => __( 'Medium size image height' ),
529                                 'readonly'      => false,
530                                 'option'        => 'medium_size_h'
531                         ),
532                         'medium_large_size_w'   => array(
533                                 'desc'          => __( 'Medium-Large size image width' ),
534                                 'readonly'      => false,
535                                 'option'        => 'medium_large_size_w'
536                         ),
537                         'medium_large_size_h'   => array(
538                                 'desc'          => __( 'Medium-Large size image height' ),
539                                 'readonly'      => false,
540                                 'option'        => 'medium_large_size_h'
541                         ),
542                         'large_size_w'      => array(
543                                 'desc'          => __( 'Large size image width' ),
544                                 'readonly'      => false,
545                                 'option'        => 'large_size_w'
546                         ),
547                         'large_size_h'      => array(
548                                 'desc'          => __( 'Large size image height' ),
549                                 'readonly'      => false,
550                                 'option'        => 'large_size_h'
551                         ),
552                         'default_comment_status' => array(
553                                 'desc'          => __( 'Allow people to post comments on new articles' ),
554                                 'readonly'      => false,
555                                 'option'        => 'default_comment_status'
556                         ),
557                         'default_ping_status' => array(
558                                 'desc'          => __( 'Allow link notifications from other blogs (pingbacks and trackbacks) on new articles' ),
559                                 'readonly'      => false,
560                                 'option'        => 'default_ping_status'
561                         )
562                 );
563
564                 /**
565                  * Filter the XML-RPC blog options property.
566                  *
567                  * @since 2.6.0
568                  *
569                  * @param array $blog_options An array of XML-RPC blog options.
570                  */
571                 $this->blog_options = apply_filters( 'xmlrpc_blog_options', $this->blog_options );
572         }
573
574         /**
575          * Retrieve the blogs of the user.
576          *
577          * @since 2.6.0
578          *
579          * @param array $args {
580          *     Method arguments. Note: arguments must be ordered as documented.
581          *
582          *     @type string $username Username.
583          *     @type string $password Password.
584          * }
585          * @return array|IXR_Error Array contains:
586          *  - 'isAdmin'
587          *  - 'isPrimary' - whether the blog is the user's primary blog
588          *  - 'url'
589          *  - 'blogid'
590          *  - 'blogName'
591          *  - 'xmlrpc' - url of xmlrpc endpoint
592          */
593         public function wp_getUsersBlogs( $args ) {
594                 // If this isn't on WPMU then just use blogger_getUsersBlogs
595                 if ( !is_multisite() ) {
596                         array_unshift( $args, 1 );
597                         return $this->blogger_getUsersBlogs( $args );
598                 }
599
600                 $this->escape( $args );
601
602                 $username = $args[0];
603                 $password = $args[1];
604
605                 if ( !$user = $this->login($username, $password) )
606                         return $this->error;
607
608                 /**
609                  * Fires after the XML-RPC user has been authenticated but before the rest of
610                  * the method logic begins.
611                  *
612                  * All built-in XML-RPC methods use the action xmlrpc_call, with a parameter
613                  * equal to the method's name, e.g., wp.getUsersBlogs, wp.newPost, etc.
614                  *
615                  * @since 2.5.0
616                  *
617                  * @param string $name The method name.
618                  */
619                 do_action( 'xmlrpc_call', 'wp.getUsersBlogs' );
620
621                 $blogs = (array) get_blogs_of_user( $user->ID );
622                 $struct = array();
623                 $primary_blog_id = 0;
624                 $active_blog = get_active_blog_for_user( $user->ID );
625                 if ( $active_blog ) {
626                         $primary_blog_id = (int) $active_blog->blog_id;
627                 }
628
629                 foreach ( $blogs as $blog ) {
630                         // Don't include blogs that aren't hosted at this site.
631                         if ( $blog->site_id != get_current_site()->id )
632                                 continue;
633
634                         $blog_id = $blog->userblog_id;
635
636                         switch_to_blog( $blog_id );
637
638                         $is_admin = current_user_can( 'manage_options' );
639                         $is_primary = ( (int) $blog_id === $primary_blog_id );
640
641                         $struct[] = array(
642                                 'isAdmin'   => $is_admin,
643                                 'isPrimary' => $is_primary,
644                                 'url'       => home_url( '/' ),
645                                 'blogid'    => (string) $blog_id,
646                                 'blogName'  => get_option( 'blogname' ),
647                                 'xmlrpc'    => site_url( 'xmlrpc.php', 'rpc' ),
648                         );
649
650                         restore_current_blog();
651                 }
652
653                 return $struct;
654         }
655
656         /**
657          * Checks if the method received at least the minimum number of arguments.
658          *
659          * @since 3.4.0
660          * @access protected
661          *
662          * @param string|array $args Sanitize single string or array of strings.
663          * @param int $count         Minimum number of arguments.
664          * @return bool if `$args` contains at least $count arguments.
665          */
666         protected function minimum_args( $args, $count ) {
667                 if ( count( $args ) < $count ) {
668                         $this->error = new IXR_Error( 400, __( 'Insufficient arguments passed to this XML-RPC method.' ) );
669                         return false;
670                 }
671
672                 return true;
673         }
674
675         /**
676          * Prepares taxonomy data for return in an XML-RPC object.
677          *
678          * @access protected
679          *
680          * @param object $taxonomy The unprepared taxonomy data.
681          * @param array $fields    The subset of taxonomy fields to return.
682          * @return array The prepared taxonomy data.
683          */
684         protected function _prepare_taxonomy( $taxonomy, $fields ) {
685                 $_taxonomy = array(
686                         'name' => $taxonomy->name,
687                         'label' => $taxonomy->label,
688                         'hierarchical' => (bool) $taxonomy->hierarchical,
689                         'public' => (bool) $taxonomy->public,
690                         'show_ui' => (bool) $taxonomy->show_ui,
691                         '_builtin' => (bool) $taxonomy->_builtin,
692                 );
693
694                 if ( in_array( 'labels', $fields ) )
695                         $_taxonomy['labels'] = (array) $taxonomy->labels;
696
697                 if ( in_array( 'cap', $fields ) )
698                         $_taxonomy['cap'] = (array) $taxonomy->cap;
699
700                 if ( in_array( 'menu', $fields ) )
701                         $_taxonomy['show_in_menu'] = (bool) $_taxonomy->show_in_menu;
702
703                 if ( in_array( 'object_type', $fields ) )
704                         $_taxonomy['object_type'] = array_unique( (array) $taxonomy->object_type );
705
706                 /**
707                  * Filter XML-RPC-prepared data for the given taxonomy.
708                  *
709                  * @since 3.4.0
710                  *
711                  * @param array  $_taxonomy An array of taxonomy data.
712                  * @param object $taxonomy  Taxonomy object.
713                  * @param array  $fields    The subset of taxonomy fields to return.
714                  */
715                 return apply_filters( 'xmlrpc_prepare_taxonomy', $_taxonomy, $taxonomy, $fields );
716         }
717
718         /**
719          * Prepares term data for return in an XML-RPC object.
720          *
721          * @access protected
722          *
723          * @param array|object $term The unprepared term data.
724          * @return array The prepared term data.
725          */
726         protected function _prepare_term( $term ) {
727                 $_term = $term;
728                 if ( ! is_array( $_term ) )
729                         $_term = get_object_vars( $_term );
730
731                 // For integers which may be larger than XML-RPC supports ensure we return strings.
732                 $_term['term_id'] = strval( $_term['term_id'] );
733                 $_term['term_group'] = strval( $_term['term_group'] );
734                 $_term['term_taxonomy_id'] = strval( $_term['term_taxonomy_id'] );
735                 $_term['parent'] = strval( $_term['parent'] );
736
737                 // Count we are happy to return as an integer because people really shouldn't use terms that much.
738                 $_term['count'] = intval( $_term['count'] );
739
740                 /**
741                  * Filter XML-RPC-prepared data for the given term.
742                  *
743                  * @since 3.4.0
744                  *
745                  * @param array        $_term An array of term data.
746                  * @param array|object $term  Term object or array.
747                  */
748                 return apply_filters( 'xmlrpc_prepare_term', $_term, $term );
749         }
750
751         /**
752          * Convert a WordPress date string to an IXR_Date object.
753          *
754          * @access protected
755          *
756          * @param string $date Date string to convert.
757          * @return IXR_Date IXR_Date object.
758          */
759         protected function _convert_date( $date ) {
760                 if ( $date === '0000-00-00 00:00:00' ) {
761                         return new IXR_Date( '00000000T00:00:00Z' );
762                 }
763                 return new IXR_Date( mysql2date( 'Ymd\TH:i:s', $date, false ) );
764         }
765
766         /**
767          * Convert a WordPress GMT date string to an IXR_Date object.
768          *
769          * @access protected
770          *
771          * @param string $date_gmt WordPress GMT date string.
772          * @param string $date     Date string.
773          * @return IXR_Date IXR_Date object.
774          */
775         protected function _convert_date_gmt( $date_gmt, $date ) {
776                 if ( $date !== '0000-00-00 00:00:00' && $date_gmt === '0000-00-00 00:00:00' ) {
777                         return new IXR_Date( get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $date, false ), 'Ymd\TH:i:s' ) );
778                 }
779                 return $this->_convert_date( $date_gmt );
780         }
781
782         /**
783          * Prepares post data for return in an XML-RPC object.
784          *
785          * @access protected
786          *
787          * @param array $post   The unprepared post data.
788          * @param array $fields The subset of post type fields to return.
789          * @return array The prepared post data.
790          */
791         protected function _prepare_post( $post, $fields ) {
792                 // Holds the data for this post. built up based on $fields.
793                 $_post = array( 'post_id' => strval( $post['ID'] ) );
794
795                 // Prepare common post fields.
796                 $post_fields = array(
797                         'post_title'        => $post['post_title'],
798                         'post_date'         => $this->_convert_date( $post['post_date'] ),
799                         'post_date_gmt'     => $this->_convert_date_gmt( $post['post_date_gmt'], $post['post_date'] ),
800                         'post_modified'     => $this->_convert_date( $post['post_modified'] ),
801                         'post_modified_gmt' => $this->_convert_date_gmt( $post['post_modified_gmt'], $post['post_modified'] ),
802                         'post_status'       => $post['post_status'],
803                         'post_type'         => $post['post_type'],
804                         'post_name'         => $post['post_name'],
805                         'post_author'       => $post['post_author'],
806                         'post_password'     => $post['post_password'],
807                         'post_excerpt'      => $post['post_excerpt'],
808                         'post_content'      => $post['post_content'],
809                         'post_parent'       => strval( $post['post_parent'] ),
810                         'post_mime_type'    => $post['post_mime_type'],
811                         'link'              => get_permalink( $post['ID'] ),
812                         'guid'              => $post['guid'],
813                         'menu_order'        => intval( $post['menu_order'] ),
814                         'comment_status'    => $post['comment_status'],
815                         'ping_status'       => $post['ping_status'],
816                         'sticky'            => ( $post['post_type'] === 'post' && is_sticky( $post['ID'] ) ),
817                 );
818
819                 // Thumbnail.
820                 $post_fields['post_thumbnail'] = array();
821                 $thumbnail_id = get_post_thumbnail_id( $post['ID'] );
822                 if ( $thumbnail_id ) {
823                         $thumbnail_size = current_theme_supports('post-thumbnail') ? 'post-thumbnail' : 'thumbnail';
824                         $post_fields['post_thumbnail'] = $this->_prepare_media_item( get_post( $thumbnail_id ), $thumbnail_size );
825                 }
826
827                 // Consider future posts as published.
828                 if ( $post_fields['post_status'] === 'future' )
829                         $post_fields['post_status'] = 'publish';
830
831                 // Fill in blank post format.
832                 $post_fields['post_format'] = get_post_format( $post['ID'] );
833                 if ( empty( $post_fields['post_format'] ) )
834                         $post_fields['post_format'] = 'standard';
835
836                 // Merge requested $post_fields fields into $_post.
837                 if ( in_array( 'post', $fields ) ) {
838                         $_post = array_merge( $_post, $post_fields );
839                 } else {
840                         $requested_fields = array_intersect_key( $post_fields, array_flip( $fields ) );
841                         $_post = array_merge( $_post, $requested_fields );
842                 }
843
844                 $all_taxonomy_fields = in_array( 'taxonomies', $fields );
845
846                 if ( $all_taxonomy_fields || in_array( 'terms', $fields ) ) {
847                         $post_type_taxonomies = get_object_taxonomies( $post['post_type'], 'names' );
848                         $terms = wp_get_object_terms( $post['ID'], $post_type_taxonomies );
849                         $_post['terms'] = array();
850                         foreach ( $terms as $term ) {
851                                 $_post['terms'][] = $this->_prepare_term( $term );
852                         }
853                 }
854
855                 if ( in_array( 'custom_fields', $fields ) )
856                         $_post['custom_fields'] = $this->get_custom_fields( $post['ID'] );
857
858                 if ( in_array( 'enclosure', $fields ) ) {
859                         $_post['enclosure'] = array();
860                         $enclosures = (array) get_post_meta( $post['ID'], 'enclosure' );
861                         if ( ! empty( $enclosures ) ) {
862                                 $encdata = explode( "\n", $enclosures[0] );
863                                 $_post['enclosure']['url'] = trim( htmlspecialchars( $encdata[0] ) );
864                                 $_post['enclosure']['length'] = (int) trim( $encdata[1] );
865                                 $_post['enclosure']['type'] = trim( $encdata[2] );
866                         }
867                 }
868
869                 /**
870                  * Filter XML-RPC-prepared date for the given post.
871                  *
872                  * @since 3.4.0
873                  *
874                  * @param array $_post  An array of modified post data.
875                  * @param array $post   An array of post data.
876                  * @param array $fields An array of post fields.
877                  */
878                 return apply_filters( 'xmlrpc_prepare_post', $_post, $post, $fields );
879         }
880
881         /**
882          * Prepares post data for return in an XML-RPC object.
883          *
884          * @access protected
885          *
886          * @param object $post_type Post type object.
887          * @param array  $fields    The subset of post fields to return.
888          * @return array The prepared post type data.
889          */
890         protected function _prepare_post_type( $post_type, $fields ) {
891                 $_post_type = array(
892                         'name' => $post_type->name,
893                         'label' => $post_type->label,
894                         'hierarchical' => (bool) $post_type->hierarchical,
895                         'public' => (bool) $post_type->public,
896                         'show_ui' => (bool) $post_type->show_ui,
897                         '_builtin' => (bool) $post_type->_builtin,
898                         'has_archive' => (bool) $post_type->has_archive,
899                         'supports' => get_all_post_type_supports( $post_type->name ),
900                 );
901
902                 if ( in_array( 'labels', $fields ) ) {
903                         $_post_type['labels'] = (array) $post_type->labels;
904                 }
905
906                 if ( in_array( 'cap', $fields ) ) {
907                         $_post_type['cap'] = (array) $post_type->cap;
908                         $_post_type['map_meta_cap'] = (bool) $post_type->map_meta_cap;
909                 }
910
911                 if ( in_array( 'menu', $fields ) ) {
912                         $_post_type['menu_position'] = (int) $post_type->menu_position;
913                         $_post_type['menu_icon'] = $post_type->menu_icon;
914                         $_post_type['show_in_menu'] = (bool) $post_type->show_in_menu;
915                 }
916
917                 if ( in_array( 'taxonomies', $fields ) )
918                         $_post_type['taxonomies'] = get_object_taxonomies( $post_type->name, 'names' );
919
920                 /**
921                  * Filter XML-RPC-prepared date for the given post type.
922                  *
923                  * @since 3.4.0
924                  *
925                  * @param array  $_post_type An array of post type data.
926                  * @param object $post_type  Post type object.
927                  */
928                 return apply_filters( 'xmlrpc_prepare_post_type', $_post_type, $post_type );
929         }
930
931         /**
932          * Prepares media item data for return in an XML-RPC object.
933          *
934          * @access protected
935          *
936          * @param object $media_item     The unprepared media item data.
937          * @param string $thumbnail_size The image size to use for the thumbnail URL.
938          * @return array The prepared media item data.
939          */
940         protected function _prepare_media_item( $media_item, $thumbnail_size = 'thumbnail' ) {
941                 $_media_item = array(
942                         'attachment_id'    => strval( $media_item->ID ),
943                         'date_created_gmt' => $this->_convert_date_gmt( $media_item->post_date_gmt, $media_item->post_date ),
944                         'parent'           => $media_item->post_parent,
945                         'link'             => wp_get_attachment_url( $media_item->ID ),
946                         'title'            => $media_item->post_title,
947                         'caption'          => $media_item->post_excerpt,
948                         'description'      => $media_item->post_content,
949                         'metadata'         => wp_get_attachment_metadata( $media_item->ID ),
950                         'type'             => $media_item->post_mime_type
951                 );
952
953                 $thumbnail_src = image_downsize( $media_item->ID, $thumbnail_size );
954                 if ( $thumbnail_src )
955                         $_media_item['thumbnail'] = $thumbnail_src[0];
956                 else
957                         $_media_item['thumbnail'] = $_media_item['link'];
958
959                 /**
960                  * Filter XML-RPC-prepared data for the given media item.
961                  *
962                  * @since 3.4.0
963                  *
964                  * @param array  $_media_item    An array of media item data.
965                  * @param object $media_item     Media item object.
966                  * @param string $thumbnail_size Image size.
967                  */
968                 return apply_filters( 'xmlrpc_prepare_media_item', $_media_item, $media_item, $thumbnail_size );
969         }
970
971         /**
972          * Prepares page data for return in an XML-RPC object.
973          *
974          * @access protected
975          *
976          * @param object $page The unprepared page data.
977          * @return array The prepared page data.
978          */
979         protected function _prepare_page( $page ) {
980                 // Get all of the page content and link.
981                 $full_page = get_extended( $page->post_content );
982                 $link = get_permalink( $page->ID );
983
984                 // Get info the page parent if there is one.
985                 $parent_title = "";
986                 if ( ! empty( $page->post_parent ) ) {
987                         $parent = get_post( $page->post_parent );
988                         $parent_title = $parent->post_title;
989                 }
990
991                 // Determine comment and ping settings.
992                 $allow_comments = comments_open( $page->ID ) ? 1 : 0;
993                 $allow_pings = pings_open( $page->ID ) ? 1 : 0;
994
995                 // Format page date.
996                 $page_date = $this->_convert_date( $page->post_date );
997                 $page_date_gmt = $this->_convert_date_gmt( $page->post_date_gmt, $page->post_date );
998
999                 // Pull the categories info together.
1000                 $categories = array();
1001                 if ( is_object_in_taxonomy( 'page', 'category' ) ) {
1002                         foreach ( wp_get_post_categories( $page->ID ) as $cat_id ) {
1003                                 $categories[] = get_cat_name( $cat_id );
1004                         }
1005                 }
1006
1007                 // Get the author info.
1008                 $author = get_userdata( $page->post_author );
1009
1010                 $page_template = get_page_template_slug( $page->ID );
1011                 if ( empty( $page_template ) )
1012                         $page_template = 'default';
1013
1014                 $_page = array(
1015                         'dateCreated'            => $page_date,
1016                         'userid'                 => $page->post_author,
1017                         'page_id'                => $page->ID,
1018                         'page_status'            => $page->post_status,
1019                         'description'            => $full_page['main'],
1020                         'title'                  => $page->post_title,
1021                         'link'                   => $link,
1022                         'permaLink'              => $link,
1023                         'categories'             => $categories,
1024                         'excerpt'                => $page->post_excerpt,
1025                         'text_more'              => $full_page['extended'],
1026                         'mt_allow_comments'      => $allow_comments,
1027                         'mt_allow_pings'         => $allow_pings,
1028                         'wp_slug'                => $page->post_name,
1029                         'wp_password'            => $page->post_password,
1030                         'wp_author'              => $author->display_name,
1031                         'wp_page_parent_id'      => $page->post_parent,
1032                         'wp_page_parent_title'   => $parent_title,
1033                         'wp_page_order'          => $page->menu_order,
1034                         'wp_author_id'           => (string) $author->ID,
1035                         'wp_author_display_name' => $author->display_name,
1036                         'date_created_gmt'       => $page_date_gmt,
1037                         'custom_fields'          => $this->get_custom_fields( $page->ID ),
1038                         'wp_page_template'       => $page_template
1039                 );
1040
1041                 /**
1042                  * Filter XML-RPC-prepared data for the given page.
1043                  *
1044                  * @since 3.4.0
1045                  *
1046                  * @param array   $_page An array of page data.
1047                  * @param WP_Post $page  Page object.
1048                  */
1049                 return apply_filters( 'xmlrpc_prepare_page', $_page, $page );
1050         }
1051
1052         /**
1053          * Prepares comment data for return in an XML-RPC object.
1054          *
1055          * @access protected
1056          *
1057          * @param object $comment The unprepared comment data.
1058          * @return array The prepared comment data.
1059          */
1060         protected function _prepare_comment( $comment ) {
1061                 // Format page date.
1062                 $comment_date_gmt = $this->_convert_date_gmt( $comment->comment_date_gmt, $comment->comment_date );
1063
1064                 if ( '0' == $comment->comment_approved ) {
1065                         $comment_status = 'hold';
1066                 } elseif ( 'spam' == $comment->comment_approved ) {
1067                         $comment_status = 'spam';
1068                 } elseif ( '1' == $comment->comment_approved ) {
1069                         $comment_status = 'approve';
1070                 } else {
1071                         $comment_status = $comment->comment_approved;
1072                 }
1073                 $_comment = array(
1074                         'date_created_gmt' => $comment_date_gmt,
1075                         'user_id'          => $comment->user_id,
1076                         'comment_id'       => $comment->comment_ID,
1077                         'parent'           => $comment->comment_parent,
1078                         'status'           => $comment_status,
1079                         'content'          => $comment->comment_content,
1080                         'link'             => get_comment_link($comment),
1081                         'post_id'          => $comment->comment_post_ID,
1082                         'post_title'       => get_the_title($comment->comment_post_ID),
1083                         'author'           => $comment->comment_author,
1084                         'author_url'       => $comment->comment_author_url,
1085                         'author_email'     => $comment->comment_author_email,
1086                         'author_ip'        => $comment->comment_author_IP,
1087                         'type'             => $comment->comment_type,
1088                 );
1089
1090                 /**
1091                  * Filter XML-RPC-prepared data for the given comment.
1092                  *
1093                  * @since 3.4.0
1094                  *
1095                  * @param array      $_comment An array of prepared comment data.
1096                  * @param WP_Comment $comment  Comment object.
1097                  */
1098                 return apply_filters( 'xmlrpc_prepare_comment', $_comment, $comment );
1099         }
1100
1101         /**
1102          * Prepares user data for return in an XML-RPC object.
1103          *
1104          * @access protected
1105          *
1106          * @param WP_User $user   The unprepared user object.
1107          * @param array   $fields The subset of user fields to return.
1108          * @return array The prepared user data.
1109          */
1110         protected function _prepare_user( $user, $fields ) {
1111                 $_user = array( 'user_id' => strval( $user->ID ) );
1112
1113                 $user_fields = array(
1114                         'username'          => $user->user_login,
1115                         'first_name'        => $user->user_firstname,
1116                         'last_name'         => $user->user_lastname,
1117                         'registered'        => $this->_convert_date( $user->user_registered ),
1118                         'bio'               => $user->user_description,
1119                         'email'             => $user->user_email,
1120                         'nickname'          => $user->nickname,
1121                         'nicename'          => $user->user_nicename,
1122                         'url'               => $user->user_url,
1123                         'display_name'      => $user->display_name,
1124                         'roles'             => $user->roles,
1125                 );
1126
1127                 if ( in_array( 'all', $fields ) ) {
1128                         $_user = array_merge( $_user, $user_fields );
1129                 } else {
1130                         if ( in_array( 'basic', $fields ) ) {
1131                                 $basic_fields = array( 'username', 'email', 'registered', 'display_name', 'nicename' );
1132                                 $fields = array_merge( $fields, $basic_fields );
1133                         }
1134                         $requested_fields = array_intersect_key( $user_fields, array_flip( $fields ) );
1135                         $_user = array_merge( $_user, $requested_fields );
1136                 }
1137
1138                 /**
1139                  * Filter XML-RPC-prepared data for the given user.
1140                  *
1141                  * @since 3.5.0
1142                  *
1143                  * @param array   $_user  An array of user data.
1144                  * @param WP_User $user   User object.
1145                  * @param array   $fields An array of user fields.
1146                  */
1147                 return apply_filters( 'xmlrpc_prepare_user', $_user, $user, $fields );
1148         }
1149
1150         /**
1151          * Create a new post for any registered post type.
1152          *
1153          * @since 3.4.0
1154          *
1155          * @link http://en.wikipedia.org/wiki/RSS_enclosure for information on RSS enclosures.
1156          *
1157          * @param array  $args {
1158          *     Method arguments. Note: top-level arguments must be ordered as documented.
1159          *
1160          *     @type int    $blog_id        Blog ID (unused).
1161          *     @type string $username       Username.
1162          *     @type string $password       Password.
1163          *     @type array  $content_struct {
1164          *         Content struct for adding a new post. See wp_insert_post() for information on
1165          *         additional post fields
1166          *
1167          *         @type string $post_type      Post type. Default 'post'.
1168          *         @type string $post_status    Post status. Default 'draft'
1169          *         @type string $post_title     Post title.
1170          *         @type int    $post_author    Post author ID.
1171          *         @type string $post_excerpt   Post excerpt.
1172          *         @type string $post_content   Post content.
1173          *         @type string $post_date_gmt  Post date in GMT.
1174          *         @type string $post_date      Post date.
1175          *         @type string $post_password  Post password (20-character limit).
1176          *         @type string $comment_status Post comment enabled status. Accepts 'open' or 'closed'.
1177          *         @type string $ping_status    Post ping status. Accepts 'open' or 'closed'.
1178          *         @type bool   $sticky         Whether the post should be sticky. Automatically false if
1179          *                                      `$post_status` is 'private'.
1180          *         @type int    $post_thumbnail ID of an image to use as the post thumbnail/featured image.
1181          *         @type array  $custom_fields  Array of meta key/value pairs to add to the post.
1182          *         @type array  $terms          Associative array with taxonomy names as keys and arrays
1183          *                                      of term IDs as values.
1184          *         @type array  $terms_names    Associative array with taxonomy names as keys and arrays
1185          *                                      of term names as values.
1186          *         @type array  $enclosure      {
1187          *             Array of feed enclosure data to add to post meta.
1188          *
1189          *             @type string $url    URL for the feed enclosure.
1190          *             @type int    $length Size in bytes of the enclosure.
1191          *             @type string $type   Mime-type for the enclosure.
1192          *         }
1193          *     }
1194          * }
1195          * @return int|IXR_Error Post ID on success, IXR_Error instance otherwise.
1196          */
1197         public function wp_newPost( $args ) {
1198                 if ( ! $this->minimum_args( $args, 4 ) )
1199                         return $this->error;
1200
1201                 $this->escape( $args );
1202
1203                 $username       = $args[1];
1204                 $password       = $args[2];
1205                 $content_struct = $args[3];
1206
1207                 if ( ! $user = $this->login( $username, $password ) )
1208                         return $this->error;
1209
1210                 // convert the date field back to IXR form
1211                 if ( isset( $content_struct['post_date'] ) && ! ( $content_struct['post_date'] instanceof IXR_Date ) ) {
1212                         $content_struct['post_date'] = $this->_convert_date( $content_struct['post_date'] );
1213                 }
1214
1215                 // ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct,
1216                 // since _insert_post will ignore the non-GMT date if the GMT date is set
1217                 if ( isset( $content_struct['post_date_gmt'] ) && ! ( $content_struct['post_date_gmt'] instanceof IXR_Date ) ) {
1218                         if ( $content_struct['post_date_gmt'] == '0000-00-00 00:00:00' || isset( $content_struct['post_date'] ) ) {
1219                                 unset( $content_struct['post_date_gmt'] );
1220                         } else {
1221                                 $content_struct['post_date_gmt'] = $this->_convert_date( $content_struct['post_date_gmt'] );
1222                         }
1223                 }
1224
1225                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1226                 do_action( 'xmlrpc_call', 'wp.newPost' );
1227
1228                 unset( $content_struct['ID'] );
1229
1230                 return $this->_insert_post( $user, $content_struct );
1231         }
1232
1233         /**
1234          * Helper method for filtering out elements from an array.
1235          *
1236          * @since 3.4.0
1237          *
1238          * @param int $count Number to compare to one.
1239          */
1240         private function _is_greater_than_one( $count ) {
1241                 return $count > 1;
1242         }
1243
1244         /**
1245          * Encapsulate the logic for sticking a post
1246          * and determining if the user has permission to do so
1247          *
1248          * @since 4.3.0
1249          * @access private
1250          *
1251          * @param array $post_data
1252          * @param bool  $update
1253          * @return void|IXR_Error
1254          */
1255         private function _toggle_sticky( $post_data, $update = false ) {
1256                 $post_type = get_post_type_object( $post_data['post_type'] );
1257
1258                 // Private and password-protected posts cannot be stickied.
1259                 if ( 'private' === $post_data['post_status'] || ! empty( $post_data['post_password'] ) ) {
1260                         // Error if the client tried to stick the post, otherwise, silently unstick.
1261                         if ( ! empty( $post_data['sticky'] ) ) {
1262                                 return new IXR_Error( 401, __( 'Sorry, you cannot stick a private post.' ) );
1263                         }
1264
1265                         if ( $update ) {
1266                                 unstick_post( $post_data['ID'] );
1267                         }
1268                 } elseif ( isset( $post_data['sticky'] ) )  {
1269                         if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
1270                                 return new IXR_Error( 401, __( 'Sorry, you are not allowed to stick this post.' ) );
1271                         }
1272
1273                         $sticky = wp_validate_boolean( $post_data['sticky'] );
1274                         if ( $sticky ) {
1275                                 stick_post( $post_data['ID'] );
1276                         } else {
1277                                 unstick_post( $post_data['ID'] );
1278                         }
1279                 }
1280         }
1281
1282         /**
1283          * Helper method for wp_newPost() and wp_editPost(), containing shared logic.
1284          *
1285          * @since 3.4.0
1286          * @access protected
1287          *
1288          * @see wp_insert_post()
1289          *
1290          * @param WP_User         $user           The post author if post_author isn't set in $content_struct.
1291          * @param array|IXR_Error $content_struct Post data to insert.
1292          * @return IXR_Error|string
1293          */
1294         protected function _insert_post( $user, $content_struct ) {
1295                 $defaults = array( 'post_status' => 'draft', 'post_type' => 'post', 'post_author' => 0,
1296                         'post_password' => '', 'post_excerpt' => '', 'post_content' => '', 'post_title' => '' );
1297
1298                 $post_data = wp_parse_args( $content_struct, $defaults );
1299
1300                 $post_type = get_post_type_object( $post_data['post_type'] );
1301                 if ( ! $post_type )
1302                         return new IXR_Error( 403, __( 'Invalid post type' ) );
1303
1304                 $update = ! empty( $post_data['ID'] );
1305
1306                 if ( $update ) {
1307                         if ( ! get_post( $post_data['ID'] ) )
1308                                 return new IXR_Error( 401, __( 'Invalid post ID.' ) );
1309                         if ( ! current_user_can( 'edit_post', $post_data['ID'] ) )
1310                                 return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
1311                         if ( $post_data['post_type'] != get_post_type( $post_data['ID'] ) )
1312                                 return new IXR_Error( 401, __( 'The post type may not be changed.' ) );
1313                 } else {
1314                         if ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( $post_type->cap->edit_posts ) )
1315                                 return new IXR_Error( 401, __( 'Sorry, you are not allowed to post on this site.' ) );
1316                 }
1317
1318                 switch ( $post_data['post_status'] ) {
1319                         case 'draft':
1320                         case 'pending':
1321                                 break;
1322                         case 'private':
1323                                 if ( ! current_user_can( $post_type->cap->publish_posts ) )
1324                                         return new IXR_Error( 401, __( 'Sorry, you are not allowed to create private posts in this post type' ) );
1325                                 break;
1326                         case 'publish':
1327                         case 'future':
1328                                 if ( ! current_user_can( $post_type->cap->publish_posts ) )
1329                                         return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts in this post type' ) );
1330                                 break;
1331                         default:
1332                                 if ( ! get_post_status_object( $post_data['post_status'] ) )
1333                                         $post_data['post_status'] = 'draft';
1334                         break;
1335                 }
1336
1337                 if ( ! empty( $post_data['post_password'] ) && ! current_user_can( $post_type->cap->publish_posts ) )
1338                         return new IXR_Error( 401, __( 'Sorry, you are not allowed to create password protected posts in this post type' ) );
1339
1340                 $post_data['post_author'] = absint( $post_data['post_author'] );
1341                 if ( ! empty( $post_data['post_author'] ) && $post_data['post_author'] != $user->ID ) {
1342                         if ( ! current_user_can( $post_type->cap->edit_others_posts ) )
1343                                 return new IXR_Error( 401, __( 'You are not allowed to create posts as this user.' ) );
1344
1345                         $author = get_userdata( $post_data['post_author'] );
1346
1347                         if ( ! $author )
1348                                 return new IXR_Error( 404, __( 'Invalid author ID.' ) );
1349                 } else {
1350                         $post_data['post_author'] = $user->ID;
1351                 }
1352
1353                 if ( isset( $post_data['comment_status'] ) && $post_data['comment_status'] != 'open' && $post_data['comment_status'] != 'closed' )
1354                         unset( $post_data['comment_status'] );
1355
1356                 if ( isset( $post_data['ping_status'] ) && $post_data['ping_status'] != 'open' && $post_data['ping_status'] != 'closed' )
1357                         unset( $post_data['ping_status'] );
1358
1359                 // Do some timestamp voodoo.
1360                 if ( ! empty( $post_data['post_date_gmt'] ) ) {
1361                         // We know this is supposed to be GMT, so we're going to slap that Z on there by force.
1362                         $dateCreated = rtrim( $post_data['post_date_gmt']->getIso(), 'Z' ) . 'Z';
1363                 } elseif ( ! empty( $post_data['post_date'] ) ) {
1364                         $dateCreated = $post_data['post_date']->getIso();
1365                 }
1366
1367                 // Default to not flagging the post date to be edited unless it's intentional.
1368                 $post_data['edit_date'] = false;
1369
1370                 if ( ! empty( $dateCreated ) ) {
1371                         $post_data['post_date'] = get_date_from_gmt( iso8601_to_datetime( $dateCreated ) );
1372                         $post_data['post_date_gmt'] = iso8601_to_datetime( $dateCreated, 'GMT' );
1373
1374                         // Flag the post date to be edited.
1375                         $post_data['edit_date'] = true;
1376                 }
1377
1378                 if ( ! isset( $post_data['ID'] ) )
1379                         $post_data['ID'] = get_default_post_to_edit( $post_data['post_type'], true )->ID;
1380                 $post_ID = $post_data['ID'];
1381
1382                 if ( $post_data['post_type'] == 'post' ) {
1383                         $error = $this->_toggle_sticky( $post_data, $update );
1384                         if ( $error ) {
1385                                 return $error;
1386                         }
1387                 }
1388
1389                 if ( isset( $post_data['post_thumbnail'] ) ) {
1390                         // empty value deletes, non-empty value adds/updates.
1391                         if ( ! $post_data['post_thumbnail'] )
1392                                 delete_post_thumbnail( $post_ID );
1393                         elseif ( ! get_post( absint( $post_data['post_thumbnail'] ) ) )
1394                                 return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
1395                         set_post_thumbnail( $post_ID, $post_data['post_thumbnail'] );
1396                         unset( $content_struct['post_thumbnail'] );
1397                 }
1398
1399                 if ( isset( $post_data['custom_fields'] ) )
1400                         $this->set_custom_fields( $post_ID, $post_data['custom_fields'] );
1401
1402                 if ( isset( $post_data['terms'] ) || isset( $post_data['terms_names'] ) ) {
1403                         $post_type_taxonomies = get_object_taxonomies( $post_data['post_type'], 'objects' );
1404
1405                         // Accumulate term IDs from terms and terms_names.
1406                         $terms = array();
1407
1408                         // First validate the terms specified by ID.
1409                         if ( isset( $post_data['terms'] ) && is_array( $post_data['terms'] ) ) {
1410                                 $taxonomies = array_keys( $post_data['terms'] );
1411
1412                                 // Validating term ids.
1413                                 foreach ( $taxonomies as $taxonomy ) {
1414                                         if ( ! array_key_exists( $taxonomy , $post_type_taxonomies ) )
1415                                                 return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
1416
1417                                         if ( ! current_user_can( $post_type_taxonomies[$taxonomy]->cap->assign_terms ) )
1418                                                 return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
1419
1420                                         $term_ids = $post_data['terms'][$taxonomy];
1421                                         $terms[ $taxonomy ] = array();
1422                                         foreach ( $term_ids as $term_id ) {
1423                                                 $term = get_term_by( 'id', $term_id, $taxonomy );
1424
1425                                                 if ( ! $term )
1426                                                         return new IXR_Error( 403, __( 'Invalid term ID' ) );
1427
1428                                                 $terms[$taxonomy][] = (int) $term_id;
1429                                         }
1430                                 }
1431                         }
1432
1433                         // Now validate terms specified by name.
1434                         if ( isset( $post_data['terms_names'] ) && is_array( $post_data['terms_names'] ) ) {
1435                                 $taxonomies = array_keys( $post_data['terms_names'] );
1436
1437                                 foreach ( $taxonomies as $taxonomy ) {
1438                                         if ( ! array_key_exists( $taxonomy , $post_type_taxonomies ) )
1439                                                 return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
1440
1441                                         if ( ! current_user_can( $post_type_taxonomies[$taxonomy]->cap->assign_terms ) )
1442                                                 return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
1443
1444                                         /*
1445                                          * For hierarchical taxonomies, we can't assign a term when multiple terms
1446                                          * in the hierarchy share the same name.
1447                                          */
1448                                         $ambiguous_terms = array();
1449                                         if ( is_taxonomy_hierarchical( $taxonomy ) ) {
1450                                                 $tax_term_names = get_terms( $taxonomy, array( 'fields' => 'names', 'hide_empty' => false ) );
1451
1452                                                 // Count the number of terms with the same name.
1453                                                 $tax_term_names_count = array_count_values( $tax_term_names );
1454
1455                                                 // Filter out non-ambiguous term names.
1456                                                 $ambiguous_tax_term_counts = array_filter( $tax_term_names_count, array( $this, '_is_greater_than_one') );
1457
1458                                                 $ambiguous_terms = array_keys( $ambiguous_tax_term_counts );
1459                                         }
1460
1461                                         $term_names = $post_data['terms_names'][$taxonomy];
1462                                         foreach ( $term_names as $term_name ) {
1463                                                 if ( in_array( $term_name, $ambiguous_terms ) )
1464                                                         return new IXR_Error( 401, __( 'Ambiguous term name used in a hierarchical taxonomy. Please use term ID instead.' ) );
1465
1466                                                 $term = get_term_by( 'name', $term_name, $taxonomy );
1467
1468                                                 if ( ! $term ) {
1469                                                         // Term doesn't exist, so check that the user is allowed to create new terms.
1470                                                         if ( ! current_user_can( $post_type_taxonomies[$taxonomy]->cap->edit_terms ) )
1471                                                                 return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a term to one of the given taxonomies.' ) );
1472
1473                                                         // Create the new term.
1474                                                         $term_info = wp_insert_term( $term_name, $taxonomy );
1475                                                         if ( is_wp_error( $term_info ) )
1476                                                                 return new IXR_Error( 500, $term_info->get_error_message() );
1477
1478                                                         $terms[$taxonomy][] = (int) $term_info['term_id'];
1479                                                 } else {
1480                                                         $terms[$taxonomy][] = (int) $term->term_id;
1481                                                 }
1482                                         }
1483                                 }
1484                         }
1485
1486                         $post_data['tax_input'] = $terms;
1487                         unset( $post_data['terms'], $post_data['terms_names'] );
1488                 } else {
1489                         // Do not allow direct submission of 'tax_input', clients must use 'terms' and/or 'terms_names'.
1490                         unset( $post_data['tax_input'], $post_data['post_category'], $post_data['tags_input'] );
1491                 }
1492
1493                 if ( isset( $post_data['post_format'] ) ) {
1494                         $format = set_post_format( $post_ID, $post_data['post_format'] );
1495
1496                         if ( is_wp_error( $format ) )
1497                                 return new IXR_Error( 500, $format->get_error_message() );
1498
1499                         unset( $post_data['post_format'] );
1500                 }
1501
1502                 // Handle enclosures.
1503                 $enclosure = isset( $post_data['enclosure'] ) ? $post_data['enclosure'] : null;
1504                 $this->add_enclosure_if_new( $post_ID, $enclosure );
1505
1506                 $this->attach_uploads( $post_ID, $post_data['post_content'] );
1507
1508                 /**
1509                  * Filter post data array to be inserted via XML-RPC.
1510                  *
1511                  * @since 3.4.0
1512                  *
1513                  * @param array $post_data      Parsed array of post data.
1514                  * @param array $content_struct Post data array.
1515                  */
1516                 $post_data = apply_filters( 'xmlrpc_wp_insert_post_data', $post_data, $content_struct );
1517
1518                 $post_ID = $update ? wp_update_post( $post_data, true ) : wp_insert_post( $post_data, true );
1519                 if ( is_wp_error( $post_ID ) )
1520                         return new IXR_Error( 500, $post_ID->get_error_message() );
1521
1522                 if ( ! $post_ID )
1523                         return new IXR_Error( 401, __( 'Sorry, your entry could not be posted. Something wrong happened.' ) );
1524
1525                 return strval( $post_ID );
1526         }
1527
1528         /**
1529          * Edit a post for any registered post type.
1530          *
1531          * The $content_struct parameter only needs to contain fields that
1532          * should be changed. All other fields will retain their existing values.
1533          *
1534          * @since 3.4.0
1535          *
1536          * @param array  $args {
1537          *     Method arguments. Note: arguments must be ordered as documented.
1538          *
1539          *     @type int    $blog_id        Blog ID (unused).
1540          *     @type string $username       Username.
1541          *     @type string $password       Password.
1542          *     @type int    $post_id        Post ID.
1543          *     @type array  $content_struct Extra content arguments.
1544          * }
1545          * @return true|IXR_Error True on success, IXR_Error on failure.
1546          */
1547         public function wp_editPost( $args ) {
1548                 if ( ! $this->minimum_args( $args, 5 ) )
1549                         return $this->error;
1550
1551                 $this->escape( $args );
1552
1553                 $username       = $args[1];
1554                 $password       = $args[2];
1555                 $post_id        = (int) $args[3];
1556                 $content_struct = $args[4];
1557
1558                 if ( ! $user = $this->login( $username, $password ) )
1559                         return $this->error;
1560
1561                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1562                 do_action( 'xmlrpc_call', 'wp.editPost' );
1563
1564                 $post = get_post( $post_id, ARRAY_A );
1565
1566                 if ( empty( $post['ID'] ) )
1567                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1568
1569                 if ( isset( $content_struct['if_not_modified_since'] ) ) {
1570                         // If the post has been modified since the date provided, return an error.
1571                         if ( mysql2date( 'U', $post['post_modified_gmt'] ) > $content_struct['if_not_modified_since']->getTimestamp() ) {
1572                                 return new IXR_Error( 409, __( 'There is a revision of this post that is more recent.' ) );
1573                         }
1574                 }
1575
1576                 // Convert the date field back to IXR form.
1577                 $post['post_date'] = $this->_convert_date( $post['post_date'] );
1578
1579                 /*
1580                  * Ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct,
1581                  * since _insert_post() will ignore the non-GMT date if the GMT date is set.
1582                  */
1583                 if ( $post['post_date_gmt'] == '0000-00-00 00:00:00' || isset( $content_struct['post_date'] ) )
1584                         unset( $post['post_date_gmt'] );
1585                 else
1586                         $post['post_date_gmt'] = $this->_convert_date( $post['post_date_gmt'] );
1587
1588                 $this->escape( $post );
1589                 $merged_content_struct = array_merge( $post, $content_struct );
1590
1591                 $retval = $this->_insert_post( $user, $merged_content_struct );
1592                 if ( $retval instanceof IXR_Error )
1593                         return $retval;
1594
1595                 return true;
1596         }
1597
1598         /**
1599          * Delete a post for any registered post type.
1600          *
1601          * @since 3.4.0
1602          *
1603          * @see wp_delete_post()
1604          *
1605          * @param array  $args {
1606          *     Method arguments. Note: arguments must be ordered as documented.
1607          *
1608          *     @type int    $blog_id  Blog ID (unused).
1609          *     @type string $username Username.
1610          *     @type string $password Password.
1611          *     @type int    $post_id  Post ID.
1612          * }
1613          * @return true|IXR_Error True on success, IXR_Error instance on failure.
1614          */
1615         public function wp_deletePost( $args ) {
1616                 if ( ! $this->minimum_args( $args, 4 ) )
1617                         return $this->error;
1618
1619                 $this->escape( $args );
1620
1621                 $username   = $args[1];
1622                 $password   = $args[2];
1623                 $post_id    = (int) $args[3];
1624
1625                 if ( ! $user = $this->login( $username, $password ) )
1626                         return $this->error;
1627
1628                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1629                 do_action( 'xmlrpc_call', 'wp.deletePost' );
1630
1631                 $post = get_post( $post_id, ARRAY_A );
1632                 if ( empty( $post['ID'] ) ) {
1633                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1634                 }
1635
1636                 if ( ! current_user_can( 'delete_post', $post_id ) ) {
1637                         return new IXR_Error( 401, __( 'Sorry, you do not have the right to delete this post.' ) );
1638                 }
1639
1640                 $result = wp_delete_post( $post_id );
1641
1642                 if ( ! $result ) {
1643                         return new IXR_Error( 500, __( 'The post cannot be deleted.' ) );
1644                 }
1645
1646                 return true;
1647         }
1648
1649         /**
1650          * Retrieve a post.
1651          *
1652          * @since 3.4.0
1653          *
1654          * The optional $fields parameter specifies what fields will be included
1655          * in the response array. This should be a list of field names. 'post_id' will
1656          * always be included in the response regardless of the value of $fields.
1657          *
1658          * Instead of, or in addition to, individual field names, conceptual group
1659          * names can be used to specify multiple fields. The available conceptual
1660          * groups are 'post' (all basic fields), 'taxonomies', 'custom_fields',
1661          * and 'enclosure'.
1662          *
1663          * @see get_post()
1664          *
1665          * @param array $args {
1666          *     Method arguments. Note: arguments must be ordered as documented.
1667          *
1668          *     @type int    $blog_id  Blog ID (unused).
1669          *     @type string $username Username.
1670          *     @type string $password Password.
1671          *     @type int    $post_id  Post ID.
1672          *     @type array  $fields   The subset of post type fields to return.
1673          * }
1674          * @return array|IXR_Error Array contains (based on $fields parameter):
1675          *  - 'post_id'
1676          *  - 'post_title'
1677          *  - 'post_date'
1678          *  - 'post_date_gmt'
1679          *  - 'post_modified'
1680          *  - 'post_modified_gmt'
1681          *  - 'post_status'
1682          *  - 'post_type'
1683          *  - 'post_name'
1684          *  - 'post_author'
1685          *  - 'post_password'
1686          *  - 'post_excerpt'
1687          *  - 'post_content'
1688          *  - 'link'
1689          *  - 'comment_status'
1690          *  - 'ping_status'
1691          *  - 'sticky'
1692          *  - 'custom_fields'
1693          *  - 'terms'
1694          *  - 'categories'
1695          *  - 'tags'
1696          *  - 'enclosure'
1697          */
1698         public function wp_getPost( $args ) {
1699                 if ( ! $this->minimum_args( $args, 4 ) )
1700                         return $this->error;
1701
1702                 $this->escape( $args );
1703
1704                 $username = $args[1];
1705                 $password = $args[2];
1706                 $post_id  = (int) $args[3];
1707
1708                 if ( isset( $args[4] ) ) {
1709                         $fields = $args[4];
1710                 } else {
1711                         /**
1712                          * Filter the list of post query fields used by the given XML-RPC method.
1713                          *
1714                          * @since 3.4.0
1715                          *
1716                          * @param array  $fields Array of post fields. Default array contains 'post', 'terms', and 'custom_fields'.
1717                          * @param string $method Method name.
1718                          */
1719                         $fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPost' );
1720                 }
1721
1722                 if ( ! $user = $this->login( $username, $password ) )
1723                         return $this->error;
1724
1725                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1726                 do_action( 'xmlrpc_call', 'wp.getPost' );
1727
1728                 $post = get_post( $post_id, ARRAY_A );
1729
1730                 if ( empty( $post['ID'] ) )
1731                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1732
1733                 if ( ! current_user_can( 'edit_post', $post_id ) )
1734                         return new IXR_Error( 401, __( 'Sorry, you cannot edit this post.' ) );
1735
1736                 return $this->_prepare_post( $post, $fields );
1737         }
1738
1739         /**
1740          * Retrieve posts.
1741          *
1742          * @since 3.4.0
1743          *
1744          * @see wp_get_recent_posts()
1745          * @see wp_getPost() for more on `$fields`
1746          * @see get_posts() for more on `$filter` values
1747          *
1748          * @param array $args {
1749          *     Method arguments. Note: arguments must be ordered as documented.
1750          *
1751          *     @type int    $blog_id  Blog ID (unused).
1752          *     @type string $username Username.
1753          *     @type string $password Password.
1754          *     @type array  $filter   Optional. Modifies the query used to retrieve posts. Accepts 'post_type',
1755          *                            'post_status', 'number', 'offset', 'orderby', 's', and 'order'.
1756          *                            Default empty array.
1757          *     @type array  $fields   Optional. The subset of post type fields to return in the response array.
1758          * }
1759          * @return array|IXR_Error Array contains a collection of posts.
1760          */
1761         public function wp_getPosts( $args ) {
1762                 if ( ! $this->minimum_args( $args, 3 ) )
1763                         return $this->error;
1764
1765                 $this->escape( $args );
1766
1767                 $username = $args[1];
1768                 $password = $args[2];
1769                 $filter   = isset( $args[3] ) ? $args[3] : array();
1770
1771                 if ( isset( $args[4] ) ) {
1772                         $fields = $args[4];
1773                 } else {
1774                         /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1775                         $fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPosts' );
1776                 }
1777
1778                 if ( ! $user = $this->login( $username, $password ) )
1779                         return $this->error;
1780
1781                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1782                 do_action( 'xmlrpc_call', 'wp.getPosts' );
1783
1784                 $query = array();
1785
1786                 if ( isset( $filter['post_type'] ) ) {
1787                         $post_type = get_post_type_object( $filter['post_type'] );
1788                         if ( ! ( (bool) $post_type ) )
1789                                 return new IXR_Error( 403, __( 'The post type specified is not valid' ) );
1790                 } else {
1791                         $post_type = get_post_type_object( 'post' );
1792                 }
1793
1794                 if ( ! current_user_can( $post_type->cap->edit_posts ) )
1795                         return new IXR_Error( 401, __( 'You are not allowed to edit posts in this post type.' ));
1796
1797                 $query['post_type'] = $post_type->name;
1798
1799                 if ( isset( $filter['post_status'] ) )
1800                         $query['post_status'] = $filter['post_status'];
1801
1802                 if ( isset( $filter['number'] ) )
1803                         $query['numberposts'] = absint( $filter['number'] );
1804
1805                 if ( isset( $filter['offset'] ) )
1806                         $query['offset'] = absint( $filter['offset'] );
1807
1808                 if ( isset( $filter['orderby'] ) ) {
1809                         $query['orderby'] = $filter['orderby'];
1810
1811                         if ( isset( $filter['order'] ) )
1812                                 $query['order'] = $filter['order'];
1813                 }
1814
1815                 if ( isset( $filter['s'] ) ) {
1816                         $query['s'] = $filter['s'];
1817                 }
1818
1819                 $posts_list = wp_get_recent_posts( $query );
1820
1821                 if ( ! $posts_list )
1822                         return array();
1823
1824                 // Holds all the posts data.
1825                 $struct = array();
1826
1827                 foreach ( $posts_list as $post ) {
1828                         if ( ! current_user_can( 'edit_post', $post['ID'] ) )
1829                                 continue;
1830
1831                         $struct[] = $this->_prepare_post( $post, $fields );
1832                 }
1833
1834                 return $struct;
1835         }
1836
1837         /**
1838          * Create a new term.
1839          *
1840          * @since 3.4.0
1841          *
1842          * @see wp_insert_term()
1843          *
1844          * @param array $args {
1845          *     Method arguments. Note: arguments must be ordered as documented.
1846          *
1847          *     @type int    $blog_id        Blog ID (unused).
1848          *     @type string $username       Username.
1849          *     @type string $password       Password.
1850          *     @type array  $content_struct Content struct for adding a new term. The struct must contain
1851          *                                  the term 'name' and 'taxonomy'. Optional accepted values include
1852          *                                  'parent', 'description', and 'slug'.
1853          * }
1854          * @return int|IXR_Error The term ID on success, or an IXR_Error object on failure.
1855          */
1856         public function wp_newTerm( $args ) {
1857                 if ( ! $this->minimum_args( $args, 4 ) )
1858                         return $this->error;
1859
1860                 $this->escape( $args );
1861
1862                 $username       = $args[1];
1863                 $password       = $args[2];
1864                 $content_struct = $args[3];
1865
1866                 if ( ! $user = $this->login( $username, $password ) )
1867                         return $this->error;
1868
1869                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1870                 do_action( 'xmlrpc_call', 'wp.newTerm' );
1871
1872                 if ( ! taxonomy_exists( $content_struct['taxonomy'] ) )
1873                         return new IXR_Error( 403, __( 'Invalid taxonomy' ) );
1874
1875                 $taxonomy = get_taxonomy( $content_struct['taxonomy'] );
1876
1877                 if ( ! current_user_can( $taxonomy->cap->manage_terms ) )
1878                         return new IXR_Error( 401, __( 'You are not allowed to create terms in this taxonomy.' ) );
1879
1880                 $taxonomy = (array) $taxonomy;
1881
1882                 // hold the data of the term
1883                 $term_data = array();
1884
1885                 $term_data['name'] = trim( $content_struct['name'] );
1886                 if ( empty( $term_data['name'] ) )
1887                         return new IXR_Error( 403, __( 'The term name cannot be empty.' ) );
1888
1889                 if ( isset( $content_struct['parent'] ) ) {
1890                         if ( ! $taxonomy['hierarchical'] )
1891                                 return new IXR_Error( 403, __( 'This taxonomy is not hierarchical.' ) );
1892
1893                         $parent_term_id = (int) $content_struct['parent'];
1894                         $parent_term = get_term( $parent_term_id , $taxonomy['name'] );
1895
1896                         if ( is_wp_error( $parent_term ) )
1897                                 return new IXR_Error( 500, $parent_term->get_error_message() );
1898
1899                         if ( ! $parent_term )
1900                                 return new IXR_Error( 403, __( 'Parent term does not exist.' ) );
1901
1902                         $term_data['parent'] = $content_struct['parent'];
1903                 }
1904
1905                 if ( isset( $content_struct['description'] ) )
1906                         $term_data['description'] = $content_struct['description'];
1907
1908                 if ( isset( $content_struct['slug'] ) )
1909                         $term_data['slug'] = $content_struct['slug'];
1910
1911                 $term = wp_insert_term( $term_data['name'] , $taxonomy['name'] , $term_data );
1912
1913                 if ( is_wp_error( $term ) )
1914                         return new IXR_Error( 500, $term->get_error_message() );
1915
1916                 if ( ! $term )
1917                         return new IXR_Error( 500, __( 'Sorry, your term could not be created. Something wrong happened.' ) );
1918
1919                 return strval( $term['term_id'] );
1920         }
1921
1922         /**
1923          * Edit a term.
1924          *
1925          * @since 3.4.0
1926          *
1927          * @see wp_update_term()
1928          *
1929          * @param array $args {
1930          *     Method arguments. Note: arguments must be ordered as documented.
1931          *
1932          *     @type int    $blog_id        Blog ID (unused).
1933          *     @type string $username       Username.
1934          *     @type string $password       Password.
1935          *     @type int    $term_id        Term ID.
1936          *     @type array  $content_struct Content struct for editing a term. The struct must contain the
1937          *                                  term ''taxonomy'. Optional accepted values include 'name', 'parent',
1938          *                                  'description', and 'slug'.
1939          * }
1940          * @return true|IXR_Error True on success, IXR_Error instance on failure.
1941          */
1942         public function wp_editTerm( $args ) {
1943                 if ( ! $this->minimum_args( $args, 5 ) )
1944                         return $this->error;
1945
1946                 $this->escape( $args );
1947
1948                 $username       = $args[1];
1949                 $password       = $args[2];
1950                 $term_id        = (int) $args[3];
1951                 $content_struct = $args[4];
1952
1953                 if ( ! $user = $this->login( $username, $password ) )
1954                         return $this->error;
1955
1956                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1957                 do_action( 'xmlrpc_call', 'wp.editTerm' );
1958
1959                 if ( ! taxonomy_exists( $content_struct['taxonomy'] ) )
1960                         return new IXR_Error( 403, __( 'Invalid taxonomy' ) );
1961
1962                 $taxonomy = get_taxonomy( $content_struct['taxonomy'] );
1963
1964                 if ( ! current_user_can( $taxonomy->cap->edit_terms ) )
1965                         return new IXR_Error( 401, __( 'You are not allowed to edit terms in this taxonomy.' ) );
1966
1967                 $taxonomy = (array) $taxonomy;
1968
1969                 // hold the data of the term
1970                 $term_data = array();
1971
1972                 $term = get_term( $term_id , $content_struct['taxonomy'] );
1973
1974                 if ( is_wp_error( $term ) )
1975                         return new IXR_Error( 500, $term->get_error_message() );
1976
1977                 if ( ! $term )
1978                         return new IXR_Error( 404, __( 'Invalid term ID' ) );
1979
1980                 if ( isset( $content_struct['name'] ) ) {
1981                         $term_data['name'] = trim( $content_struct['name'] );
1982
1983                         if ( empty( $term_data['name'] ) )
1984                                 return new IXR_Error( 403, __( 'The term name cannot be empty.' ) );
1985                 }
1986
1987                 if ( ! empty( $content_struct['parent'] ) ) {
1988                         if ( ! $taxonomy['hierarchical'] )
1989                                 return new IXR_Error( 403, __( "This taxonomy is not hierarchical so you can't set a parent." ) );
1990
1991                         $parent_term_id = (int) $content_struct['parent'];
1992                         $parent_term = get_term( $parent_term_id , $taxonomy['name'] );
1993
1994                         if ( is_wp_error( $parent_term ) )
1995                                 return new IXR_Error( 500, $parent_term->get_error_message() );
1996
1997                         if ( ! $parent_term )
1998                                 return new IXR_Error( 403, __( 'Parent term does not exist.' ) );
1999
2000                         $term_data['parent'] = $content_struct['parent'];
2001                 }
2002
2003                 if ( isset( $content_struct['description'] ) )
2004                         $term_data['description'] = $content_struct['description'];
2005
2006                 if ( isset( $content_struct['slug'] ) )
2007                         $term_data['slug'] = $content_struct['slug'];
2008
2009                 $term = wp_update_term( $term_id , $taxonomy['name'] , $term_data );
2010
2011                 if ( is_wp_error( $term ) )
2012                         return new IXR_Error( 500, $term->get_error_message() );
2013
2014                 if ( ! $term )
2015                         return new IXR_Error( 500, __( 'Sorry, editing the term failed.' ) );
2016
2017                 return true;
2018         }
2019
2020         /**
2021          * Delete a term.
2022          *
2023          * @since 3.4.0
2024          *
2025          * @see wp_delete_term()
2026          *
2027          * @param array  $args {
2028          *     Method arguments. Note: arguments must be ordered as documented.
2029          *
2030          *     @type int    $blog_id      Blog ID (unused).
2031          *     @type string $username     Username.
2032          *     @type string $password     Password.
2033          *     @type string $taxnomy_name Taxonomy name.
2034          *     @type int    $term_id      Term ID.
2035          * }
2036          * @return bool|IXR_Error True on success, IXR_Error instance on failure.
2037          */
2038         public function wp_deleteTerm( $args ) {
2039                 if ( ! $this->minimum_args( $args, 5 ) )
2040                         return $this->error;
2041
2042                 $this->escape( $args );
2043
2044                 $username           = $args[1];
2045                 $password           = $args[2];
2046                 $taxonomy           = $args[3];
2047                 $term_id            = (int) $args[4];
2048
2049                 if ( ! $user = $this->login( $username, $password ) )
2050                         return $this->error;
2051
2052                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2053                 do_action( 'xmlrpc_call', 'wp.deleteTerm' );
2054
2055                 if ( ! taxonomy_exists( $taxonomy ) )
2056                         return new IXR_Error( 403, __( 'Invalid taxonomy' ) );
2057
2058                 $taxonomy = get_taxonomy( $taxonomy );
2059
2060                 if ( ! current_user_can( $taxonomy->cap->delete_terms ) )
2061                         return new IXR_Error( 401, __( 'You are not allowed to delete terms in this taxonomy.' ) );
2062
2063                 $term = get_term( $term_id, $taxonomy->name );
2064
2065                 if ( is_wp_error( $term ) )
2066                         return new IXR_Error( 500, $term->get_error_message() );
2067
2068                 if ( ! $term )
2069                         return new IXR_Error( 404, __( 'Invalid term ID' ) );
2070
2071                 $result = wp_delete_term( $term_id, $taxonomy->name );
2072
2073                 if ( is_wp_error( $result ) )
2074                         return new IXR_Error( 500, $term->get_error_message() );
2075
2076                 if ( ! $result )
2077                         return new IXR_Error( 500, __( 'Sorry, deleting the term failed.' ) );
2078
2079                 return $result;
2080         }
2081
2082         /**
2083          * Retrieve a term.
2084          *
2085          * @since 3.4.0
2086          *
2087          * @see get_term()
2088          *
2089          * @param array  $args {
2090          *     Method arguments. Note: arguments must be ordered as documented.
2091          *
2092          *     @type int    $blog_id  Blog ID (unused).
2093          *     @type string $username Username.
2094          *     @type string $password Password.
2095          *     @type string $taxnomy  Taxonomy name.
2096          *     @type string $term_id  Term ID.
2097          * }
2098          * @return array|IXR_Error IXR_Error on failure, array on success, containing:
2099          *  - 'term_id'
2100          *  - 'name'
2101          *  - 'slug'
2102          *  - 'term_group'
2103          *  - 'term_taxonomy_id'
2104          *  - 'taxonomy'
2105          *  - 'description'
2106          *  - 'parent'
2107          *  - 'count'
2108          */
2109         public function wp_getTerm( $args ) {
2110                 if ( ! $this->minimum_args( $args, 5 ) )
2111                         return $this->error;
2112
2113                 $this->escape( $args );
2114
2115                 $username           = $args[1];
2116                 $password           = $args[2];
2117                 $taxonomy           = $args[3];
2118                 $term_id            = (int) $args[4];
2119
2120                 if ( ! $user = $this->login( $username, $password ) )
2121                         return $this->error;
2122
2123                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2124                 do_action( 'xmlrpc_call', 'wp.getTerm' );
2125
2126                 if ( ! taxonomy_exists( $taxonomy ) )
2127                         return new IXR_Error( 403, __( 'Invalid taxonomy' ) );
2128
2129                 $taxonomy = get_taxonomy( $taxonomy );
2130
2131                 if ( ! current_user_can( $taxonomy->cap->assign_terms ) )
2132                         return new IXR_Error( 401, __( 'You are not allowed to assign terms in this taxonomy.' ) );
2133
2134                 $term = get_term( $term_id , $taxonomy->name, ARRAY_A );
2135
2136                 if ( is_wp_error( $term ) )
2137                         return new IXR_Error( 500, $term->get_error_message() );
2138
2139                 if ( ! $term )
2140                         return new IXR_Error( 404, __( 'Invalid term ID' ) );
2141
2142                 return $this->_prepare_term( $term );
2143         }
2144
2145         /**
2146          * Retrieve all terms for a taxonomy.
2147          *
2148          * @since 3.4.0
2149          *
2150          * The optional $filter parameter modifies the query used to retrieve terms.
2151          * Accepted keys are 'number', 'offset', 'orderby', 'order', 'hide_empty', and 'search'.
2152          *
2153          * @see get_terms()
2154          *
2155          * @param array  $args {
2156          *     Method arguments. Note: arguments must be ordered as documented.
2157          *
2158          *     @type int    $blog_id  Blog ID (unused).
2159          *     @type string $username Username.
2160          *     @type string $password Password.
2161          *     @type string $taxnomy  Taxonomy name.
2162          *     @type array  $filter   Optional. Modifies the query used to retrieve posts. Accepts 'number',
2163          *                            'offset', 'orderby', 'order', 'hide_empty', and 'search'. Default empty array.
2164          * }
2165          * @return array|IXR_Error An associative array of terms data on success, IXR_Error instance otherwise.
2166          */
2167         public function wp_getTerms( $args ) {
2168                 if ( ! $this->minimum_args( $args, 4 ) )
2169                         return $this->error;
2170
2171                 $this->escape( $args );
2172
2173                 $username       = $args[1];
2174                 $password       = $args[2];
2175                 $taxonomy       = $args[3];
2176                 $filter         = isset( $args[4] ) ? $args[4] : array();
2177
2178                 if ( ! $user = $this->login( $username, $password ) )
2179                         return $this->error;
2180
2181                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2182                 do_action( 'xmlrpc_call', 'wp.getTerms' );
2183
2184                 if ( ! taxonomy_exists( $taxonomy ) )
2185                         return new IXR_Error( 403, __( 'Invalid taxonomy' ) );
2186
2187                 $taxonomy = get_taxonomy( $taxonomy );
2188
2189                 if ( ! current_user_can( $taxonomy->cap->assign_terms ) )
2190                         return new IXR_Error( 401, __( 'You are not allowed to assign terms in this taxonomy.' ) );
2191
2192                 $query = array();
2193
2194                 if ( isset( $filter['number'] ) )
2195                         $query['number'] = absint( $filter['number'] );
2196
2197                 if ( isset( $filter['offset'] ) )
2198                         $query['offset'] = absint( $filter['offset'] );
2199
2200                 if ( isset( $filter['orderby'] ) ) {
2201                         $query['orderby'] = $filter['orderby'];
2202
2203                         if ( isset( $filter['order'] ) )
2204                                 $query['order'] = $filter['order'];
2205                 }
2206
2207                 if ( isset( $filter['hide_empty'] ) )
2208                         $query['hide_empty'] = $filter['hide_empty'];
2209                 else
2210                         $query['get'] = 'all';
2211
2212                 if ( isset( $filter['search'] ) )
2213                         $query['search'] = $filter['search'];
2214
2215                 $terms = get_terms( $taxonomy->name, $query );
2216
2217                 if ( is_wp_error( $terms ) )
2218                         return new IXR_Error( 500, $terms->get_error_message() );
2219
2220                 $struct = array();
2221
2222                 foreach ( $terms as $term ) {
2223                         $struct[] = $this->_prepare_term( $term );
2224                 }
2225
2226                 return $struct;
2227         }
2228
2229         /**
2230          * Retrieve a taxonomy.
2231          *
2232          * @since 3.4.0
2233          *
2234          * @see get_taxonomy()
2235          *
2236          * @param array  $args {
2237          *     Method arguments. Note: arguments must be ordered as documented.
2238          *
2239          *     @type int    $blog_id  Blog ID (unused).
2240          *     @type string $username Username.
2241          *     @type string $password Password.
2242          *     @type string $taxnomy  Taxonomy name.
2243          *     @type array  $fields   Optional. Array of taxonomy fields to limit to in the return.
2244          *                            Accepts 'labels', 'cap', 'menu', and 'object_type'.
2245          *                            Default empty array.
2246          * }
2247          * @return array|IXR_Error An array of taxonomy data on success, IXR_Error instance otherwise.
2248          */
2249         public function wp_getTaxonomy( $args ) {
2250                 if ( ! $this->minimum_args( $args, 4 ) )
2251                         return $this->error;
2252
2253                 $this->escape( $args );
2254
2255                 $username = $args[1];
2256                 $password = $args[2];
2257                 $taxonomy = $args[3];
2258
2259                 if ( isset( $args[4] ) ) {
2260                         $fields = $args[4];
2261                 } else {
2262                         /**
2263                          * Filter the taxonomy query fields used by the given XML-RPC method.
2264                          *
2265                          * @since 3.4.0
2266                          *
2267                          * @param array  $fields An array of taxonomy fields to retrieve.
2268                          * @param string $method The method name.
2269                          */
2270                         $fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomy' );
2271                 }
2272
2273                 if ( ! $user = $this->login( $username, $password ) )
2274                         return $this->error;
2275
2276                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2277                 do_action( 'xmlrpc_call', 'wp.getTaxonomy' );
2278
2279                 if ( ! taxonomy_exists( $taxonomy ) )
2280                         return new IXR_Error( 403, __( 'Invalid taxonomy' ) );
2281
2282                 $taxonomy = get_taxonomy( $taxonomy );
2283
2284                 if ( ! current_user_can( $taxonomy->cap->assign_terms ) )
2285                         return new IXR_Error( 401, __( 'You are not allowed to assign terms in this taxonomy.' ) );
2286
2287                 return $this->_prepare_taxonomy( $taxonomy, $fields );
2288         }
2289
2290         /**
2291          * Retrieve all taxonomies.
2292          *
2293          * @since 3.4.0
2294          *
2295          * @see get_taxonomies()
2296          *
2297          * @param array  $args {
2298          *     Method arguments. Note: arguments must be ordered as documented.
2299          *
2300          *     @type int    $blog_id  Blog ID (unused).
2301          *     @type string $username Username.
2302          *     @type string $password Password.
2303          *     @type array  $filter   Optional. An array of arguments for retrieving taxonomies.
2304          *     @type array  $fields   Optional. The subset of taxonomy fields to return.
2305          * }
2306          * @return array|IXR_Error An associative array of taxonomy data with returned fields determined
2307          *                         by `$fields`, or an IXR_Error instance on failure.
2308          */
2309         public function wp_getTaxonomies( $args ) {
2310                 if ( ! $this->minimum_args( $args, 3 ) )
2311                         return $this->error;
2312
2313                 $this->escape( $args );
2314
2315                 $username = $args[1];
2316                 $password = $args[2];
2317                 $filter   = isset( $args[3] ) ? $args[3] : array( 'public' => true );
2318
2319                 if ( isset( $args[4] ) ) {
2320                         $fields = $args[4];
2321                 } else {
2322                         /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2323                         $fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomies' );
2324                 }
2325
2326                 if ( ! $user = $this->login( $username, $password ) )
2327                         return $this->error;
2328
2329                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2330                 do_action( 'xmlrpc_call', 'wp.getTaxonomies' );
2331
2332                 $taxonomies = get_taxonomies( $filter, 'objects' );
2333
2334                 // holds all the taxonomy data
2335                 $struct = array();
2336
2337                 foreach ( $taxonomies as $taxonomy ) {
2338                         // capability check for post_types
2339                         if ( ! current_user_can( $taxonomy->cap->assign_terms ) )
2340                                 continue;
2341
2342                         $struct[] = $this->_prepare_taxonomy( $taxonomy, $fields );
2343                 }
2344
2345                 return $struct;
2346         }
2347
2348         /**
2349          * Retrieve a user.
2350          *
2351          * The optional $fields parameter specifies what fields will be included
2352          * in the response array. This should be a list of field names. 'user_id' will
2353          * always be included in the response regardless of the value of $fields.
2354          *
2355          * Instead of, or in addition to, individual field names, conceptual group
2356          * names can be used to specify multiple fields. The available conceptual
2357          * groups are 'basic' and 'all'.
2358          *
2359          * @uses get_userdata()
2360          *
2361          * @param array  $args {
2362          *     Method arguments. Note: arguments must be ordered as documented.
2363          *
2364          *     @type int    $blog_id (unused)
2365          *     @type string $username
2366          *     @type string $password
2367          *     @type int    $user_id
2368          *     @type array  $fields (optional)
2369          * }
2370          * @return array|IXR_Error Array contains (based on $fields parameter):
2371          *  - 'user_id'
2372          *  - 'username'
2373          *  - 'first_name'
2374          *  - 'last_name'
2375          *  - 'registered'
2376          *  - 'bio'
2377          *  - 'email'
2378          *  - 'nickname'
2379          *  - 'nicename'
2380          *  - 'url'
2381          *  - 'display_name'
2382          *  - 'roles'
2383          */
2384         public function wp_getUser( $args ) {
2385                 if ( ! $this->minimum_args( $args, 4 ) )
2386                         return $this->error;
2387
2388                 $this->escape( $args );
2389
2390                 $username = $args[1];
2391                 $password = $args[2];
2392                 $user_id  = (int) $args[3];
2393
2394                 if ( isset( $args[4] ) ) {
2395                         $fields = $args[4];
2396                 } else {
2397                         /**
2398                          * Filter the default user query fields used by the given XML-RPC method.
2399                          *
2400                          * @since 3.5.0
2401                          *
2402                          * @param array  $fields User query fields for given method. Default 'all'.
2403                          * @param string $method The method name.
2404                          */
2405                         $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUser' );
2406                 }
2407
2408                 if ( ! $user = $this->login( $username, $password ) )
2409                         return $this->error;
2410
2411                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2412                 do_action( 'xmlrpc_call', 'wp.getUser' );
2413
2414                 if ( ! current_user_can( 'edit_user', $user_id ) )
2415                         return new IXR_Error( 401, __( 'Sorry, you cannot edit users.' ) );
2416
2417                 $user_data = get_userdata( $user_id );
2418
2419                 if ( ! $user_data )
2420                         return new IXR_Error( 404, __( 'Invalid user ID.' ) );
2421
2422                 return $this->_prepare_user( $user_data, $fields );
2423         }
2424
2425         /**
2426          * Retrieve users.
2427          *
2428          * The optional $filter parameter modifies the query used to retrieve users.
2429          * Accepted keys are 'number' (default: 50), 'offset' (default: 0), 'role',
2430          * 'who', 'orderby', and 'order'.
2431          *
2432          * The optional $fields parameter specifies what fields will be included
2433          * in the response array.
2434          *
2435          * @uses get_users()
2436          * @see wp_getUser() for more on $fields and return values
2437          *
2438          * @param array  $args {
2439          *     Method arguments. Note: arguments must be ordered as documented.
2440          *
2441          *     @type int    $blog_id (unused)
2442          *     @type string $username
2443          *     @type string $password
2444          *     @type array  $filter (optional)
2445          *     @type array  $fields (optional)
2446          * }
2447          * @return array|IXR_Error users data
2448          */
2449         public function wp_getUsers( $args ) {
2450                 if ( ! $this->minimum_args( $args, 3 ) )
2451                         return $this->error;
2452
2453                 $this->escape( $args );
2454
2455                 $username = $args[1];
2456                 $password = $args[2];
2457                 $filter   = isset( $args[3] ) ? $args[3] : array();
2458
2459                 if ( isset( $args[4] ) ) {
2460                         $fields = $args[4];
2461                 } else {
2462                         /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2463                         $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUsers' );
2464                 }
2465
2466                 if ( ! $user = $this->login( $username, $password ) )
2467                         return $this->error;
2468
2469                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2470                 do_action( 'xmlrpc_call', 'wp.getUsers' );
2471
2472                 if ( ! current_user_can( 'list_users' ) )
2473                         return new IXR_Error( 401, __( 'You are not allowed to browse users.' ) );
2474
2475                 $query = array( 'fields' => 'all_with_meta' );
2476
2477                 $query['number'] = ( isset( $filter['number'] ) ) ? absint( $filter['number'] ) : 50;
2478                 $query['offset'] = ( isset( $filter['offset'] ) ) ? absint( $filter['offset'] ) : 0;
2479
2480                 if ( isset( $filter['orderby'] ) ) {
2481                         $query['orderby'] = $filter['orderby'];
2482
2483                         if ( isset( $filter['order'] ) )
2484                                 $query['order'] = $filter['order'];
2485                 }
2486
2487                 if ( isset( $filter['role'] ) ) {
2488                         if ( get_role( $filter['role'] ) === null )
2489                                 return new IXR_Error( 403, __( 'The role specified is not valid' ) );
2490
2491                         $query['role'] = $filter['role'];
2492                 }
2493
2494                 if ( isset( $filter['who'] ) ) {
2495                         $query['who'] = $filter['who'];
2496                 }
2497
2498                 $users = get_users( $query );
2499
2500                 $_users = array();
2501                 foreach ( $users as $user_data ) {
2502                         if ( current_user_can( 'edit_user', $user_data->ID ) )
2503                                 $_users[] = $this->_prepare_user( $user_data, $fields );
2504                 }
2505                 return $_users;
2506         }
2507
2508         /**
2509          * Retrieve information about the requesting user.
2510          *
2511          * @uses get_userdata()
2512          *
2513          * @param array  $args {
2514          *     Method arguments. Note: arguments must be ordered as documented.
2515          *
2516          *     @type int    $blog_id (unused)
2517          *     @type string $username
2518          *     @type string $password
2519          *     @type array  $fields (optional)
2520          * }
2521          * @return array|IXR_Error (@see wp_getUser)
2522          */
2523         public function wp_getProfile( $args ) {
2524                 if ( ! $this->minimum_args( $args, 3 ) )
2525                         return $this->error;
2526
2527                 $this->escape( $args );
2528
2529                 $username = $args[1];
2530                 $password = $args[2];
2531
2532                 if ( isset( $args[3] ) ) {
2533                         $fields = $args[3];
2534                 } else {
2535                         /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2536                         $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getProfile' );
2537                 }
2538
2539                 if ( ! $user = $this->login( $username, $password ) )
2540                         return $this->error;
2541
2542                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2543                 do_action( 'xmlrpc_call', 'wp.getProfile' );
2544
2545                 if ( ! current_user_can( 'edit_user', $user->ID ) )
2546                         return new IXR_Error( 401, __( 'Sorry, you cannot edit your profile.' ) );
2547
2548                 $user_data = get_userdata( $user->ID );
2549
2550                 return $this->_prepare_user( $user_data, $fields );
2551         }
2552
2553         /**
2554          * Edit user's profile.
2555          *
2556          * @uses wp_update_user()
2557          *
2558          * @param array  $args {
2559          *     Method arguments. Note: arguments must be ordered as documented.
2560          *
2561          *     @type int    $blog_id (unused)
2562          *     @type string $username
2563          *     @type string $password
2564          *     @type array  $content_struct It can optionally contain:
2565          *      - 'first_name'
2566          *      - 'last_name'
2567          *      - 'website'
2568          *      - 'display_name'
2569          *      - 'nickname'
2570          *      - 'nicename'
2571          *      - 'bio'
2572          * }
2573          * @return true|IXR_Error True, on success.
2574          */
2575         public function wp_editProfile( $args ) {
2576                 if ( ! $this->minimum_args( $args, 4 ) )
2577                         return $this->error;
2578
2579                 $this->escape( $args );
2580
2581                 $username       = $args[1];
2582                 $password       = $args[2];
2583                 $content_struct = $args[3];
2584
2585                 if ( ! $user = $this->login( $username, $password ) )
2586                         return $this->error;
2587
2588                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2589                 do_action( 'xmlrpc_call', 'wp.editProfile' );
2590
2591                 if ( ! current_user_can( 'edit_user', $user->ID ) )
2592                         return new IXR_Error( 401, __( 'Sorry, you cannot edit your profile.' ) );
2593
2594                 // holds data of the user
2595                 $user_data = array();
2596                 $user_data['ID'] = $user->ID;
2597
2598                 // only set the user details if it was given
2599                 if ( isset( $content_struct['first_name'] ) )
2600                         $user_data['first_name'] = $content_struct['first_name'];
2601
2602                 if ( isset( $content_struct['last_name'] ) )
2603                         $user_data['last_name'] = $content_struct['last_name'];
2604
2605                 if ( isset( $content_struct['url'] ) )
2606                         $user_data['user_url'] = $content_struct['url'];
2607
2608                 if ( isset( $content_struct['display_name'] ) )
2609                         $user_data['display_name'] = $content_struct['display_name'];
2610
2611                 if ( isset( $content_struct['nickname'] ) )
2612                         $user_data['nickname'] = $content_struct['nickname'];
2613
2614                 if ( isset( $content_struct['nicename'] ) )
2615                         $user_data['user_nicename'] = $content_struct['nicename'];
2616
2617                 if ( isset( $content_struct['bio'] ) )
2618                         $user_data['description'] = $content_struct['bio'];
2619
2620                 $result = wp_update_user( $user_data );
2621
2622                 if ( is_wp_error( $result ) )
2623                         return new IXR_Error( 500, $result->get_error_message() );
2624
2625                 if ( ! $result )
2626                         return new IXR_Error( 500, __( 'Sorry, the user cannot be updated.' ) );
2627
2628                 return true;
2629         }
2630
2631         /**
2632          * Retrieve page.
2633          *
2634          * @since 2.2.0
2635          *
2636          * @param array  $args {
2637          *     Method arguments. Note: arguments must be ordered as documented.
2638          *
2639          *     @type int    $blog_id (unused)
2640          *     @type int    $page_id
2641          *     @type string $username
2642          *     @type string $password
2643          * }
2644          * @return array|IXR_Error
2645          */
2646         public function wp_getPage( $args ) {
2647                 $this->escape( $args );
2648
2649                 $page_id  = (int) $args[1];
2650                 $username = $args[2];
2651                 $password = $args[3];
2652
2653                 if ( !$user = $this->login($username, $password) ) {
2654                         return $this->error;
2655                 }
2656
2657                 $page = get_post($page_id);
2658                 if ( ! $page )
2659                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
2660
2661                 if ( !current_user_can( 'edit_page', $page_id ) )
2662                         return new IXR_Error( 401, __( 'Sorry, you cannot edit this page.' ) );
2663
2664                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2665                 do_action( 'xmlrpc_call', 'wp.getPage' );
2666
2667                 // If we found the page then format the data.
2668                 if ( $page->ID && ($page->post_type == 'page') ) {
2669                         return $this->_prepare_page( $page );
2670                 }
2671                 // If the page doesn't exist indicate that.
2672                 else {
2673                         return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
2674                 }
2675         }
2676
2677         /**
2678          * Retrieve Pages.
2679          *
2680          * @since 2.2.0
2681          *
2682          * @param array  $args {
2683          *     Method arguments. Note: arguments must be ordered as documented.
2684          *
2685          *     @type int    $blog_id (unused)
2686          *     @type string $username
2687          *     @type string $password
2688          *     @type int    $num_pages
2689          * }
2690          * @return array|IXR_Error
2691          */
2692         public function wp_getPages( $args ) {
2693                 $this->escape( $args );
2694
2695                 $username  = $args[1];
2696                 $password  = $args[2];
2697                 $num_pages = isset($args[3]) ? (int) $args[3] : 10;
2698
2699                 if ( !$user = $this->login($username, $password) )
2700                         return $this->error;
2701
2702                 if ( !current_user_can( 'edit_pages' ) )
2703                         return new IXR_Error( 401, __( 'Sorry, you cannot edit pages.' ) );
2704
2705                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2706                 do_action( 'xmlrpc_call', 'wp.getPages' );
2707
2708                 $pages = get_posts( array('post_type' => 'page', 'post_status' => 'any', 'numberposts' => $num_pages) );
2709                 $num_pages = count($pages);
2710
2711                 // If we have pages, put together their info.
2712                 if ( $num_pages >= 1 ) {
2713                         $pages_struct = array();
2714
2715                         foreach ($pages as $page) {
2716                                 if ( current_user_can( 'edit_page', $page->ID ) )
2717                                         $pages_struct[] = $this->_prepare_page( $page );
2718                         }
2719
2720                         return $pages_struct;
2721                 }
2722
2723                 return array();
2724         }
2725
2726         /**
2727          * Create new page.
2728          *
2729          * @since 2.2.0
2730          *
2731          * @see wp_xmlrpc_server::mw_newPost()
2732          *
2733          * @param array  $args {
2734          *     Method arguments. Note: arguments must be ordered as documented.
2735          *
2736          *     @type int    $blog_id (unused)
2737          *     @type string $username
2738          *     @type string $password
2739          *     @type array  $content_struct
2740          * }
2741          * @return int|IXR_Error
2742          */
2743         public function wp_newPage( $args ) {
2744                 // Items not escaped here will be escaped in newPost.
2745                 $username = $this->escape( $args[1] );
2746                 $password = $this->escape( $args[2] );
2747
2748                 if ( !$user = $this->login($username, $password) )
2749                         return $this->error;
2750
2751                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2752                 do_action( 'xmlrpc_call', 'wp.newPage' );
2753
2754                 // Mark this as content for a page.
2755                 $args[3]["post_type"] = 'page';
2756
2757                 // Let mw_newPost do all of the heavy lifting.
2758                 return $this->mw_newPost( $args );
2759         }
2760
2761         /**
2762          * Delete page.
2763          *
2764          * @since 2.2.0
2765          *
2766          * @param array  $args {
2767          *     Method arguments. Note: arguments must be ordered as documented.
2768          *
2769          *     @type int    $blog_id (unused)
2770          *     @type string $username
2771          *     @type string $password
2772          *     @type int    $page_id
2773          * }
2774          * @return true|IXR_Error True, if success.
2775          */
2776         public function wp_deletePage( $args ) {
2777                 $this->escape( $args );
2778
2779                 $username = $args[1];
2780                 $password = $args[2];
2781                 $page_id  = (int) $args[3];
2782
2783                 if ( !$user = $this->login($username, $password) )
2784                         return $this->error;
2785
2786                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2787                 do_action( 'xmlrpc_call', 'wp.deletePage' );
2788
2789                 // Get the current page based on the page_id and
2790                 // make sure it is a page and not a post.
2791                 $actual_page = get_post($page_id, ARRAY_A);
2792                 if ( !$actual_page || ($actual_page['post_type'] != 'page') )
2793                         return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
2794
2795                 // Make sure the user can delete pages.
2796                 if ( !current_user_can('delete_page', $page_id) )
2797                         return new IXR_Error( 401, __( 'Sorry, you do not have the right to delete this page.' ) );
2798
2799                 // Attempt to delete the page.
2800                 $result = wp_delete_post($page_id);
2801                 if ( !$result )
2802                         return new IXR_Error( 500, __( 'Failed to delete the page.' ) );
2803
2804                 /**
2805                  * Fires after a page has been successfully deleted via XML-RPC.
2806                  *
2807                  * @since 3.4.0
2808                  *
2809                  * @param int   $page_id ID of the deleted page.
2810                  * @param array $args    An array of arguments to delete the page.
2811                  */
2812                 do_action( 'xmlrpc_call_success_wp_deletePage', $page_id, $args );
2813
2814                 return true;
2815         }
2816
2817         /**
2818          * Edit page.
2819          *
2820          * @since 2.2.0
2821          *
2822          * @param array  $args {
2823          *     Method arguments. Note: arguments must be ordered as documented.
2824          *
2825          *     @type int    $blog_id (unused)
2826          *     @type int    $page_id
2827          *     @type string $username
2828          *     @type string $password
2829          *     @type string $content
2830          *     @type string $publish
2831          * }
2832          * @return array|IXR_Error
2833          */
2834         public function wp_editPage( $args ) {
2835                 // Items will be escaped in mw_editPost.
2836                 $page_id  = (int) $args[1];
2837                 $username = $args[2];
2838                 $password = $args[3];
2839                 $content  = $args[4];
2840                 $publish  = $args[5];
2841
2842                 $escaped_username = $this->escape( $username );
2843                 $escaped_password = $this->escape( $password );
2844
2845                 if ( !$user = $this->login( $escaped_username, $escaped_password ) ) {
2846                         return $this->error;
2847                 }
2848
2849                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2850                 do_action( 'xmlrpc_call', 'wp.editPage' );
2851
2852                 // Get the page data and make sure it is a page.
2853                 $actual_page = get_post($page_id, ARRAY_A);
2854                 if ( !$actual_page || ($actual_page['post_type'] != 'page') )
2855                         return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
2856
2857                 // Make sure the user is allowed to edit pages.
2858                 if ( !current_user_can('edit_page', $page_id) )
2859                         return new IXR_Error( 401, __( 'Sorry, you do not have the right to edit this page.' ) );
2860
2861                 // Mark this as content for a page.
2862                 $content['post_type'] = 'page';
2863
2864                 // Arrange args in the way mw_editPost understands.
2865                 $args = array(
2866                         $page_id,
2867                         $username,
2868                         $password,
2869                         $content,
2870                         $publish
2871                 );
2872
2873                 // Let mw_editPost do all of the heavy lifting.
2874                 return $this->mw_editPost( $args );
2875         }
2876
2877         /**
2878          * Retrieve page list.
2879          *
2880          * @since 2.2.0
2881          *
2882          * @global wpdb $wpdb WordPress database abstraction object.
2883          *
2884          * @param array  $args {
2885          *     Method arguments. Note: arguments must be ordered as documented.
2886          *
2887          *     @type int    $blog_id (unused)
2888          *     @type string $username
2889          *     @type string $password
2890          * }
2891          * @return array|IXR_Error
2892          */
2893         public function wp_getPageList( $args ) {
2894                 global $wpdb;
2895
2896                 $this->escape( $args );
2897
2898                 $username = $args[1];
2899                 $password = $args[2];
2900
2901                 if ( !$user = $this->login($username, $password) )
2902                         return $this->error;
2903
2904                 if ( !current_user_can( 'edit_pages' ) )
2905                         return new IXR_Error( 401, __( 'Sorry, you cannot edit pages.' ) );
2906
2907                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2908                 do_action( 'xmlrpc_call', 'wp.getPageList' );
2909
2910                 // Get list of pages ids and titles
2911                 $page_list = $wpdb->get_results("
2912                         SELECT ID page_id,
2913                                 post_title page_title,
2914                                 post_parent page_parent_id,
2915                                 post_date_gmt,
2916                                 post_date,
2917                                 post_status
2918                         FROM {$wpdb->posts}
2919                         WHERE post_type = 'page'
2920                         ORDER BY ID
2921                 ");
2922
2923                 // The date needs to be formatted properly.
2924                 $num_pages = count($page_list);
2925                 for ( $i = 0; $i < $num_pages; $i++ ) {
2926                         $page_list[$i]->dateCreated = $this->_convert_date(  $page_list[$i]->post_date );
2927                         $page_list[$i]->date_created_gmt = $this->_convert_date_gmt( $page_list[$i]->post_date_gmt, $page_list[$i]->post_date );
2928
2929                         unset($page_list[$i]->post_date_gmt);
2930                         unset($page_list[$i]->post_date);
2931                         unset($page_list[$i]->post_status);
2932                 }
2933
2934                 return $page_list;
2935         }
2936
2937         /**
2938          * Retrieve authors list.
2939          *
2940          * @since 2.2.0
2941          *
2942          * @param array  $args {
2943          *     Method arguments. Note: arguments must be ordered as documented.
2944          *
2945          *     @type int    $blog_id (unused)
2946          *     @type string $username
2947          *     @type string $password
2948          * }
2949          * @return array|IXR_Error
2950          */
2951         public function wp_getAuthors( $args ) {
2952                 $this->escape( $args );
2953
2954                 $username = $args[1];
2955                 $password = $args[2];
2956
2957                 if ( !$user = $this->login($username, $password) )
2958                         return $this->error;
2959
2960                 if ( !current_user_can('edit_posts') )
2961                         return new IXR_Error( 401, __( 'Sorry, you cannot edit posts on this site.' ) );
2962
2963                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2964                 do_action( 'xmlrpc_call', 'wp.getAuthors' );
2965
2966                 $authors = array();
2967                 foreach ( get_users( array( 'fields' => array('ID','user_login','display_name') ) ) as $user ) {
2968                         $authors[] = array(
2969                                 'user_id'       => $user->ID,
2970                                 'user_login'    => $user->user_login,
2971                                 'display_name'  => $user->display_name
2972                         );
2973                 }
2974
2975                 return $authors;
2976         }
2977
2978         /**
2979          * Get list of all tags
2980          *
2981          * @since 2.7.0
2982          *
2983          * @param array  $args {
2984          *     Method arguments. Note: arguments must be ordered as documented.
2985          *
2986          *     @type int    $blog_id (unused)
2987          *     @type string $username
2988          *     @type string $password
2989          * }
2990          * @return array|IXR_Error
2991          */
2992         public function wp_getTags( $args ) {
2993                 $this->escape( $args );
2994
2995                 $username = $args[1];
2996                 $password = $args[2];
2997
2998                 if ( !$user = $this->login($username, $password) )
2999                         return $this->error;
3000
3001                 if ( !current_user_can( 'edit_posts' ) )
3002                         return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view tags.' ) );
3003
3004                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3005                 do_action( 'xmlrpc_call', 'wp.getKeywords' );
3006
3007                 $tags = array();
3008
3009                 if ( $all_tags = get_tags() ) {
3010                         foreach ( (array) $all_tags as $tag ) {
3011                                 $struct = array();
3012                                 $struct['tag_id']                       = $tag->term_id;
3013                                 $struct['name']                         = $tag->name;
3014                                 $struct['count']                        = $tag->count;
3015                                 $struct['slug']                         = $tag->slug;
3016                                 $struct['html_url']                     = esc_html( get_tag_link( $tag->term_id ) );
3017                                 $struct['rss_url']                      = esc_html( get_tag_feed_link( $tag->term_id ) );
3018
3019                                 $tags[] = $struct;
3020                         }
3021                 }
3022
3023                 return $tags;
3024         }
3025
3026         /**
3027          * Create new category.
3028          *
3029          * @since 2.2.0
3030          *
3031          * @param array  $args {
3032          *     Method arguments. Note: arguments must be ordered as documented.
3033          *
3034          *     @type int    $blog_id (unused)
3035          *     @type string $username
3036          *     @type string $password
3037          *     @type array  $category
3038          * }
3039          * @return int|IXR_Error Category ID.
3040          */
3041         public function wp_newCategory( $args ) {
3042                 $this->escape( $args );
3043
3044                 $username = $args[1];
3045                 $password = $args[2];
3046                 $category = $args[3];
3047
3048                 if ( !$user = $this->login($username, $password) )
3049                         return $this->error;
3050
3051                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3052                 do_action( 'xmlrpc_call', 'wp.newCategory' );
3053
3054                 // Make sure the user is allowed to add a category.
3055                 if ( !current_user_can('manage_categories') )
3056                         return new IXR_Error(401, __('Sorry, you do not have the right to add a category.'));
3057
3058                 // If no slug was provided make it empty so that
3059                 // WordPress will generate one.
3060                 if ( empty($category['slug']) )
3061                         $category['slug'] = '';
3062
3063                 // If no parent_id was provided make it empty
3064                 // so that it will be a top level page (no parent).
3065                 if ( !isset($category['parent_id']) )
3066                         $category['parent_id'] = '';
3067
3068                 // If no description was provided make it empty.
3069                 if ( empty($category["description"]) )
3070                         $category["description"] = "";
3071
3072                 $new_category = array(
3073                         'cat_name'                              => $category['name'],
3074                         'category_nicename'             => $category['slug'],
3075                         'category_parent'               => $category['parent_id'],
3076                         'category_description'  => $category['description']
3077                 );
3078
3079                 $cat_id = wp_insert_category($new_category, true);
3080                 if ( is_wp_error( $cat_id ) ) {
3081                         if ( 'term_exists' == $cat_id->get_error_code() )
3082                                 return (int) $cat_id->get_error_data();
3083                         else
3084                                 return new IXR_Error(500, __('Sorry, the new category failed.'));
3085                 } elseif ( ! $cat_id ) {
3086                         return new IXR_Error(500, __('Sorry, the new category failed.'));
3087                 }
3088
3089                 /**
3090                  * Fires after a new category has been successfully created via XML-RPC.
3091                  *
3092                  * @since 3.4.0
3093                  *
3094                  * @param int   $cat_id ID of the new category.
3095                  * @param array $args   An array of new category arguments.
3096                  */
3097                 do_action( 'xmlrpc_call_success_wp_newCategory', $cat_id, $args );
3098
3099                 return $cat_id;
3100         }
3101
3102         /**
3103          * Remove category.
3104          *
3105          * @since 2.5.0
3106          *
3107          * @param array  $args {
3108          *     Method arguments. Note: arguments must be ordered as documented.
3109          *
3110          *     @type int    $blog_id (unused)
3111          *     @type string $username
3112          *     @type string $password
3113          *     @type int    $category_id
3114          * }
3115          * @return bool|IXR_Error See {@link wp_delete_term()} for return info.
3116          */
3117         public function wp_deleteCategory( $args ) {
3118                 $this->escape( $args );
3119
3120                 $username    = $args[1];
3121                 $password    = $args[2];
3122                 $category_id = (int) $args[3];
3123
3124                 if ( !$user = $this->login($username, $password) )
3125                         return $this->error;
3126
3127                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3128                 do_action( 'xmlrpc_call', 'wp.deleteCategory' );
3129
3130                 if ( !current_user_can('manage_categories') )
3131                         return new IXR_Error( 401, __( 'Sorry, you do not have the right to delete a category.' ) );
3132
3133                 $status = wp_delete_term( $category_id, 'category' );
3134
3135                 if ( true == $status ) {
3136                         /**
3137                          * Fires after a category has been successfully deleted via XML-RPC.
3138                          *
3139                          * @since 3.4.0
3140                          *
3141                          * @param int   $category_id ID of the deleted category.
3142                          * @param array $args        An array of arguments to delete the category.
3143                          */
3144                         do_action( 'xmlrpc_call_success_wp_deleteCategory', $category_id, $args );
3145                 }
3146
3147                 return $status;
3148         }
3149
3150         /**
3151          * Retrieve category list.
3152          *
3153          * @since 2.2.0
3154          *
3155          * @param array  $args {
3156          *     Method arguments. Note: arguments must be ordered as documented.
3157          *
3158          *     @type int    $blog_id (unused)
3159          *     @type string $username
3160          *     @type string $password
3161          *     @type array  $category
3162          *     @type int    $max_results
3163          * }
3164          * @return array|IXR_Error
3165          */
3166         public function wp_suggestCategories( $args ) {
3167                 $this->escape( $args );
3168
3169                 $username    = $args[1];
3170                 $password    = $args[2];
3171                 $category    = $args[3];
3172                 $max_results = (int) $args[4];
3173
3174                 if ( !$user = $this->login($username, $password) )
3175                         return $this->error;
3176
3177                 if ( !current_user_can( 'edit_posts' ) )
3178                         return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
3179
3180                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3181                 do_action( 'xmlrpc_call', 'wp.suggestCategories' );
3182
3183                 $category_suggestions = array();
3184                 $args = array('get' => 'all', 'number' => $max_results, 'name__like' => $category);
3185                 foreach ( (array) get_categories($args) as $cat ) {
3186                         $category_suggestions[] = array(
3187                                 'category_id'   => $cat->term_id,
3188                                 'category_name' => $cat->name
3189                         );
3190                 }
3191
3192                 return $category_suggestions;
3193         }
3194
3195         /**
3196          * Retrieve comment.
3197          *
3198          * @since 2.7.0
3199          *
3200          * @param array  $args {
3201          *     Method arguments. Note: arguments must be ordered as documented.
3202          *
3203          *     @type int    $blog_id (unused)
3204          *     @type string $username
3205          *     @type string $password
3206          *     @type int    $comment_id
3207          * }
3208          * @return array|IXR_Error
3209          */
3210         public function wp_getComment($args) {
3211                 $this->escape($args);
3212
3213                 $username       = $args[1];
3214                 $password       = $args[2];
3215                 $comment_id     = (int) $args[3];
3216
3217                 if ( ! $user = $this->login( $username, $password ) ) {
3218                         return $this->error;
3219                 }
3220
3221                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3222                 do_action( 'xmlrpc_call', 'wp.getComment' );
3223
3224                 if ( ! $comment = get_comment( $comment_id ) ) {
3225                         return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
3226                 }
3227
3228                 if ( ! current_user_can( 'edit_comment', $comment_id ) ) {
3229                         return new IXR_Error( 403, __( 'You are not allowed to moderate or edit this comment.' ) );
3230                 }
3231
3232                 return $this->_prepare_comment( $comment );
3233         }
3234
3235         /**
3236          * Retrieve comments.
3237          *
3238          * Besides the common blog_id (unused), username, and password arguments, it takes a filter
3239          * array as last argument.
3240          *
3241          * Accepted 'filter' keys are 'status', 'post_id', 'offset', and 'number'.
3242          *
3243          * The defaults are as follows:
3244          * - 'status' - Default is ''. Filter by status (e.g., 'approve', 'hold')
3245          * - 'post_id' - Default is ''. The post where the comment is posted. Empty string shows all comments.
3246          * - 'number' - Default is 10. Total number of media items to retrieve.
3247          * - 'offset' - Default is 0. See {@link WP_Query::query()} for more.
3248          *
3249          * @since 2.7.0
3250          *
3251          * @param array  $args {
3252          *     Method arguments. Note: arguments must be ordered as documented.
3253          *
3254          *     @type int    $blog_id (unused)
3255          *     @type string $username
3256          *     @type string $password
3257          *     @type array  $struct
3258          * }
3259          * @return array|IXR_Error Contains a collection of comments. See {@link wp_xmlrpc_server::wp_getComment()} for a description of each item contents
3260          */
3261         public function wp_getComments( $args ) {
3262                 $this->escape( $args );
3263
3264                 $username = $args[1];
3265                 $password = $args[2];
3266                 $struct   = isset( $args[3] ) ? $args[3] : array();
3267
3268                 if ( ! $user = $this->login( $username, $password ) ) {
3269                         return $this->error;
3270                 }
3271
3272                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3273                 do_action( 'xmlrpc_call', 'wp.getComments' );
3274
3275                 if ( isset( $struct['status'] ) ) {
3276                         $status = $struct['status'];
3277                 } else {
3278                         $status = '';
3279                 }
3280
3281                 if ( ! current_user_can( 'moderate_comments' ) && 'approve' !== $status ) {
3282                         return new IXR_Error( 401, __( 'Invalid comment status.' ) );
3283                 }
3284
3285                 $post_id = '';
3286                 if ( isset( $struct['post_id'] ) ) {
3287                         $post_id = absint( $struct['post_id'] );
3288                 }
3289
3290                 $post_type = '';
3291                 if ( isset( $struct['post_type'] ) ) {
3292                         $post_type_object = get_post_type_object( $struct['post_type'] );
3293                         if ( ! $post_type_object || ! post_type_supports( $post_type_object->name, 'comments' ) ) {
3294                                 return new IXR_Error( 404, __( 'Invalid post type.' ) );
3295                         }
3296                         $post_type = $struct['post_type'];
3297                 }
3298
3299                 $offset = 0;
3300                 if ( isset( $struct['offset'] ) ) {
3301                         $offset = absint( $struct['offset'] );
3302                 }
3303
3304                 $number = 10;
3305                 if ( isset( $struct['number'] ) ) {
3306                         $number = absint( $struct['number'] );
3307                 }
3308
3309                 $comments = get_comments( array(
3310                         'status' => $status,
3311                         'post_id' => $post_id,
3312                         'offset' => $offset,
3313                         'number' => $number,
3314                         'post_type' => $post_type,
3315                 ) );
3316
3317                 $comments_struct = array();
3318                 if ( is_array( $comments ) ) {
3319                         foreach ( $comments as $comment ) {
3320                                 $comments_struct[] = $this->_prepare_comment( $comment );
3321                         }
3322                 }
3323
3324                 return $comments_struct;
3325         }
3326
3327         /**
3328          * Delete a comment.
3329          *
3330          * By default, the comment will be moved to the trash instead of deleted.
3331          * See {@link wp_delete_comment()} for more information on
3332          * this behavior.
3333          *
3334          * @since 2.7.0
3335          *
3336          * @param array  $args {
3337          *     Method arguments. Note: arguments must be ordered as documented.
3338          *
3339          *     @type int    $blog_id (unused)
3340          *     @type string $username
3341          *     @type string $password
3342          *     @type int    $comment_ID
3343          * }
3344          * @return bool|IXR_Error {@link wp_delete_comment()}
3345          */
3346         public function wp_deleteComment( $args ) {
3347                 $this->escape($args);
3348
3349                 $username       = $args[1];
3350                 $password       = $args[2];
3351                 $comment_ID     = (int) $args[3];
3352
3353                 if ( ! $user = $this->login( $username, $password ) ) {
3354                         return $this->error;
3355                 }
3356
3357                 if ( ! get_comment( $comment_ID ) ) {
3358                         return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
3359                 }
3360
3361                 if ( !current_user_can( 'edit_comment', $comment_ID ) ) {
3362                         return new IXR_Error( 403, __( 'You are not allowed to moderate or edit this comment.' ) );
3363                 }
3364
3365                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3366                 do_action( 'xmlrpc_call', 'wp.deleteComment' );
3367
3368                 $status = wp_delete_comment( $comment_ID );
3369
3370                 if ( $status ) {
3371                         /**
3372                          * Fires after a comment has been successfully deleted via XML-RPC.
3373                          *
3374                          * @since 3.4.0
3375                          *
3376                          * @param int   $comment_ID ID of the deleted comment.
3377                          * @param array $args       An array of arguments to delete the comment.
3378                          */
3379                         do_action( 'xmlrpc_call_success_wp_deleteComment', $comment_ID, $args );
3380                 }
3381
3382                 return $status;
3383         }
3384
3385         /**
3386          * Edit comment.
3387          *
3388          * Besides the common blog_id (unused), username, and password arguments, it takes a
3389          * comment_id integer and a content_struct array as last argument.
3390          *
3391          * The allowed keys in the content_struct array are:
3392          *  - 'author'
3393          *  - 'author_url'
3394          *  - 'author_email'
3395          *  - 'content'
3396          *  - 'date_created_gmt'
3397          *  - 'status'. Common statuses are 'approve', 'hold', 'spam'. See get_comment_statuses() for more details
3398          *
3399          * @since 2.7.0
3400          *
3401          * @param array  $args {
3402          *     Method arguments. Note: arguments must be ordered as documented.
3403          *
3404          *     @type int    $blog_id (unused)
3405          *     @type string $username
3406          *     @type string $password
3407          *     @type int    $comment_ID
3408          *     @type array  $content_struct
3409          * }
3410          * @return true|IXR_Error True, on success.
3411          */
3412         public function wp_editComment( $args ) {
3413                 $this->escape( $args );
3414
3415                 $username       = $args[1];
3416                 $password       = $args[2];
3417                 $comment_ID     = (int) $args[3];
3418                 $content_struct = $args[4];
3419
3420                 if ( !$user = $this->login( $username, $password ) ) {
3421                         return $this->error;
3422                 }
3423
3424                 if ( ! get_comment( $comment_ID ) ) {
3425                         return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
3426                 }
3427
3428                 if ( ! current_user_can( 'edit_comment', $comment_ID ) ) {
3429                         return new IXR_Error( 403, __( 'You are not allowed to moderate or edit this comment.' ) );
3430                 }
3431
3432                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3433                 do_action( 'xmlrpc_call', 'wp.editComment' );
3434
3435                 if ( isset($content_struct['status']) ) {
3436                         $statuses = get_comment_statuses();
3437                         $statuses = array_keys($statuses);
3438
3439                         if ( ! in_array($content_struct['status'], $statuses) )
3440                                 return new IXR_Error( 401, __( 'Invalid comment status.' ) );
3441                         $comment_approved = $content_struct['status'];
3442                 }
3443
3444                 // Do some timestamp voodoo
3445                 if ( !empty( $content_struct['date_created_gmt'] ) ) {
3446                         // We know this is supposed to be GMT, so we're going to slap that Z on there by force
3447                         $dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
3448                         $comment_date = get_date_from_gmt(iso8601_to_datetime($dateCreated));
3449                         $comment_date_gmt = iso8601_to_datetime($dateCreated, 'GMT');
3450                 }
3451
3452                 if ( isset($content_struct['content']) )
3453                         $comment_content = $content_struct['content'];
3454
3455                 if ( isset($content_struct['author']) )
3456                         $comment_author = $content_struct['author'];
3457
3458                 if ( isset($content_struct['author_url']) )
3459                         $comment_author_url = $content_struct['author_url'];
3460
3461                 if ( isset($content_struct['author_email']) )
3462                         $comment_author_email = $content_struct['author_email'];
3463
3464                 // We've got all the data -- post it:
3465                 $comment = compact('comment_ID', 'comment_content', 'comment_approved', 'comment_date', 'comment_date_gmt', 'comment_author', 'comment_author_email', 'comment_author_url');
3466
3467                 $result = wp_update_comment($comment);
3468                 if ( is_wp_error( $result ) )
3469                         return new IXR_Error(500, $result->get_error_message());
3470
3471                 if ( !$result )
3472                         return new IXR_Error(500, __('Sorry, the comment could not be edited. Something wrong happened.'));
3473
3474                 /**
3475                  * Fires after a comment has been successfully updated via XML-RPC.
3476                  *
3477                  * @since 3.4.0
3478                  *
3479                  * @param int   $comment_ID ID of the updated comment.
3480                  * @param array $args       An array of arguments to update the comment.
3481                  */
3482                 do_action( 'xmlrpc_call_success_wp_editComment', $comment_ID, $args );
3483
3484                 return true;
3485         }
3486
3487         /**
3488          * Create new comment.
3489          *
3490          * @since 2.7.0
3491          *
3492          * @param array  $args {
3493          *     Method arguments. Note: arguments must be ordered as documented.
3494          *
3495          *     @type int        $blog_id (unused)
3496          *     @type string     $username
3497          *     @type string     $password
3498          *     @type string|int $post
3499          *     @type array      $content_struct
3500          * }
3501          * @return int|IXR_Error {@link wp_new_comment()}
3502          */
3503         public function wp_newComment($args) {
3504                 $this->escape($args);
3505
3506                 $username       = $args[1];
3507                 $password       = $args[2];
3508                 $post           = $args[3];
3509                 $content_struct = $args[4];
3510
3511                 /**
3512                  * Filter whether to allow anonymous comments over XML-RPC.
3513                  *
3514                  * @since 2.7.0
3515                  *
3516                  * @param bool $allow Whether to allow anonymous commenting via XML-RPC.
3517                  *                    Default false.
3518                  */
3519                 $allow_anon = apply_filters( 'xmlrpc_allow_anonymous_comments', false );
3520
3521                 $user = $this->login($username, $password);
3522
3523                 if ( !$user ) {
3524                         $logged_in = false;
3525                         if ( $allow_anon && get_option('comment_registration') ) {
3526                                 return new IXR_Error( 403, __( 'You must be registered to comment' ) );
3527                         } elseif ( ! $allow_anon ) {
3528                                 return $this->error;
3529                         }
3530                 } else {
3531                         $logged_in = true;
3532                 }
3533
3534                 if ( is_numeric($post) )
3535                         $post_id = absint($post);
3536                 else
3537                         $post_id = url_to_postid($post);
3538
3539                 if ( ! $post_id ) {
3540                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
3541                 }
3542
3543                 if ( ! get_post( $post_id ) ) {
3544                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
3545                 }
3546
3547                 if ( ! comments_open( $post_id ) ) {
3548                         return new IXR_Error( 403, __( 'Sorry, comments are closed for this item.' ) );
3549                 }
3550
3551                 $comment = array();
3552                 $comment['comment_post_ID'] = $post_id;
3553
3554                 if ( $logged_in ) {
3555                         $display_name = $user->display_name;
3556                         $user_email = $user->user_email;
3557                         $user_url = $user->user_url;
3558
3559                         $comment['comment_author'] = $this->escape( $display_name );
3560                         $comment['comment_author_email'] = $this->escape( $user_email );
3561                         $comment['comment_author_url'] = $this->escape( $user_url );
3562                         $comment['user_ID'] = $user->ID;
3563                 } else {
3564                         $comment['comment_author'] = '';
3565                         if ( isset($content_struct['author']) )
3566                                 $comment['comment_author'] = $content_struct['author'];
3567
3568                         $comment['comment_author_email'] = '';
3569                         if ( isset($content_struct['author_email']) )
3570                                 $comment['comment_author_email'] = $content_struct['author_email'];
3571
3572                         $comment['comment_author_url'] = '';
3573                         if ( isset($content_struct['author_url']) )
3574                                 $comment['comment_author_url'] = $content_struct['author_url'];
3575
3576                         $comment['user_ID'] = 0;
3577
3578                         if ( get_option('require_name_email') ) {
3579                                 if ( 6 > strlen($comment['comment_author_email']) || '' == $comment['comment_author'] )
3580                                         return new IXR_Error( 403, __( 'Comment author name and email are required' ) );
3581                                 elseif ( !is_email($comment['comment_author_email']) )
3582                                         return new IXR_Error( 403, __( 'A valid email address is required' ) );
3583                         }
3584                 }
3585
3586                 $comment['comment_parent'] = isset($content_struct['comment_parent']) ? absint($content_struct['comment_parent']) : 0;
3587
3588                 $comment['comment_content'] =  isset($content_struct['content']) ? $content_struct['content'] : null;
3589
3590                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3591                 do_action( 'xmlrpc_call', 'wp.newComment' );
3592
3593                 $comment_ID = wp_new_comment( $comment );
3594
3595                 /**
3596                  * Fires after a new comment has been successfully created via XML-RPC.
3597                  *
3598                  * @since 3.4.0
3599                  *
3600                  * @param int   $comment_ID ID of the new comment.
3601                  * @param array $args       An array of new comment arguments.
3602                  */
3603                 do_action( 'xmlrpc_call_success_wp_newComment', $comment_ID, $args );
3604
3605                 return $comment_ID;
3606         }
3607
3608         /**
3609          * Retrieve all of the comment status.
3610          *
3611          * @since 2.7.0
3612          *
3613          * @param array  $args {
3614          *     Method arguments. Note: arguments must be ordered as documented.
3615          *
3616          *     @type int    $blog_id (unused)
3617          *     @type string $username
3618          *     @type string $password
3619          * }
3620          * @return array|IXR_Error
3621          */
3622         public function wp_getCommentStatusList( $args ) {
3623                 $this->escape( $args );
3624
3625                 $username = $args[1];
3626                 $password = $args[2];
3627
3628                 if ( ! $user = $this->login( $username, $password ) ) {
3629                         return $this->error;
3630                 }
3631
3632                 if ( ! current_user_can( 'publish_posts' ) ) {
3633                         return new IXR_Error( 403, __( 'You are not allowed access to details about this site.' ) );
3634                 }
3635
3636                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3637                 do_action( 'xmlrpc_call', 'wp.getCommentStatusList' );
3638
3639                 return get_comment_statuses();
3640         }
3641
3642         /**
3643          * Retrieve comment count.
3644          *
3645          * @since 2.5.0
3646          *
3647          * @param array  $args {
3648          *     Method arguments. Note: arguments must be ordered as documented.
3649          *
3650          *     @type int    $blog_id (unused)
3651          *     @type string $username
3652          *     @type string $password
3653          *     @type int    $post_id
3654          * }
3655          * @return array|IXR_Error
3656          */
3657         public function wp_getCommentCount( $args ) {
3658                 $this->escape( $args );
3659
3660                 $username       = $args[1];
3661                 $password       = $args[2];
3662                 $post_id        = (int) $args[3];
3663
3664                 if ( ! $user = $this->login( $username, $password ) ) {
3665                         return $this->error;
3666                 }
3667
3668                 $post = get_post( $post_id, ARRAY_A );
3669                 if ( empty( $post['ID'] ) ) {
3670                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
3671                 }
3672
3673                 if ( ! current_user_can( 'edit_post', $post_id ) ) {
3674                         return new IXR_Error( 403, __( 'You are not allowed access to details of this post.' ) );
3675                 }
3676
3677                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3678                 do_action( 'xmlrpc_call', 'wp.getCommentCount' );
3679
3680                 $count = wp_count_comments( $post_id );
3681
3682                 return array(
3683                         'approved' => $count->approved,
3684                         'awaiting_moderation' => $count->moderated,
3685                         'spam' => $count->spam,
3686                         'total_comments' => $count->total_comments
3687                 );
3688         }
3689
3690         /**
3691          * Retrieve post statuses.
3692          *
3693          * @since 2.5.0
3694          *
3695          * @param array  $args {
3696          *     Method arguments. Note: arguments must be ordered as documented.
3697          *
3698          *     @type int    $blog_id (unused)
3699          *     @type string $username
3700          *     @type string $password
3701          * }
3702          * @return array|IXR_Error
3703          */
3704         public function wp_getPostStatusList( $args ) {
3705                 $this->escape( $args );
3706
3707                 $username = $args[1];
3708                 $password = $args[2];
3709
3710                 if ( !$user = $this->login($username, $password) )
3711                         return $this->error;
3712
3713                 if ( !current_user_can( 'edit_posts' ) )
3714                         return new IXR_Error( 403, __( 'You are not allowed access to details about this site.' ) );
3715
3716                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3717                 do_action( 'xmlrpc_call', 'wp.getPostStatusList' );
3718
3719                 return get_post_statuses();
3720         }
3721
3722         /**
3723          * Retrieve page statuses.
3724          *
3725          * @since 2.5.0
3726          *
3727          * @param array  $args {
3728          *     Method arguments. Note: arguments must be ordered as documented.
3729          *
3730          *     @type int    $blog_id (unused)
3731          *     @type string $username
3732          *     @type string $password
3733          * }
3734          * @return array|IXR_Error
3735          */
3736         public function wp_getPageStatusList( $args ) {
3737                 $this->escape( $args );
3738
3739                 $username = $args[1];
3740                 $password = $args[2];
3741
3742                 if ( !$user = $this->login($username, $password) )
3743                         return $this->error;
3744
3745                 if ( !current_user_can( 'edit_pages' ) )
3746                         return new IXR_Error( 403, __( 'You are not allowed access to details about this site.' ) );
3747
3748                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3749                 do_action( 'xmlrpc_call', 'wp.getPageStatusList' );
3750
3751                 return get_page_statuses();
3752         }
3753
3754         /**
3755          * Retrieve page templates.
3756          *
3757          * @since 2.6.0
3758          *
3759          * @param array  $args {
3760          *     Method arguments. Note: arguments must be ordered as documented.
3761          *
3762          *     @type int    $blog_id (unused)
3763          *     @type string $username
3764          *     @type string $password
3765          * }
3766          * @return array|IXR_Error
3767          */
3768         public function wp_getPageTemplates( $args ) {
3769                 $this->escape( $args );
3770
3771                 $username = $args[1];
3772                 $password = $args[2];
3773
3774                 if ( !$user = $this->login($username, $password) )
3775                         return $this->error;
3776
3777                 if ( !current_user_can( 'edit_pages' ) )
3778                         return new IXR_Error( 403, __( 'You are not allowed access to details about this site.' ) );
3779
3780                 $templates = get_page_templates();
3781                 $templates['Default'] = 'default';
3782
3783                 return $templates;
3784         }
3785
3786         /**
3787          * Retrieve blog options.
3788          *
3789          * @since 2.6.0
3790          *
3791          * @param array  $args {
3792          *     Method arguments. Note: arguments must be ordered as documented.
3793          *
3794          *     @type int    $blog_id (unused)
3795          *     @type string $username
3796          *     @type string $password
3797          *     @type array  $options
3798          * }
3799          * @return array|IXR_Error
3800          */
3801         public function wp_getOptions( $args ) {
3802                 $this->escape( $args );
3803
3804                 $username       = $args[1];
3805                 $password       = $args[2];
3806                 $options        = isset( $args[3] ) ? (array) $args[3] : array();
3807
3808                 if ( !$user = $this->login($username, $password) )
3809                         return $this->error;
3810
3811                 // If no specific options where asked for, return all of them
3812                 if ( count( $options ) == 0 )
3813                         $options = array_keys($this->blog_options);
3814
3815                 return $this->_getOptions($options);
3816         }
3817
3818         /**
3819          * Retrieve blog options value from list.
3820          *
3821          * @since 2.6.0
3822          *
3823          * @param array $options Options to retrieve.
3824          * @return array
3825          */
3826         public function _getOptions($options) {
3827                 $data = array();
3828                 $can_manage = current_user_can( 'manage_options' );
3829                 foreach ( $options as $option ) {
3830                         if ( array_key_exists( $option, $this->blog_options ) ) {
3831                                 $data[$option] = $this->blog_options[$option];
3832                                 //Is the value static or dynamic?
3833                                 if ( isset( $data[$option]['option'] ) ) {
3834                                         $data[$option]['value'] = get_option( $data[$option]['option'] );
3835                                         unset($data[$option]['option']);
3836                                 }
3837
3838                                 if ( ! $can_manage )
3839                                         $data[$option]['readonly'] = true;
3840                         }
3841                 }
3842
3843                 return $data;
3844         }
3845
3846         /**
3847          * Update blog options.
3848          *
3849          * @since 2.6.0
3850          *
3851          * @param array  $args {
3852          *     Method arguments. Note: arguments must be ordered as documented.
3853          *
3854          *     @type int    $blog_id (unused)
3855          *     @type string $username
3856          *     @type string $password
3857          *     @type array  $options
3858          * }
3859          * @return array|IXR_Error
3860          */
3861         public function wp_setOptions( $args ) {
3862                 $this->escape( $args );
3863
3864                 $username       = $args[1];
3865                 $password       = $args[2];
3866                 $options        = (array) $args[3];
3867
3868                 if ( !$user = $this->login($username, $password) )
3869                         return $this->error;
3870
3871                 if ( !current_user_can( 'manage_options' ) )
3872                         return new IXR_Error( 403, __( 'You are not allowed to update options.' ) );
3873
3874                 $option_names = array();
3875                 foreach ( $options as $o_name => $o_value ) {
3876                         $option_names[] = $o_name;
3877                         if ( !array_key_exists( $o_name, $this->blog_options ) )
3878                                 continue;
3879
3880                         if ( $this->blog_options[$o_name]['readonly'] == true )
3881                                 continue;
3882
3883                         update_option( $this->blog_options[$o_name]['option'], wp_unslash( $o_value ) );
3884                 }
3885
3886                 //Now return the updated values
3887                 return $this->_getOptions($option_names);
3888         }
3889
3890         /**
3891          * Retrieve a media item by ID
3892          *
3893          * @since 3.1.0
3894          *
3895          * @param array  $args {
3896          *     Method arguments. Note: arguments must be ordered as documented.
3897          *
3898          *     @type int    $blog_id (unused)
3899          *     @type string $username
3900          *     @type string $password
3901          *     @type int    $attachment_id
3902          * }
3903          * @return array|IXR_Error Associative array contains:
3904          *  - 'date_created_gmt'
3905          *  - 'parent'
3906          *  - 'link'
3907          *  - 'thumbnail'
3908          *  - 'title'
3909          *  - 'caption'
3910          *  - 'description'
3911          *  - 'metadata'
3912          */
3913         public function wp_getMediaItem( $args ) {
3914                 $this->escape( $args );
3915
3916                 $username               = $args[1];
3917                 $password               = $args[2];
3918                 $attachment_id  = (int) $args[3];
3919
3920                 if ( !$user = $this->login($username, $password) )
3921                         return $this->error;
3922
3923                 if ( !current_user_can( 'upload_files' ) )
3924                         return new IXR_Error( 403, __( 'You do not have permission to upload files.' ) );
3925
3926                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3927                 do_action( 'xmlrpc_call', 'wp.getMediaItem' );
3928
3929                 if ( ! $attachment = get_post($attachment_id) )
3930                         return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
3931
3932                 return $this->_prepare_media_item( $attachment );
3933         }
3934
3935         /**
3936          * Retrieves a collection of media library items (or attachments)
3937          *
3938          * Besides the common blog_id (unused), username, and password arguments, it takes a filter
3939          * array as last argument.
3940          *
3941          * Accepted 'filter' keys are 'parent_id', 'mime_type', 'offset', and 'number'.
3942          *
3943          * The defaults are as follows:
3944          * - 'number' - Default is 5. Total number of media items to retrieve.
3945          * - 'offset' - Default is 0. See WP_Query::query() for more.
3946          * - 'parent_id' - Default is ''. The post where the media item is attached. Empty string shows all media items. 0 shows unattached media items.
3947          * - 'mime_type' - Default is ''. Filter by mime type (e.g., 'image/jpeg', 'application/pdf')
3948          *
3949          * @since 3.1.0
3950          *
3951          * @param array  $args {
3952          *     Method arguments. Note: arguments must be ordered as documented.
3953          *
3954          *     @type int    $blog_id (unused)
3955          *     @type string $username
3956          *     @type string $password
3957          *     @type array  $struct
3958          * }
3959          * @return array|IXR_Error Contains a collection of media items. See wp_xmlrpc_server::wp_getMediaItem() for a description of each item contents
3960          */
3961         public function wp_getMediaLibrary($args) {
3962                 $this->escape($args);
3963
3964                 $username       = $args[1];
3965                 $password       = $args[2];
3966                 $struct         = isset( $args[3] ) ? $args[3] : array() ;
3967
3968                 if ( !$user = $this->login($username, $password) )
3969                         return $this->error;
3970
3971                 if ( !current_user_can( 'upload_files' ) )
3972                         return new IXR_Error( 401, __( 'You do not have permission to upload files.' ) );
3973
3974                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3975                 do_action( 'xmlrpc_call', 'wp.getMediaLibrary' );
3976
3977                 $parent_id = ( isset($struct['parent_id']) ) ? absint($struct['parent_id']) : '' ;
3978                 $mime_type = ( isset($struct['mime_type']) ) ? $struct['mime_type'] : '' ;
3979                 $offset = ( isset($struct['offset']) ) ? absint($struct['offset']) : 0 ;
3980                 $number = ( isset($struct['number']) ) ? absint($struct['number']) : -1 ;
3981
3982                 $attachments = get_posts( array('post_type' => 'attachment', 'post_parent' => $parent_id, 'offset' => $offset, 'numberposts' => $number, 'post_mime_type' => $mime_type ) );
3983
3984                 $attachments_struct = array();
3985
3986                 foreach ($attachments as $attachment )
3987                         $attachments_struct[] = $this->_prepare_media_item( $attachment );
3988
3989                 return $attachments_struct;
3990         }
3991
3992         /**
3993          * Retrieves a list of post formats used by the site.
3994          *
3995          * @since 3.1.0
3996          *
3997          * @param array  $args {
3998          *     Method arguments. Note: arguments must be ordered as documented.
3999          *
4000          *     @type int    $blog_id (unused)
4001          *     @type string $username
4002          *     @type string $password
4003          * }
4004          * @return array|IXR_Error List of post formats, otherwise IXR_Error object.
4005          */
4006         public function wp_getPostFormats( $args ) {
4007                 $this->escape( $args );
4008
4009                 $username = $args[1];
4010                 $password = $args[2];
4011
4012                 if ( !$user = $this->login( $username, $password ) )
4013                         return $this->error;
4014
4015                 if ( !current_user_can( 'edit_posts' ) )
4016                         return new IXR_Error( 403, __( 'You are not allowed access to details about this site.' ) );
4017
4018                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4019                 do_action( 'xmlrpc_call', 'wp.getPostFormats' );
4020
4021                 $formats = get_post_format_strings();
4022
4023                 // find out if they want a list of currently supports formats
4024                 if ( isset( $args[3] ) && is_array( $args[3] ) ) {
4025                         if ( $args[3]['show-supported'] ) {
4026                                 if ( current_theme_supports( 'post-formats' ) ) {
4027                                         $supported = get_theme_support( 'post-formats' );
4028
4029                                         $data = array();
4030                                         $data['all'] = $formats;
4031                                         $data['supported'] = $supported[0];
4032
4033                                         $formats = $data;
4034                                 }
4035                         }
4036                 }
4037
4038                 return $formats;
4039         }
4040
4041         /**
4042          * Retrieves a post type
4043          *
4044          * @since 3.4.0
4045          *
4046          * @see get_post_type_object()
4047          *
4048          * @param array  $args {
4049          *     Method arguments. Note: arguments must be ordered as documented.
4050          *
4051          *     @type int    $blog_id (unused)
4052          *     @type string $username
4053          *     @type string $password
4054          *     @type string $post_type_name
4055          *     @type array  $fields (optional)
4056          * }
4057          * @return array|IXR_Error Array contains:
4058          *  - 'labels'
4059          *  - 'description'
4060          *  - 'capability_type'
4061          *  - 'cap'
4062          *  - 'map_meta_cap'
4063          *  - 'hierarchical'
4064          *  - 'menu_position'
4065          *  - 'taxonomies'
4066          *  - 'supports'
4067          */
4068         public function wp_getPostType( $args ) {
4069                 if ( ! $this->minimum_args( $args, 4 ) )
4070                         return $this->error;
4071
4072                 $this->escape( $args );
4073
4074                 $username       = $args[1];
4075                 $password       = $args[2];
4076                 $post_type_name = $args[3];
4077
4078                 if ( isset( $args[4] ) ) {
4079                         $fields = $args[4];
4080                 } else {
4081                         /**
4082                          * Filter the default query fields used by the given XML-RPC method.
4083                          *
4084                          * @since 3.4.0
4085                          *
4086                          * @param array  $fields An array of post type query fields for the given method.
4087                          * @param string $method The method name.
4088                          */
4089                         $fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostType' );
4090                 }
4091
4092                 if ( !$user = $this->login( $username, $password ) )
4093                         return $this->error;
4094
4095                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4096                 do_action( 'xmlrpc_call', 'wp.getPostType' );
4097
4098                 if ( ! post_type_exists( $post_type_name ) )
4099                         return new IXR_Error( 403, __( 'Invalid post type' ) );
4100
4101                 $post_type = get_post_type_object( $post_type_name );
4102
4103                 if ( ! current_user_can( $post_type->cap->edit_posts ) )
4104                         return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post type.' ) );
4105
4106                 return $this->_prepare_post_type( $post_type, $fields );
4107         }
4108
4109         /**
4110          * Retrieves a post types
4111          *
4112          * @since 3.4.0
4113          *
4114          * @see get_post_types()
4115          *
4116          * @param array  $args {
4117          *     Method arguments. Note: arguments must be ordered as documented.
4118          *
4119          *     @type int    $blog_id (unused)
4120          *     @type string $username
4121          *     @type string $password
4122          *     @type array  $filter (optional)
4123          *     @type array  $fields (optional)
4124          * }
4125          * @return array|IXR_Error
4126          */
4127         public function wp_getPostTypes( $args ) {
4128                 if ( ! $this->minimum_args( $args, 3 ) )
4129                         return $this->error;
4130
4131                 $this->escape( $args );
4132
4133                 $username = $args[1];
4134                 $password = $args[2];
4135                 $filter   = isset( $args[3] ) ? $args[3] : array( 'public' => true );
4136
4137                 if ( isset( $args[4] ) ) {
4138                         $fields = $args[4];
4139                 } else {
4140                         /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4141                         $fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostTypes' );
4142                 }
4143
4144                 if ( ! $user = $this->login( $username, $password ) )
4145                         return $this->error;
4146
4147                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4148                 do_action( 'xmlrpc_call', 'wp.getPostTypes' );
4149
4150                 $post_types = get_post_types( $filter, 'objects' );
4151
4152                 $struct = array();
4153
4154                 foreach ( $post_types as $post_type ) {
4155                         if ( ! current_user_can( $post_type->cap->edit_posts ) )
4156                                 continue;
4157
4158                         $struct[$post_type->name] = $this->_prepare_post_type( $post_type, $fields );
4159                 }
4160
4161                 return $struct;
4162         }
4163
4164         /**
4165          * Retrieve revisions for a specific post.
4166          *
4167          * @since 3.5.0
4168          *
4169          * The optional $fields parameter specifies what fields will be included
4170          * in the response array.
4171          *
4172          * @uses wp_get_post_revisions()
4173          * @see wp_getPost() for more on $fields
4174          *
4175          * @param array  $args {
4176          *     Method arguments. Note: arguments must be ordered as documented.
4177          *
4178          *     @type int    $blog_id (unused)
4179          *     @type string $username
4180          *     @type string $password
4181          *     @type int    $post_id
4182          *     @type array  $fields (optional)
4183          * }
4184          * @return array|IXR_Error contains a collection of posts.
4185          */
4186         public function wp_getRevisions( $args ) {
4187                 if ( ! $this->minimum_args( $args, 4 ) )
4188                         return $this->error;
4189
4190                 $this->escape( $args );
4191
4192                 $username = $args[1];
4193                 $password = $args[2];
4194                 $post_id  = (int) $args[3];
4195
4196                 if ( isset( $args[4] ) ) {
4197                         $fields = $args[4];
4198                 } else {
4199                         /**
4200                          * Filter the default revision query fields used by the given XML-RPC method.
4201                          *
4202                          * @since 3.5.0
4203                          *
4204                          * @param array  $field  An array of revision query fields.
4205                          * @param string $method The method name.
4206                          */
4207                         $fields = apply_filters( 'xmlrpc_default_revision_fields', array( 'post_date', 'post_date_gmt' ), 'wp.getRevisions' );
4208                 }
4209
4210                 if ( ! $user = $this->login( $username, $password ) )
4211                         return $this->error;
4212
4213                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4214                 do_action( 'xmlrpc_call', 'wp.getRevisions' );
4215
4216                 if ( ! $post = get_post( $post_id ) )
4217                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4218
4219                 if ( ! current_user_can( 'edit_post', $post_id ) )
4220                         return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
4221
4222                 // Check if revisions are enabled.
4223                 if ( ! wp_revisions_enabled( $post ) )
4224                         return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) );
4225
4226                 $revisions = wp_get_post_revisions( $post_id );
4227
4228                 if ( ! $revisions )
4229                         return array();
4230
4231                 $struct = array();
4232
4233                 foreach ( $revisions as $revision ) {
4234                         if ( ! current_user_can( 'read_post', $revision->ID ) )
4235                                 continue;
4236
4237                         // Skip autosaves
4238                         if ( wp_is_post_autosave( $revision ) )
4239                                 continue;
4240
4241                         $struct[] = $this->_prepare_post( get_object_vars( $revision ), $fields );
4242                 }
4243
4244                 return $struct;
4245         }
4246
4247         /**
4248          * Restore a post revision
4249          *
4250          * @since 3.5.0
4251          *
4252          * @uses wp_restore_post_revision()
4253          *
4254          * @param array  $args {
4255          *     Method arguments. Note: arguments must be ordered as documented.
4256          *
4257          *     @type int    $blog_id (unused)
4258          *     @type string $username
4259          *     @type string $password
4260          *     @type int    $revision_id
4261          * }
4262          * @return bool|IXR_Error false if there was an error restoring, true if success.
4263          */
4264         public function wp_restoreRevision( $args ) {
4265                 if ( ! $this->minimum_args( $args, 3 ) )
4266                         return $this->error;
4267
4268                 $this->escape( $args );
4269
4270                 $username    = $args[1];
4271                 $password    = $args[2];
4272                 $revision_id = (int) $args[3];
4273
4274                 if ( ! $user = $this->login( $username, $password ) )
4275                         return $this->error;
4276
4277                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4278                 do_action( 'xmlrpc_call', 'wp.restoreRevision' );
4279
4280                 if ( ! $revision = wp_get_post_revision( $revision_id ) )
4281                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4282
4283                 if ( wp_is_post_autosave( $revision ) )
4284                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4285
4286                 if ( ! $post = get_post( $revision->post_parent ) )
4287                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4288
4289                 if ( ! current_user_can( 'edit_post', $revision->post_parent ) )
4290                         return new IXR_Error( 401, __( 'Sorry, you cannot edit this post.' ) );
4291
4292                 // Check if revisions are disabled.
4293                 if ( ! wp_revisions_enabled( $post ) )
4294                         return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) );
4295
4296                 $post = wp_restore_post_revision( $revision_id );
4297
4298                 return (bool) $post;
4299         }
4300
4301         /* Blogger API functions.
4302          * specs on http://plant.blogger.com/api and http://groups.yahoo.com/group/bloggerDev/
4303          */
4304
4305         /**
4306          * Retrieve blogs that user owns.
4307          *
4308          * Will make more sense once we support multiple blogs.
4309          *
4310          * @since 1.5.0
4311          *
4312          * @param array  $args {
4313          *     Method arguments. Note: arguments must be ordered as documented.
4314          *
4315          *     @type int    $blog_id (unused)
4316          *     @type string $username
4317          *     @type string $password
4318          * }
4319          * @return array|IXR_Error
4320          */
4321         public function blogger_getUsersBlogs($args) {
4322                 if ( is_multisite() )
4323                         return $this->_multisite_getUsersBlogs($args);
4324
4325                 $this->escape($args);
4326
4327                 $username = $args[1];
4328                 $password = $args[2];
4329
4330                 if ( !$user = $this->login($username, $password) )
4331                         return $this->error;
4332
4333                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4334                 do_action( 'xmlrpc_call', 'blogger.getUsersBlogs' );
4335
4336                 $is_admin = current_user_can('manage_options');
4337
4338                 $struct = array(
4339                         'isAdmin'  => $is_admin,
4340                         'url'      => get_option('home') . '/',
4341                         'blogid'   => '1',
4342                         'blogName' => get_option('blogname'),
4343                         'xmlrpc'   => site_url( 'xmlrpc.php', 'rpc' ),
4344                 );
4345
4346                 return array($struct);
4347         }
4348
4349         /**
4350          * Private function for retrieving a users blogs for multisite setups
4351          *
4352          * @since 3.0.0
4353          * @access protected
4354          *
4355          * @param array $args {
4356          *     Method arguments. Note: arguments must be ordered as documented.
4357          *
4358          *     @type string $username Username.
4359          *     @type string $password Password.
4360          * }
4361          * @return array|IXR_Error
4362          */
4363         protected function _multisite_getUsersBlogs( $args ) {
4364                 $current_blog = get_blog_details();
4365
4366                 $domain = $current_blog->domain;
4367                 $path = $current_blog->path . 'xmlrpc.php';
4368
4369                 $rpc = new IXR_Client( set_url_scheme( "http://{$domain}{$path}" ) );
4370                 $rpc->query('wp.getUsersBlogs', $args[1], $args[2]);
4371                 $blogs = $rpc->getResponse();
4372
4373                 if ( isset($blogs['faultCode']) )
4374                         return new IXR_Error($blogs['faultCode'], $blogs['faultString']);
4375
4376                 if ( $_SERVER['HTTP_HOST'] == $domain && $_SERVER['REQUEST_URI'] == $path ) {
4377                         return $blogs;
4378                 } else {
4379                         foreach ( (array) $blogs as $blog ) {
4380                                 if ( strpos($blog['url'], $_SERVER['HTTP_HOST']) )
4381                                         return array($blog);
4382                         }
4383                         return array();
4384                 }
4385         }
4386
4387         /**
4388          * Retrieve user's data.
4389          *
4390          * Gives your client some info about you, so you don't have to.
4391          *
4392          * @since 1.5.0
4393          *
4394          * @param array  $args {
4395          *     Method arguments. Note: arguments must be ordered as documented.
4396          *
4397          *     @type int    $blog_id (unused)
4398          *     @type string $username
4399          *     @type string $password
4400          * }
4401          * @return array|IXR_Error
4402          */
4403         public function blogger_getUserInfo( $args ) {
4404                 $this->escape( $args );
4405
4406                 $username = $args[1];
4407                 $password = $args[2];
4408
4409                 if ( !$user = $this->login($username, $password) )
4410                         return $this->error;
4411
4412                 if ( !current_user_can( 'edit_posts' ) )
4413                         return new IXR_Error( 401, __( 'Sorry, you do not have access to user data on this site.' ) );
4414
4415                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4416                 do_action( 'xmlrpc_call', 'blogger.getUserInfo' );
4417
4418                 $struct = array(
4419                         'nickname'  => $user->nickname,
4420                         'userid'    => $user->ID,
4421                         'url'       => $user->user_url,
4422                         'lastname'  => $user->last_name,
4423                         'firstname' => $user->first_name
4424                 );
4425
4426                 return $struct;
4427         }
4428
4429         /**
4430          * Retrieve post.
4431          *
4432          * @since 1.5.0
4433          *
4434          * @param array  $args {
4435          *     Method arguments. Note: arguments must be ordered as documented.
4436          *
4437          *     @type int    $blog_id (unused)
4438          *     @type int    $post_ID
4439          *     @type string $username
4440          *     @type string $password
4441          * }
4442          * @return array|IXR_Error
4443          */
4444         public function blogger_getPost( $args ) {
4445                 $this->escape( $args );
4446
4447                 $post_ID  = (int) $args[1];
4448                 $username = $args[2];
4449                 $password = $args[3];
4450
4451                 if ( !$user = $this->login($username, $password) )
4452                         return $this->error;
4453
4454                 $post_data = get_post($post_ID, ARRAY_A);
4455                 if ( ! $post_data )
4456                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4457
4458                 if ( !current_user_can( 'edit_post', $post_ID ) )
4459                         return new IXR_Error( 401, __( 'Sorry, you cannot edit this post.' ) );
4460
4461                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4462                 do_action( 'xmlrpc_call', 'blogger.getPost' );
4463
4464                 $categories = implode(',', wp_get_post_categories($post_ID));
4465
4466                 $content  = '<title>'.wp_unslash($post_data['post_title']).'</title>';
4467                 $content .= '<category>'.$categories.'</category>';
4468                 $content .= wp_unslash($post_data['post_content']);
4469
4470                 $struct = array(
4471                         'userid'    => $post_data['post_author'],
4472                         'dateCreated' => $this->_convert_date( $post_data['post_date'] ),
4473                         'content'     => $content,
4474                         'postid'  => (string) $post_data['ID']
4475                 );
4476
4477                 return $struct;
4478         }
4479
4480         /**
4481          * Retrieve list of recent posts.
4482          *
4483          * @since 1.5.0
4484          *
4485          * @param array  $args {
4486          *     Method arguments. Note: arguments must be ordered as documented.
4487          *
4488          *     @type string $appkey (unused)
4489          *     @type int    $blog_id (unused)
4490          *     @type string $username
4491          *     @type string $password
4492          *     @type int    $numberposts (optional)
4493          * }
4494          * @return array|IXR_Error
4495          */
4496         public function blogger_getRecentPosts( $args ) {
4497
4498                 $this->escape($args);
4499
4500                 // $args[0] = appkey - ignored
4501                 $username = $args[2];
4502                 $password = $args[3];
4503                 if ( isset( $args[4] ) )
4504                         $query = array( 'numberposts' => absint( $args[4] ) );
4505                 else
4506                         $query = array();
4507
4508                 if ( !$user = $this->login($username, $password) )
4509                         return $this->error;
4510
4511                 if ( ! current_user_can( 'edit_posts' ) )
4512                         return new IXR_Error( 401, __( 'Sorry, you cannot edit posts on this site.' ) );
4513
4514                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4515                 do_action( 'xmlrpc_call', 'blogger.getRecentPosts' );
4516
4517                 $posts_list = wp_get_recent_posts( $query );
4518
4519                 if ( !$posts_list ) {
4520                         $this->error = new IXR_Error(500, __('Either there are no posts, or something went wrong.'));
4521                         return $this->error;
4522                 }
4523
4524                 $recent_posts = array();
4525                 foreach ($posts_list as $entry) {
4526                         if ( !current_user_can( 'edit_post', $entry['ID'] ) )
4527                                 continue;
4528
4529                         $post_date  = $this->_convert_date( $entry['post_date'] );
4530                         $categories = implode(',', wp_get_post_categories($entry['ID']));
4531
4532                         $content  = '<title>'.wp_unslash($entry['post_title']).'</title>';
4533                         $content .= '<category>'.$categories.'</category>';
4534                         $content .= wp_unslash($entry['post_content']);
4535
4536                         $recent_posts[] = array(
4537                                 'userid' => $entry['post_author'],
4538                                 'dateCreated' => $post_date,
4539                                 'content' => $content,
4540                                 'postid' => (string) $entry['ID'],
4541                         );
4542                 }
4543
4544                 return $recent_posts;
4545         }
4546
4547         /**
4548          * Deprecated.
4549          *
4550          * @since 1.5.0
4551          * @deprecated 3.5.0
4552          *
4553          * @param array $args Unused.
4554          * @return IXR_Error Error object.
4555          */
4556         public function blogger_getTemplate($args) {
4557                 return new IXR_Error( 403, __('Sorry, that file cannot be edited.' ) );
4558         }
4559
4560         /**
4561          * Deprecated.
4562          *
4563          * @since 1.5.0
4564          * @deprecated 3.5.0
4565          *
4566          * @param array $args Unused.
4567          * @return IXR_Error Error object.
4568          */
4569         public function blogger_setTemplate($args) {
4570                 return new IXR_Error( 403, __('Sorry, that file cannot be edited.' ) );
4571         }
4572
4573         /**
4574          * Creates new post.
4575          *
4576          * @since 1.5.0
4577          *
4578          * @param array $args {
4579          *     Method arguments. Note: arguments must be ordered as documented.
4580          *
4581          *     @type string $appkey (unused)
4582          *     @type int    $blog_id (unused)
4583          *     @type string $username
4584          *     @type string $password
4585          *     @type string $content
4586          *     @type string $publish
4587          * }
4588          * @return int|IXR_Error
4589          */
4590         public function blogger_newPost( $args ) {
4591                 $this->escape( $args );
4592
4593                 $username = $args[2];
4594                 $password = $args[3];
4595                 $content  = $args[4];
4596                 $publish  = $args[5];
4597
4598                 if ( !$user = $this->login($username, $password) )
4599                         return $this->error;
4600
4601                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4602                 do_action( 'xmlrpc_call', 'blogger.newPost' );
4603
4604                 $cap = ($publish) ? 'publish_posts' : 'edit_posts';
4605                 if ( ! current_user_can( get_post_type_object( 'post' )->cap->create_posts ) || !current_user_can($cap) )
4606                         return new IXR_Error(401, __('Sorry, you are not allowed to post on this site.'));
4607
4608                 $post_status = ($publish) ? 'publish' : 'draft';
4609
4610                 $post_author = $user->ID;
4611
4612                 $post_title = xmlrpc_getposttitle($content);
4613                 $post_category = xmlrpc_getpostcategory($content);
4614                 $post_content = xmlrpc_removepostdata($content);
4615
4616                 $post_date = current_time('mysql');
4617                 $post_date_gmt = current_time('mysql', 1);
4618
4619                 $post_data = compact('post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status');
4620
4621                 $post_ID = wp_insert_post($post_data);
4622                 if ( is_wp_error( $post_ID ) )
4623                         return new IXR_Error(500, $post_ID->get_error_message());
4624
4625                 if ( !$post_ID )
4626                         return new IXR_Error(500, __('Sorry, your entry could not be posted. Something wrong happened.'));
4627
4628                 $this->attach_uploads( $post_ID, $post_content );
4629
4630                 /**
4631                  * Fires after a new post has been successfully created via the XML-RPC Blogger API.
4632                  *
4633                  * @since 3.4.0
4634                  *
4635                  * @param int   $post_ID ID of the new post.
4636                  * @param array $args    An array of new post arguments.
4637                  */
4638                 do_action( 'xmlrpc_call_success_blogger_newPost', $post_ID, $args );
4639
4640                 return $post_ID;
4641         }
4642
4643         /**
4644          * Edit a post.
4645          *
4646          * @since 1.5.0
4647          *
4648          * @param array  $args {
4649          *     Method arguments. Note: arguments must be ordered as documented.
4650          *
4651          *     @type int    $blog_id (unused)
4652          *     @type int    $post_ID
4653          *     @type string $username
4654          *     @type string $password
4655          *     @type string $content
4656          *     @type bool   $publish
4657          * }
4658          * @return true|IXR_Error true when done.
4659          */
4660         public function blogger_editPost( $args ) {
4661
4662                 $this->escape($args);
4663
4664                 $post_ID  = (int) $args[1];
4665                 $username = $args[2];
4666                 $password = $args[3];
4667                 $content  = $args[4];
4668                 $publish  = $args[5];
4669
4670                 if ( ! $user = $this->login( $username, $password ) ) {
4671                         return $this->error;
4672                 }
4673
4674                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4675                 do_action( 'xmlrpc_call', 'blogger.editPost' );
4676
4677                 $actual_post = get_post( $post_ID, ARRAY_A );
4678
4679                 if ( ! $actual_post || $actual_post['post_type'] != 'post' ) {
4680                         return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
4681                 }
4682
4683                 $this->escape($actual_post);
4684
4685                 if ( ! current_user_can( 'edit_post', $post_ID ) ) {
4686                         return new IXR_Error(401, __('Sorry, you do not have the right to edit this post.'));
4687                 }
4688                 if ( 'publish' == $actual_post['post_status'] && ! current_user_can( 'publish_posts' ) ) {
4689                         return new IXR_Error( 401, __( 'Sorry, you do not have the right to publish this post.' ) );
4690                 }
4691
4692                 $postdata = array();
4693                 $postdata['ID'] = $actual_post['ID'];
4694                 $postdata['post_content'] = xmlrpc_removepostdata( $content );
4695                 $postdata['post_title'] = xmlrpc_getposttitle( $content );
4696                 $postdata['post_category'] = xmlrpc_getpostcategory( $content );
4697                 $postdata['post_status'] = $actual_post['post_status'];
4698                 $postdata['post_excerpt'] = $actual_post['post_excerpt'];
4699                 $postdata['post_status'] = $publish ? 'publish' : 'draft';
4700
4701                 $result = wp_update_post( $postdata );
4702
4703                 if ( ! $result ) {
4704                         return new IXR_Error(500, __('For some strange yet very annoying reason, this post could not be edited.'));
4705                 }
4706                 $this->attach_uploads( $actual_post['ID'], $postdata['post_content'] );
4707
4708                 /**
4709                  * Fires after a post has been successfully updated via the XML-RPC Blogger API.
4710                  *
4711                  * @since 3.4.0
4712                  *
4713                  * @param int   $post_ID ID of the updated post.
4714                  * @param array $args    An array of arguments for the post to edit.
4715                  */
4716                 do_action( 'xmlrpc_call_success_blogger_editPost', $post_ID, $args );
4717
4718                 return true;
4719         }
4720
4721         /**
4722          * Remove a post.
4723          *
4724          * @since 1.5.0
4725          *
4726          * @param array  $args {
4727          *     Method arguments. Note: arguments must be ordered as documented.
4728          *
4729          *     @type int    $blog_id (unused)
4730          *     @type int    $post_ID
4731          *     @type string $username
4732          *     @type string $password
4733          * }
4734          * @return true|IXR_Error True when post is deleted.
4735          */
4736         public function blogger_deletePost( $args ) {
4737                 $this->escape( $args );
4738
4739                 $post_ID  = (int) $args[1];
4740                 $username = $args[2];
4741                 $password = $args[3];
4742
4743                 if ( !$user = $this->login($username, $password) )
4744                         return $this->error;
4745
4746                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4747                 do_action( 'xmlrpc_call', 'blogger.deletePost' );
4748
4749                 $actual_post = get_post( $post_ID, ARRAY_A );
4750
4751                 if ( ! $actual_post || $actual_post['post_type'] != 'post' ) {
4752                         return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
4753                 }
4754
4755                 if ( ! current_user_can( 'delete_post', $post_ID ) ) {
4756                         return new IXR_Error( 401, __( 'Sorry, you do not have the right to delete this post.' ) );
4757                 }
4758
4759                 $result = wp_delete_post( $post_ID );
4760
4761                 if ( ! $result ) {
4762                         return new IXR_Error( 500, __( 'The post cannot be deleted.' ) );
4763                 }
4764
4765                 /**
4766                  * Fires after a post has been successfully deleted via the XML-RPC Blogger API.
4767                  *
4768                  * @since 3.4.0
4769                  *
4770                  * @param int   $post_ID ID of the deleted post.
4771                  * @param array $args    An array of arguments to delete the post.
4772                  */
4773                 do_action( 'xmlrpc_call_success_blogger_deletePost', $post_ID, $args );
4774
4775                 return true;
4776         }
4777
4778         /* MetaWeblog API functions
4779          * specs on wherever Dave Winer wants them to be
4780          */
4781
4782         /**
4783          * Create a new post.
4784          *
4785          * The 'content_struct' argument must contain:
4786          *  - title
4787          *  - description
4788          *  - mt_excerpt
4789          *  - mt_text_more
4790          *  - mt_keywords
4791          *  - mt_tb_ping_urls
4792          *  - categories
4793          *
4794          * Also, it can optionally contain:
4795          *  - wp_slug
4796          *  - wp_password
4797          *  - wp_page_parent_id
4798          *  - wp_page_order
4799          *  - wp_author_id
4800          *  - post_status | page_status - can be 'draft', 'private', 'publish', or 'pending'
4801          *  - mt_allow_comments - can be 'open' or 'closed'
4802          *  - mt_allow_pings - can be 'open' or 'closed'
4803          *  - date_created_gmt
4804          *  - dateCreated
4805          *  - wp_post_thumbnail
4806          *
4807          * @since 1.5.0
4808          *
4809          * @param array  $args {
4810          *     Method arguments. Note: arguments must be ordered as documented.
4811          *
4812          *     @type int    $blog_id (unused)
4813          *     @type string $username
4814          *     @type string $password
4815          *     @type array  $content_struct
4816          *     @type int    $publish
4817          * }
4818          * @return int|IXR_Error
4819          */
4820         public function mw_newPost($args) {
4821                 $this->escape($args);
4822
4823                 $username       = $args[1];
4824                 $password       = $args[2];
4825                 $content_struct = $args[3];
4826                 $publish        = isset( $args[4] ) ? $args[4] : 0;
4827
4828                 if ( !$user = $this->login($username, $password) )
4829                         return $this->error;
4830
4831                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4832                 do_action( 'xmlrpc_call', 'metaWeblog.newPost' );
4833
4834                 $page_template = '';
4835                 if ( !empty( $content_struct['post_type'] ) ) {
4836                         if ( $content_struct['post_type'] == 'page' ) {
4837                                 if ( $publish )
4838                                         $cap  = 'publish_pages';
4839                                 elseif ( isset( $content_struct['page_status'] ) && 'publish' == $content_struct['page_status'] )
4840                                         $cap  = 'publish_pages';
4841                                 else
4842                                         $cap = 'edit_pages';
4843                                 $error_message = __( 'Sorry, you are not allowed to publish pages on this site.' );
4844                                 $post_type = 'page';
4845                                 if ( !empty( $content_struct['wp_page_template'] ) )
4846                                         $page_template = $content_struct['wp_page_template'];
4847                         } elseif ( $content_struct['post_type'] == 'post' ) {
4848                                 if ( $publish )
4849                                         $cap  = 'publish_posts';
4850                                 elseif ( isset( $content_struct['post_status'] ) && 'publish' == $content_struct['post_status'] )
4851                                         $cap  = 'publish_posts';
4852                                 else
4853                                         $cap = 'edit_posts';
4854                                 $error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
4855                                 $post_type = 'post';
4856                         } else {
4857                                 // No other post_type values are allowed here
4858                                 return new IXR_Error( 401, __( 'Invalid post type' ) );
4859                         }
4860                 } else {
4861                         if ( $publish )
4862                                 $cap  = 'publish_posts';
4863                         elseif ( isset( $content_struct['post_status'] ) && 'publish' == $content_struct['post_status'])
4864                                 $cap  = 'publish_posts';
4865                         else
4866                                 $cap = 'edit_posts';
4867                         $error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
4868                         $post_type = 'post';
4869                 }
4870
4871                 if ( ! current_user_can( get_post_type_object( $post_type )->cap->create_posts ) )
4872                         return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts on this site.' ) );
4873                 if ( !current_user_can( $cap ) )
4874                         return new IXR_Error( 401, $error_message );
4875
4876                 // Check for a valid post format if one was given
4877                 if ( isset( $content_struct['wp_post_format'] ) ) {
4878                         $content_struct['wp_post_format'] = sanitize_key( $content_struct['wp_post_format'] );
4879                         if ( !array_key_exists( $content_struct['wp_post_format'], get_post_format_strings() ) ) {
4880                                 return new IXR_Error( 404, __( 'Invalid post format' ) );
4881                         }
4882                 }
4883
4884                 // Let WordPress generate the post_name (slug) unless
4885                 // one has been provided.
4886                 $post_name = "";
4887                 if ( isset($content_struct['wp_slug']) )
4888                         $post_name = $content_struct['wp_slug'];
4889
4890                 // Only use a password if one was given.
4891                 if ( isset($content_struct['wp_password']) )
4892                         $post_password = $content_struct['wp_password'];
4893
4894                 // Only set a post parent if one was provided.
4895                 if ( isset($content_struct['wp_page_parent_id']) )
4896                         $post_parent = $content_struct['wp_page_parent_id'];
4897
4898                 // Only set the menu_order if it was provided.
4899                 if ( isset($content_struct['wp_page_order']) )
4900                         $menu_order = $content_struct['wp_page_order'];
4901
4902                 $post_author = $user->ID;
4903
4904                 // If an author id was provided then use it instead.
4905                 if ( isset( $content_struct['wp_author_id'] ) && ( $user->ID != $content_struct['wp_author_id'] ) ) {
4906                         switch ( $post_type ) {
4907                                 case "post":
4908                                         if ( !current_user_can( 'edit_others_posts' ) )
4909                                                 return new IXR_Error( 401, __( 'You are not allowed to create posts as this user.' ) );
4910                                         break;
4911                                 case "page":
4912                                         if ( !current_user_can( 'edit_others_pages' ) )
4913                                                 return new IXR_Error( 401, __( 'You are not allowed to create pages as this user.' ) );
4914                                         break;
4915                                 default:
4916                                         return new IXR_Error( 401, __( 'Invalid post type' ) );
4917                         }
4918                         $author = get_userdata( $content_struct['wp_author_id'] );
4919                         if ( ! $author )
4920                                 return new IXR_Error( 404, __( 'Invalid author ID.' ) );
4921                         $post_author = $content_struct['wp_author_id'];
4922                 }
4923
4924                 $post_title = isset( $content_struct['title'] ) ? $content_struct['title'] : null;
4925                 $post_content = isset( $content_struct['description'] ) ? $content_struct['description'] : null;
4926
4927                 $post_status = $publish ? 'publish' : 'draft';
4928
4929                 if ( isset( $content_struct["{$post_type}_status"] ) ) {
4930                         switch ( $content_struct["{$post_type}_status"] ) {
4931                                 case 'draft':
4932                                 case 'pending':
4933                                 case 'private':
4934                                 case 'publish':
4935                                         $post_status = $content_struct["{$post_type}_status"];
4936                                         break;
4937                                 default:
4938                                         $post_status = $publish ? 'publish' : 'draft';
4939                                         break;
4940                         }
4941                 }
4942
4943                 $post_excerpt = isset($content_struct['mt_excerpt']) ? $content_struct['mt_excerpt'] : null;
4944                 $post_more = isset($content_struct['mt_text_more']) ? $content_struct['mt_text_more'] : null;
4945
4946                 $tags_input = isset($content_struct['mt_keywords']) ? $content_struct['mt_keywords'] : null;
4947
4948                 if ( isset($content_struct['mt_allow_comments']) ) {
4949                         if ( !is_numeric($content_struct['mt_allow_comments']) ) {
4950                                 switch ( $content_struct['mt_allow_comments'] ) {
4951                                         case 'closed':
4952                                                 $comment_status = 'closed';
4953                                                 break;
4954                                         case 'open':
4955                                                 $comment_status = 'open';
4956                                                 break;
4957                                         default:
4958                                                 $comment_status = get_default_comment_status( $post_type );
4959                                                 break;
4960                                 }
4961                         } else {
4962                                 switch ( (int) $content_struct['mt_allow_comments'] ) {
4963                                         case 0:
4964                                         case 2:
4965                                                 $comment_status = 'closed';
4966                                                 break;
4967                                         case 1:
4968                                                 $comment_status = 'open';
4969                                                 break;
4970                                         default:
4971                                                 $comment_status = get_default_comment_status( $post_type );
4972                                                 break;
4973                                 }
4974                         }
4975                 } else {
4976                         $comment_status = get_default_comment_status( $post_type );
4977                 }
4978
4979                 if ( isset($content_struct['mt_allow_pings']) ) {
4980                         if ( !is_numeric($content_struct['mt_allow_pings']) ) {
4981                                 switch ( $content_struct['mt_allow_pings'] ) {
4982                                         case 'closed':
4983                                                 $ping_status = 'closed';
4984                                                 break;
4985                                         case 'open':
4986                                                 $ping_status = 'open';
4987                                                 break;
4988                                         default:
4989                                                 $ping_status = get_default_comment_status( $post_type, 'pingback' );
4990                                                 break;
4991                                 }
4992                         } else {
4993                                 switch ( (int) $content_struct['mt_allow_pings'] ) {
4994                                         case 0:
4995                                                 $ping_status = 'closed';
4996                                                 break;
4997                                         case 1:
4998                                                 $ping_status = 'open';
4999                                                 break;
5000                                         default:
5001                                                 $ping_status = get_default_comment_status( $post_type, 'pingback' );
5002                                                 break;
5003                                 }
5004                         }
5005                 } else {
5006                         $ping_status = get_default_comment_status( $post_type, 'pingback' );
5007                 }
5008
5009                 if ( $post_more )
5010                         $post_content = $post_content . '<!--more-->' . $post_more;
5011
5012                 $to_ping = null;
5013                 if ( isset( $content_struct['mt_tb_ping_urls'] ) ) {
5014                         $to_ping = $content_struct['mt_tb_ping_urls'];
5015                         if ( is_array($to_ping) )
5016                                 $to_ping = implode(' ', $to_ping);
5017                 }
5018
5019                 // Do some timestamp voodoo
5020                 if ( !empty( $content_struct['date_created_gmt'] ) )
5021                         // We know this is supposed to be GMT, so we're going to slap that Z on there by force
5022                         $dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
5023                 elseif ( !empty( $content_struct['dateCreated']) )
5024                         $dateCreated = $content_struct['dateCreated']->getIso();
5025
5026                 if ( !empty( $dateCreated ) ) {
5027                         $post_date = get_date_from_gmt(iso8601_to_datetime($dateCreated));
5028                         $post_date_gmt = iso8601_to_datetime($dateCreated, 'GMT');
5029                 } else {
5030                         $post_date = '';
5031                         $post_date_gmt = '';
5032                 }
5033
5034                 $post_category = array();
5035                 if ( isset( $content_struct['categories'] ) ) {
5036                         $catnames = $content_struct['categories'];
5037
5038                         if ( is_array($catnames) ) {
5039                                 foreach ($catnames as $cat) {
5040                                         $post_category[] = get_cat_ID($cat);
5041                                 }
5042                         }
5043                 }
5044
5045                 $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');
5046
5047                 $post_ID = $postdata['ID'] = get_default_post_to_edit( $post_type, true )->ID;
5048
5049                 // Only posts can be sticky
5050                 if ( $post_type == 'post' && isset( $content_struct['sticky'] ) ) {
5051                         $data = $postdata;
5052                         $data['sticky'] = $content_struct['sticky'];
5053                         $error = $this->_toggle_sticky( $data );
5054                         if ( $error ) {
5055                                 return $error;
5056                         }
5057                 }
5058
5059                 if ( isset($content_struct['custom_fields']) )
5060                         $this->set_custom_fields($post_ID, $content_struct['custom_fields']);
5061
5062                 if ( isset ( $content_struct['wp_post_thumbnail'] ) ) {
5063                         if ( set_post_thumbnail( $post_ID, $content_struct['wp_post_thumbnail'] ) === false )
5064                                 return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
5065
5066                         unset( $content_struct['wp_post_thumbnail'] );
5067                 }
5068
5069                 // Handle enclosures
5070                 $thisEnclosure = isset($content_struct['enclosure']) ? $content_struct['enclosure'] : null;
5071                 $this->add_enclosure_if_new($post_ID, $thisEnclosure);
5072
5073                 $this->attach_uploads( $post_ID, $post_content );
5074
5075                 // Handle post formats if assigned, value is validated earlier
5076                 // in this function
5077                 if ( isset( $content_struct['wp_post_format'] ) )
5078                         set_post_format( $post_ID, $content_struct['wp_post_format'] );
5079
5080                 $post_ID = wp_insert_post( $postdata, true );
5081                 if ( is_wp_error( $post_ID ) )
5082                         return new IXR_Error(500, $post_ID->get_error_message());
5083
5084                 if ( !$post_ID )
5085                         return new IXR_Error(500, __('Sorry, your entry could not be posted. Something wrong happened.'));
5086
5087                 /**
5088                  * Fires after a new post has been successfully created via the XML-RPC MovableType API.
5089                  *
5090                  * @since 3.4.0
5091                  *
5092                  * @param int   $post_ID ID of the new post.
5093                  * @param array $args    An array of arguments to create the new post.
5094                  */
5095                 do_action( 'xmlrpc_call_success_mw_newPost', $post_ID, $args );
5096
5097                 return strval($post_ID);
5098         }
5099
5100         /**
5101          * Adds an enclosure to a post if it's new.
5102          *
5103          * @since 2.8.0
5104          *
5105          * @param integer $post_ID   Post ID.
5106          * @param array   $enclosure Enclosure data.
5107          */
5108         public function add_enclosure_if_new( $post_ID, $enclosure ) {
5109                 if ( is_array( $enclosure ) && isset( $enclosure['url'] ) && isset( $enclosure['length'] ) && isset( $enclosure['type'] ) ) {
5110                         $encstring = $enclosure['url'] . "\n" . $enclosure['length'] . "\n" . $enclosure['type'] . "\n";
5111                         $found = false;
5112                         if ( $enclosures = get_post_meta( $post_ID, 'enclosure' ) ) {
5113                                 foreach ( $enclosures as $enc ) {
5114                                         // This method used to omit the trailing new line. #23219
5115                                         if ( rtrim( $enc, "\n" ) == rtrim( $encstring, "\n" ) ) {
5116                                                 $found = true;
5117                                                 break;
5118                                         }
5119                                 }
5120                         }
5121                         if ( ! $found )
5122                                 add_post_meta( $post_ID, 'enclosure', $encstring );
5123                 }
5124         }
5125
5126         /**
5127          * Attach upload to a post.
5128          *
5129          * @since 2.1.0
5130          *
5131          * @global wpdb $wpdb WordPress database abstraction object.
5132          *
5133          * @param int $post_ID Post ID.
5134          * @param string $post_content Post Content for attachment.
5135          */
5136         public function attach_uploads( $post_ID, $post_content ) {
5137                 global $wpdb;
5138
5139                 // find any unattached files
5140                 $attachments = $wpdb->get_results( "SELECT ID, guid FROM {$wpdb->posts} WHERE post_parent = '0' AND post_type = 'attachment'" );
5141                 if ( is_array( $attachments ) ) {
5142                         foreach ( $attachments as $file ) {
5143                                 if ( ! empty( $file->guid ) && strpos( $post_content, $file->guid ) !== false )
5144                                         $wpdb->update($wpdb->posts, array('post_parent' => $post_ID), array('ID' => $file->ID) );
5145                         }
5146                 }
5147         }
5148
5149         /**
5150          * Edit a post.
5151          *
5152          * @since 1.5.0
5153          *
5154          * @param array  $args {
5155          *     Method arguments. Note: arguments must be ordered as documented.
5156          *
5157          *     @type int    $blog_id (unused)
5158          *     @type string $username
5159          *     @type string $password
5160          *     @type array  $content_struct
5161          *     @type int    $publish
5162          * }
5163          * @return bool|IXR_Error True on success.
5164          */
5165         public function mw_editPost( $args ) {
5166                 $this->escape( $args );
5167
5168                 $post_ID        = (int) $args[0];
5169                 $username       = $args[1];
5170                 $password       = $args[2];
5171                 $content_struct = $args[3];
5172                 $publish        = isset( $args[4] ) ? $args[4] : 0;
5173
5174                 if ( ! $user = $this->login($username, $password) )
5175                         return $this->error;
5176
5177                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5178                 do_action( 'xmlrpc_call', 'metaWeblog.editPost' );
5179
5180                 $postdata = get_post( $post_ID, ARRAY_A );
5181
5182                 /*
5183                  * If there is no post data for the give post id, stop now and return an error.
5184                  * Otherwise a new post will be created (which was the old behavior).
5185                  */
5186                 if ( ! $postdata || empty( $postdata[ 'ID' ] ) )
5187                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
5188
5189                 if ( ! current_user_can( 'edit_post', $post_ID ) )
5190                         return new IXR_Error( 401, __( 'Sorry, you do not have the right to edit this post.' ) );
5191
5192                 // Use wp.editPost to edit post types other than post and page.
5193                 if ( ! in_array( $postdata[ 'post_type' ], array( 'post', 'page' ) ) )
5194                         return new IXR_Error( 401, __( 'Invalid post type' ) );
5195
5196                 // Thwart attempt to change the post type.
5197                 if ( ! empty( $content_struct[ 'post_type' ] ) && ( $content_struct['post_type'] != $postdata[ 'post_type' ] ) )
5198                         return new IXR_Error( 401, __( 'The post type may not be changed.' ) );
5199
5200                 // Check for a valid post format if one was given
5201                 if ( isset( $content_struct['wp_post_format'] ) ) {
5202                         $content_struct['wp_post_format'] = sanitize_key( $content_struct['wp_post_format'] );
5203                         if ( !array_key_exists( $content_struct['wp_post_format'], get_post_format_strings() ) ) {
5204                                 return new IXR_Error( 404, __( 'Invalid post format' ) );
5205                         }
5206                 }
5207
5208                 $this->escape($postdata);
5209
5210                 $ID = $postdata['ID'];
5211                 $post_content = $postdata['post_content'];
5212                 $post_title = $postdata['post_title'];
5213                 $post_excerpt = $postdata['post_excerpt'];
5214                 $post_password = $postdata['post_password'];
5215                 $post_parent = $postdata['post_parent'];
5216                 $post_type = $postdata['post_type'];
5217                 $menu_order = $postdata['menu_order'];
5218
5219                 // Let WordPress manage slug if none was provided.
5220                 $post_name = $postdata['post_name'];
5221                 if ( isset($content_struct['wp_slug']) )
5222                         $post_name = $content_struct['wp_slug'];
5223
5224                 // Only use a password if one was given.
5225                 if ( isset($content_struct['wp_password']) )
5226                         $post_password = $content_struct['wp_password'];
5227
5228                 // Only set a post parent if one was given.
5229                 if ( isset($content_struct['wp_page_parent_id']) )
5230                         $post_parent = $content_struct['wp_page_parent_id'];
5231
5232                 // Only set the menu_order if it was given.
5233                 if ( isset($content_struct['wp_page_order']) )
5234                         $menu_order = $content_struct['wp_page_order'];
5235
5236                 $page_template = null;
5237                 if ( ! empty( $content_struct['wp_page_template'] ) && 'page' == $post_type )
5238                         $page_template = $content_struct['wp_page_template'];
5239
5240                 $post_author = $postdata['post_author'];
5241
5242                 // Only set the post_author if one is set.
5243                 if ( isset( $content_struct['wp_author_id'] ) ) {
5244                         // Check permissions if attempting to switch author to or from another user.
5245                         if ( $user->ID != $content_struct['wp_author_id'] || $user->ID != $post_author ) {
5246                                 switch ( $post_type ) {
5247                                         case 'post':
5248                                                 if ( ! current_user_can( 'edit_others_posts' ) ) {
5249                                                         return new IXR_Error( 401, __( 'You are not allowed to change the post author as this user.' ) );
5250                                                 }
5251                                                 break;
5252                                         case 'page':
5253                                                 if ( ! current_user_can( 'edit_others_pages' ) ) {
5254                                                         return new IXR_Error( 401, __( 'You are not allowed to change the page author as this user.' ) );
5255                                                 }
5256                                                 break;
5257                                         default:
5258                                                 return new IXR_Error( 401, __( 'Invalid post type' ) );
5259                                 }
5260                                 $post_author = $content_struct['wp_author_id'];
5261                         }
5262                 }
5263
5264                 if ( isset($content_struct['mt_allow_comments']) ) {
5265                         if ( !is_numeric($content_struct['mt_allow_comments']) ) {
5266                                 switch ( $content_struct['mt_allow_comments'] ) {
5267                                         case 'closed':
5268                                                 $comment_status = 'closed';
5269                                                 break;
5270                                         case 'open':
5271                                                 $comment_status = 'open';
5272                                                 break;
5273                                         default:
5274                                                 $comment_status = get_default_comment_status( $post_type );
5275                                                 break;
5276                                 }
5277                         } else {
5278                                 switch ( (int) $content_struct['mt_allow_comments'] ) {
5279                                         case 0:
5280                                         case 2:
5281                                                 $comment_status = 'closed';
5282                                                 break;
5283                                         case 1:
5284                                                 $comment_status = 'open';
5285                                                 break;
5286                                         default:
5287                                                 $comment_status = get_default_comment_status( $post_type );
5288                                                 break;
5289                                 }
5290                         }
5291                 }
5292
5293                 if ( isset($content_struct['mt_allow_pings']) ) {
5294                         if ( !is_numeric($content_struct['mt_allow_pings']) ) {
5295                                 switch ( $content_struct['mt_allow_pings'] ) {
5296                                         case 'closed':
5297                                                 $ping_status = 'closed';
5298                                                 break;
5299                                         case 'open':
5300                                                 $ping_status = 'open';
5301                                                 break;
5302                                         default:
5303                                                 $ping_status = get_default_comment_status( $post_type, 'pingback' );
5304                                                 break;
5305                                 }
5306                         } else {
5307                                 switch ( (int) $content_struct["mt_allow_pings"] ) {
5308                                         case 0:
5309                                                 $ping_status = 'closed';
5310                                                 break;
5311                                         case 1:
5312                                                 $ping_status = 'open';
5313                                                 break;
5314                                         default:
5315                                                 $ping_status = get_default_comment_status( $post_type, 'pingback' );
5316                                                 break;
5317                                 }
5318                         }
5319                 }
5320
5321                 if ( isset( $content_struct['title'] ) )
5322                         $post_title =  $content_struct['title'];
5323
5324                 if ( isset( $content_struct['description'] ) )
5325                         $post_content = $content_struct['description'];
5326
5327                 $post_category = array();
5328                 if ( isset( $content_struct['categories'] ) ) {
5329                         $catnames = $content_struct['categories'];
5330                         if ( is_array($catnames) ) {
5331                                 foreach ($catnames as $cat) {
5332                                         $post_category[] = get_cat_ID($cat);
5333                                 }
5334                         }
5335                 }
5336
5337                 if ( isset( $content_struct['mt_excerpt'] ) )
5338                         $post_excerpt =  $content_struct['mt_excerpt'];
5339
5340                 $post_more = isset( $content_struct['mt_text_more'] ) ? $content_struct['mt_text_more'] : null;
5341
5342                 $post_status = $publish ? 'publish' : 'draft';
5343                 if ( isset( $content_struct["{$post_type}_status"] ) ) {
5344                         switch( $content_struct["{$post_type}_status"] ) {
5345                                 case 'draft':
5346                                 case 'pending':
5347                                 case 'private':
5348                                 case 'publish':
5349                                         $post_status = $content_struct["{$post_type}_status"];
5350                                         break;
5351                                 default:
5352                                         $post_status = $publish ? 'publish' : 'draft';
5353                                         break;
5354                         }
5355                 }
5356
5357                 $tags_input = isset( $content_struct['mt_keywords'] ) ? $content_struct['mt_keywords'] : null;
5358
5359                 if ( 'publish' == $post_status || 'private' == $post_status ) {
5360                         if ( 'page' == $post_type && ! current_user_can( 'publish_pages' ) ) {
5361                                 return new IXR_Error( 401, __( 'Sorry, you do not have the right to publish this page.' ) );
5362                         } elseif ( ! current_user_can( 'publish_posts' ) ) {
5363                                 return new IXR_Error( 401, __( 'Sorry, you do not have the right to publish this post.' ) );
5364                         }
5365                 }
5366
5367                 if ( $post_more )
5368                         $post_content = $post_content . "<!--more-->" . $post_more;
5369
5370                 $to_ping = null;
5371                 if ( isset( $content_struct['mt_tb_ping_urls'] ) ) {
5372                         $to_ping = $content_struct['mt_tb_ping_urls'];
5373                         if ( is_array($to_ping) )
5374                                 $to_ping = implode(' ', $to_ping);
5375                 }
5376
5377                 // Do some timestamp voodoo.
5378                 if ( !empty( $content_struct['date_created_gmt'] ) )
5379                         // We know this is supposed to be GMT, so we're going to slap that Z on there by force.
5380                         $dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
5381                 elseif ( !empty( $content_struct['dateCreated']) )
5382                         $dateCreated = $content_struct['dateCreated']->getIso();
5383
5384                 // Default to not flagging the post date to be edited unless it's intentional.
5385                 $edit_date = false;
5386
5387                 if ( !empty( $dateCreated ) ) {
5388                         $post_date = get_date_from_gmt(iso8601_to_datetime($dateCreated));
5389                         $post_date_gmt = iso8601_to_datetime($dateCreated, 'GMT');
5390
5391                         // Flag the post date to be edited.
5392                         $edit_date = true;
5393                 } else {
5394                         $post_date     = $postdata['post_date'];
5395                         $post_date_gmt = $postdata['post_date_gmt'];
5396                 }
5397
5398                 // We've got all the data -- post it.
5399                 $newpost = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'comment_status', 'ping_status', 'edit_date', 'post_date', 'post_date_gmt', 'to_ping', 'post_name', 'post_password', 'post_parent', 'menu_order', 'post_author', 'tags_input', 'page_template');
5400
5401                 $result = wp_update_post($newpost, true);
5402                 if ( is_wp_error( $result ) )
5403                         return new IXR_Error(500, $result->get_error_message());
5404
5405                 if ( !$result )
5406                         return new IXR_Error(500, __('Sorry, your entry could not be edited. Something wrong happened.'));
5407
5408                 // Only posts can be sticky
5409                 if ( $post_type == 'post' && isset( $content_struct['sticky'] ) ) {
5410                         $data = $newpost;
5411                         $data['sticky'] = $content_struct['sticky'];
5412                         $data['post_type'] = 'post';
5413                         $error = $this->_toggle_sticky( $data, true );
5414                         if ( $error ) {
5415                                 return $error;
5416                         }
5417                 }
5418
5419                 if ( isset($content_struct['custom_fields']) )
5420                         $this->set_custom_fields($post_ID, $content_struct['custom_fields']);
5421
5422                 if ( isset ( $content_struct['wp_post_thumbnail'] ) ) {
5423
5424                         // Empty value deletes, non-empty value adds/updates.
5425                         if ( empty( $content_struct['wp_post_thumbnail'] ) ) {
5426                                 delete_post_thumbnail( $post_ID );
5427                         } else {
5428                                 if ( set_post_thumbnail( $post_ID, $content_struct['wp_post_thumbnail'] ) === false )
5429                                         return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
5430                         }
5431                         unset( $content_struct['wp_post_thumbnail'] );
5432                 }
5433
5434                 // Handle enclosures.
5435                 $thisEnclosure = isset($content_struct['enclosure']) ? $content_struct['enclosure'] : null;
5436                 $this->add_enclosure_if_new($post_ID, $thisEnclosure);
5437
5438                 $this->attach_uploads( $ID, $post_content );
5439
5440                 // Handle post formats if assigned, validation is handled earlier in this function.
5441                 if ( isset( $content_struct['wp_post_format'] ) )
5442                         set_post_format( $post_ID, $content_struct['wp_post_format'] );
5443
5444                 /**
5445                  * Fires after a post has been successfully updated via the XML-RPC MovableType API.
5446                  *
5447                  * @since 3.4.0
5448                  *
5449                  * @param int   $post_ID ID of the updated post.
5450                  * @param array $args    An array of arguments to update the post.
5451                  */
5452                 do_action( 'xmlrpc_call_success_mw_editPost', $post_ID, $args );
5453
5454                 return true;
5455         }
5456
5457         /**
5458          * Retrieve post.
5459          *
5460          * @since 1.5.0
5461          *
5462          * @param array  $args {
5463          *     Method arguments. Note: arguments must be ordered as documented.
5464          *
5465          *     @type int    $blog_id (unused)
5466          *     @type int    $post_ID
5467          *     @type string $username
5468          *     @type string $password
5469          * }
5470          * @return array|IXR_Error
5471          */
5472         public function mw_getPost( $args ) {
5473                 $this->escape( $args );
5474
5475                 $post_ID  = (int) $args[0];
5476                 $username = $args[1];
5477                 $password = $args[2];
5478
5479                 if ( !$user = $this->login($username, $password) )
5480                         return $this->error;
5481
5482                 $postdata = get_post($post_ID, ARRAY_A);
5483                 if ( ! $postdata )
5484                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
5485
5486                 if ( !current_user_can( 'edit_post', $post_ID ) )
5487                         return new IXR_Error( 401, __( 'Sorry, you cannot edit this post.' ) );
5488
5489                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5490                 do_action( 'xmlrpc_call', 'metaWeblog.getPost' );
5491
5492                 if ($postdata['post_date'] != '') {
5493                         $post_date = $this->_convert_date( $postdata['post_date'] );
5494                         $post_date_gmt = $this->_convert_date_gmt( $postdata['post_date_gmt'],  $postdata['post_date'] );
5495                         $post_modified = $this->_convert_date( $postdata['post_modified'] );
5496                         $post_modified_gmt = $this->_convert_date_gmt( $postdata['post_modified_gmt'], $postdata['post_modified'] );
5497
5498                         $categories = array();
5499                         $catids = wp_get_post_categories($post_ID);
5500                         foreach ($catids as $catid)
5501                                 $categories[] = get_cat_name($catid);
5502
5503                         $tagnames = array();
5504                         $tags = wp_get_post_tags( $post_ID );
5505                         if ( !empty( $tags ) ) {
5506                                 foreach ( $tags as $tag )
5507                                         $tagnames[] = $tag->name;
5508                                 $tagnames = implode( ', ', $tagnames );
5509                         } else {
5510                                 $tagnames = '';
5511                         }
5512
5513                         $post = get_extended($postdata['post_content']);
5514                         $link = get_permalink($postdata['ID']);
5515
5516                         // Get the author info.
5517                         $author = get_userdata($postdata['post_author']);
5518
5519                         $allow_comments = ('open' == $postdata['comment_status']) ? 1 : 0;
5520                         $allow_pings = ('open' == $postdata['ping_status']) ? 1 : 0;
5521
5522                         // Consider future posts as published
5523                         if ( $postdata['post_status'] === 'future' )
5524                                 $postdata['post_status'] = 'publish';
5525
5526                         // Get post format
5527                         $post_format = get_post_format( $post_ID );
5528                         if ( empty( $post_format ) )
5529                                 $post_format = 'standard';
5530
5531                         $sticky = false;
5532                         if ( is_sticky( $post_ID ) )
5533                                 $sticky = true;
5534
5535                         $enclosure = array();
5536                         foreach ( (array) get_post_custom($post_ID) as $key => $val) {
5537                                 if ($key == 'enclosure') {
5538                                         foreach ( (array) $val as $enc ) {
5539                                                 $encdata = explode("\n", $enc);
5540                                                 $enclosure['url'] = trim(htmlspecialchars($encdata[0]));
5541                                                 $enclosure['length'] = (int) trim($encdata[1]);
5542                                                 $enclosure['type'] = trim($encdata[2]);
5543                                                 break 2;
5544                                         }
5545                                 }
5546                         }
5547
5548                         $resp = array(
5549                                 'dateCreated' => $post_date,
5550                                 'userid' => $postdata['post_author'],
5551                                 'postid' => $postdata['ID'],
5552                                 'description' => $post['main'],
5553                                 'title' => $postdata['post_title'],
5554                                 'link' => $link,
5555                                 'permaLink' => $link,
5556                                 // commented out because no other tool seems to use this
5557                                 //            'content' => $entry['post_content'],
5558                                 'categories' => $categories,
5559                                 'mt_excerpt' => $postdata['post_excerpt'],
5560                                 'mt_text_more' => $post['extended'],
5561                                 'wp_more_text' => $post['more_text'],
5562                                 'mt_allow_comments' => $allow_comments,
5563                                 'mt_allow_pings' => $allow_pings,
5564                                 'mt_keywords' => $tagnames,
5565                                 'wp_slug' => $postdata['post_name'],
5566                                 'wp_password' => $postdata['post_password'],
5567                                 'wp_author_id' => (string) $author->ID,
5568                                 'wp_author_display_name' => $author->display_name,
5569                                 'date_created_gmt' => $post_date_gmt,
5570                                 'post_status' => $postdata['post_status'],
5571                                 'custom_fields' => $this->get_custom_fields($post_ID),
5572                                 'wp_post_format' => $post_format,
5573                                 'sticky' => $sticky,
5574                                 'date_modified' => $post_modified,
5575                                 'date_modified_gmt' => $post_modified_gmt
5576                         );
5577
5578                         if ( !empty($enclosure) ) $resp['enclosure'] = $enclosure;
5579
5580                         $resp['wp_post_thumbnail'] = get_post_thumbnail_id( $postdata['ID'] );
5581
5582                         return $resp;
5583                 } else {
5584                         return new IXR_Error(404, __('Sorry, no such post.'));
5585                 }
5586         }
5587
5588         /**
5589          * Retrieve list of recent posts.
5590          *
5591          * @since 1.5.0
5592          *
5593          * @param array  $args {
5594          *     Method arguments. Note: arguments must be ordered as documented.
5595          *
5596          *     @type int    $blog_id (unused)
5597          *     @type string $username
5598          *     @type string $password
5599          *     @type int    $numberposts
5600          * }
5601          * @return array|IXR_Error
5602          */
5603         public function mw_getRecentPosts( $args ) {
5604                 $this->escape( $args );
5605
5606                 $username = $args[1];
5607                 $password = $args[2];
5608                 if ( isset( $args[3] ) )
5609                         $query = array( 'numberposts' => absint( $args[3] ) );
5610                 else
5611                         $query = array();
5612
5613                 if ( !$user = $this->login($username, $password) )
5614                         return $this->error;
5615
5616                 if ( ! current_user_can( 'edit_posts' ) )
5617                         return new IXR_Error( 401, __( 'Sorry, you cannot edit posts on this site.' ) );
5618
5619                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5620                 do_action( 'xmlrpc_call', 'metaWeblog.getRecentPosts' );
5621
5622                 $posts_list = wp_get_recent_posts( $query );
5623
5624                 if ( !$posts_list )
5625                         return array();
5626
5627                 $recent_posts = array();
5628                 foreach ($posts_list as $entry) {
5629                         if ( !current_user_can( 'edit_post', $entry['ID'] ) )
5630                                 continue;
5631
5632                         $post_date = $this->_convert_date( $entry['post_date'] );
5633                         $post_date_gmt = $this->_convert_date_gmt( $entry['post_date_gmt'], $entry['post_date'] );
5634                         $post_modified = $this->_convert_date( $entry['post_modified'] );
5635                         $post_modified_gmt = $this->_convert_date_gmt( $entry['post_modified_gmt'], $entry['post_modified'] );
5636
5637                         $categories = array();
5638                         $catids = wp_get_post_categories($entry['ID']);
5639                         foreach ( $catids as $catid )
5640                                 $categories[] = get_cat_name($catid);
5641
5642                         $tagnames = array();
5643                         $tags = wp_get_post_tags( $entry['ID'] );
5644                         if ( !empty( $tags ) ) {
5645                                 foreach ( $tags as $tag ) {
5646                                         $tagnames[] = $tag->name;
5647                                 }
5648                                 $tagnames = implode( ', ', $tagnames );
5649                         } else {
5650                                 $tagnames = '';
5651                         }
5652
5653                         $post = get_extended($entry['post_content']);
5654                         $link = get_permalink($entry['ID']);
5655
5656                         // Get the post author info.
5657                         $author = get_userdata($entry['post_author']);
5658
5659                         $allow_comments = ('open' == $entry['comment_status']) ? 1 : 0;
5660                         $allow_pings = ('open' == $entry['ping_status']) ? 1 : 0;
5661
5662                         // Consider future posts as published
5663                         if ( $entry['post_status'] === 'future' )
5664                                 $entry['post_status'] = 'publish';
5665
5666                         // Get post format
5667                         $post_format = get_post_format( $entry['ID'] );
5668                         if ( empty( $post_format ) )
5669                                 $post_format = 'standard';
5670
5671                         $recent_posts[] = array(
5672                                 'dateCreated' => $post_date,
5673                                 'userid' => $entry['post_author'],
5674                                 'postid' => (string) $entry['ID'],
5675                                 'description' => $post['main'],
5676                                 'title' => $entry['post_title'],
5677                                 'link' => $link,
5678                                 'permaLink' => $link,
5679                                 // commented out because no other tool seems to use this
5680                                 // 'content' => $entry['post_content'],
5681                                 'categories' => $categories,
5682                                 'mt_excerpt' => $entry['post_excerpt'],
5683                                 'mt_text_more' => $post['extended'],
5684                                 'wp_more_text' => $post['more_text'],
5685                                 'mt_allow_comments' => $allow_comments,
5686                                 'mt_allow_pings' => $allow_pings,
5687                                 'mt_keywords' => $tagnames,
5688                                 'wp_slug' => $entry['post_name'],
5689                                 'wp_password' => $entry['post_password'],
5690                                 'wp_author_id' => (string) $author->ID,
5691                                 'wp_author_display_name' => $author->display_name,
5692                                 'date_created_gmt' => $post_date_gmt,
5693                                 'post_status' => $entry['post_status'],
5694                                 'custom_fields' => $this->get_custom_fields($entry['ID']),
5695                                 'wp_post_format' => $post_format,
5696                                 'date_modified' => $post_modified,
5697                                 'date_modified_gmt' => $post_modified_gmt,
5698                                 'sticky' => ( $entry['post_type'] === 'post' && is_sticky( $entry['ID'] ) ),
5699                                 'wp_post_thumbnail' => get_post_thumbnail_id( $entry['ID'] )
5700                         );
5701                 }
5702
5703                 return $recent_posts;
5704         }
5705
5706         /**
5707          * Retrieve the list of categories on a given blog.
5708          *
5709          * @since 1.5.0
5710          *
5711          * @param array  $args {
5712          *     Method arguments. Note: arguments must be ordered as documented.
5713          *
5714          *     @type int    $blog_id (unused)
5715          *     @type string $username
5716          *     @type string $password
5717          * }
5718          * @return array|IXR_Error
5719          */
5720         public function mw_getCategories( $args ) {
5721                 $this->escape( $args );
5722
5723                 $username = $args[1];
5724                 $password = $args[2];
5725
5726                 if ( !$user = $this->login($username, $password) )
5727                         return $this->error;
5728
5729                 if ( !current_user_can( 'edit_posts' ) )
5730                         return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
5731
5732                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5733                 do_action( 'xmlrpc_call', 'metaWeblog.getCategories' );
5734
5735                 $categories_struct = array();
5736
5737                 if ( $cats = get_categories(array('get' => 'all')) ) {
5738                         foreach ( $cats as $cat ) {
5739                                 $struct = array();
5740                                 $struct['categoryId'] = $cat->term_id;
5741                                 $struct['parentId'] = $cat->parent;
5742                                 $struct['description'] = $cat->name;
5743                                 $struct['categoryDescription'] = $cat->description;
5744                                 $struct['categoryName'] = $cat->name;
5745                                 $struct['htmlUrl'] = esc_html(get_category_link($cat->term_id));
5746                                 $struct['rssUrl'] = esc_html(get_category_feed_link($cat->term_id, 'rss2'));
5747
5748                                 $categories_struct[] = $struct;
5749                         }
5750                 }
5751
5752                 return $categories_struct;
5753         }
5754
5755         /**
5756          * Uploads a file, following your settings.
5757          *
5758          * Adapted from a patch by Johann Richard.
5759          *
5760          * @link http://mycvs.org/archives/2004/06/30/file-upload-to-wordpress-in-ecto/
5761          *
5762          * @since 1.5.0
5763          *
5764          * @global wpdb $wpdb WordPress database abstraction object.
5765          *
5766          * @param array  $args {
5767          *     Method arguments. Note: arguments must be ordered as documented.
5768          *
5769          *     @type int    $blog_id (unused)
5770          *     @type string $username
5771          *     @type string $password
5772          *     @type array  $data
5773          * }
5774          * @return array|IXR_Error
5775          */
5776         public function mw_newMediaObject( $args ) {
5777                 global $wpdb;
5778
5779                 $username = $this->escape( $args[1] );
5780                 $password = $this->escape( $args[2] );
5781                 $data     = $args[3];
5782
5783                 $name = sanitize_file_name( $data['name'] );
5784                 $type = $data['type'];
5785                 $bits = $data['bits'];
5786
5787                 if ( !$user = $this->login($username, $password) )
5788                         return $this->error;
5789
5790                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5791                 do_action( 'xmlrpc_call', 'metaWeblog.newMediaObject' );
5792
5793                 if ( !current_user_can('upload_files') ) {
5794                         $this->error = new IXR_Error( 401, __( 'You do not have permission to upload files.' ) );
5795                         return $this->error;
5796                 }
5797
5798                 if ( is_multisite() && upload_is_user_over_quota( false ) ) {
5799                         $this->error = new IXR_Error( 401, __( 'Sorry, you have used your space allocation.' ) );
5800                         return $this->error;
5801                 }
5802
5803                 /**
5804                  * Filter whether to preempt the XML-RPC media upload.
5805                  *
5806                  * Passing a truthy value will effectively short-circuit the media upload,
5807                  * returning that value as a 500 error instead.
5808                  *
5809                  * @since 2.1.0
5810                  *
5811                  * @param bool $error Whether to pre-empt the media upload. Default false.
5812                  */
5813                 if ( $upload_err = apply_filters( 'pre_upload_error', false ) ) {
5814                         return new IXR_Error( 500, $upload_err );
5815                 }
5816
5817                 $upload = wp_upload_bits($name, null, $bits);
5818                 if ( ! empty($upload['error']) ) {
5819                         $errorString = sprintf(__('Could not write file %1$s (%2$s)'), $name, $upload['error']);
5820                         return new IXR_Error(500, $errorString);
5821                 }
5822                 // Construct the attachment array
5823                 $post_id = 0;
5824                 if ( ! empty( $data['post_id'] ) ) {
5825                         $post_id = (int) $data['post_id'];
5826
5827                         if ( ! current_user_can( 'edit_post', $post_id ) )
5828                                 return new IXR_Error( 401, __( 'Sorry, you cannot edit this post.' ) );
5829                 }
5830                 $attachment = array(
5831                         'post_title' => $name,
5832                         'post_content' => '',
5833                         'post_type' => 'attachment',
5834                         'post_parent' => $post_id,
5835                         'post_mime_type' => $type,
5836                         'guid' => $upload[ 'url' ]
5837                 );
5838
5839                 // Save the data
5840                 $id = wp_insert_attachment( $attachment, $upload[ 'file' ], $post_id );
5841                 wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $upload['file'] ) );
5842
5843                 /**
5844                  * Fires after a new attachment has been added via the XML-RPC MovableType API.
5845                  *
5846                  * @since 3.4.0
5847                  *
5848                  * @param int   $id   ID of the new attachment.
5849                  * @param array $args An array of arguments to add the attachment.
5850                  */
5851                 do_action( 'xmlrpc_call_success_mw_newMediaObject', $id, $args );
5852
5853                 $struct = $this->_prepare_media_item( get_post( $id ) );
5854
5855                 // Deprecated values
5856                 $struct['id']   = $struct['attachment_id'];
5857                 $struct['file'] = $struct['title'];
5858                 $struct['url']  = $struct['link'];
5859
5860                 return $struct;
5861         }
5862
5863         /* MovableType API functions
5864          * specs on http://www.movabletype.org/docs/mtmanual_programmatic.html
5865          */
5866
5867         /**
5868          * Retrieve the post titles of recent posts.
5869          *
5870          * @since 1.5.0
5871          *
5872          * @param array  $args {
5873          *     Method arguments. Note: arguments must be ordered as documented.
5874          *
5875          *     @type int    $blog_id (unused)
5876          *     @type string $username
5877          *     @type string $password
5878          *     @type int    $numberposts
5879          * }
5880          * @return array|IXR_Error
5881          */
5882         public function mt_getRecentPostTitles( $args ) {
5883                 $this->escape( $args );
5884
5885                 $username = $args[1];
5886                 $password = $args[2];
5887                 if ( isset( $args[3] ) )
5888                         $query = array( 'numberposts' => absint( $args[3] ) );
5889                 else
5890                         $query = array();
5891
5892                 if ( !$user = $this->login($username, $password) )
5893                         return $this->error;
5894
5895                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5896                 do_action( 'xmlrpc_call', 'mt.getRecentPostTitles' );
5897
5898                 $posts_list = wp_get_recent_posts( $query );
5899
5900                 if ( !$posts_list ) {
5901                         $this->error = new IXR_Error(500, __('Either there are no posts, or something went wrong.'));
5902                         return $this->error;
5903                 }
5904
5905                 $recent_posts = array();
5906
5907                 foreach ($posts_list as $entry) {
5908                         if ( !current_user_can( 'edit_post', $entry['ID'] ) )
5909                                 continue;
5910
5911                         $post_date = $this->_convert_date( $entry['post_date'] );
5912                         $post_date_gmt = $this->_convert_date_gmt( $entry['post_date_gmt'], $entry['post_date'] );
5913
5914                         $recent_posts[] = array(
5915                                 'dateCreated' => $post_date,
5916                                 'userid' => $entry['post_author'],
5917                                 'postid' => (string) $entry['ID'],
5918                                 'title' => $entry['post_title'],
5919                                 'post_status' => $entry['post_status'],
5920                                 'date_created_gmt' => $post_date_gmt
5921                         );
5922                 }
5923
5924                 return $recent_posts;
5925         }
5926
5927         /**
5928          * Retrieve list of all categories on blog.
5929          *
5930          * @since 1.5.0
5931          *
5932          * @param array  $args {
5933          *     Method arguments. Note: arguments must be ordered as documented.
5934          *
5935          *     @type int    $blog_id (unused)
5936          *     @type string $username
5937          *     @type string $password
5938          * }
5939          * @return array|IXR_Error
5940          */
5941         public function mt_getCategoryList( $args ) {
5942                 $this->escape( $args );
5943
5944                 $username = $args[1];
5945                 $password = $args[2];
5946
5947                 if ( !$user = $this->login($username, $password) )
5948                         return $this->error;
5949
5950                 if ( !current_user_can( 'edit_posts' ) )
5951                         return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
5952
5953                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5954                 do_action( 'xmlrpc_call', 'mt.getCategoryList' );
5955
5956                 $categories_struct = array();
5957
5958                 if ( $cats = get_categories(array('hide_empty' => 0, 'hierarchical' => 0)) ) {
5959                         foreach ( $cats as $cat ) {
5960                                 $struct = array();
5961                                 $struct['categoryId'] = $cat->term_id;
5962                                 $struct['categoryName'] = $cat->name;
5963
5964                                 $categories_struct[] = $struct;
5965                         }
5966                 }
5967
5968                 return $categories_struct;
5969         }
5970
5971         /**
5972          * Retrieve post categories.
5973          *
5974          * @since 1.5.0
5975          *
5976          * @param array  $args {
5977          *     Method arguments. Note: arguments must be ordered as documented.
5978          *
5979          *     @type int    $post_ID
5980          *     @type string $username
5981          *     @type string $password
5982          * }
5983          * @return array|IXR_Error
5984          */
5985         public function mt_getPostCategories( $args ) {
5986                 $this->escape( $args );
5987
5988                 $post_ID  = (int) $args[0];
5989                 $username = $args[1];
5990                 $password = $args[2];
5991
5992                 if ( !$user = $this->login($username, $password) )
5993                         return $this->error;
5994
5995                 if ( ! get_post( $post_ID ) )
5996                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
5997
5998                 if ( !current_user_can( 'edit_post', $post_ID ) )
5999                         return new IXR_Error( 401, __( 'Sorry, you can not edit this post.' ) );
6000
6001                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6002                 do_action( 'xmlrpc_call', 'mt.getPostCategories' );
6003
6004                 $categories = array();
6005                 $catids = wp_get_post_categories(intval($post_ID));
6006                 // first listed category will be the primary category
6007                 $isPrimary = true;
6008                 foreach ( $catids as $catid ) {
6009                         $categories[] = array(
6010                                 'categoryName' => get_cat_name($catid),
6011                                 'categoryId' => (string) $catid,
6012                                 'isPrimary' => $isPrimary
6013                         );
6014                         $isPrimary = false;
6015                 }
6016
6017                 return $categories;
6018         }
6019
6020         /**
6021          * Sets categories for a post.
6022          *
6023          * @since 1.5.0
6024          *
6025          * @param array  $args {
6026          *     Method arguments. Note: arguments must be ordered as documented.
6027          *
6028          *     @type int    $post_ID
6029          *     @type string $username
6030          *     @type string $password
6031          *     @type array  $categories
6032          * }
6033          * @return true|IXR_Error True on success.
6034          */
6035         public function mt_setPostCategories( $args ) {
6036                 $this->escape( $args );
6037
6038                 $post_ID    = (int) $args[0];
6039                 $username   = $args[1];
6040                 $password   = $args[2];
6041                 $categories = $args[3];
6042
6043                 if ( !$user = $this->login($username, $password) )
6044                         return $this->error;
6045
6046                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6047                 do_action( 'xmlrpc_call', 'mt.setPostCategories' );
6048
6049                 if ( ! get_post( $post_ID ) )
6050                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
6051
6052                 if ( !current_user_can('edit_post', $post_ID) )
6053                         return new IXR_Error(401, __('Sorry, you cannot edit this post.'));
6054
6055                 $catids = array();
6056                 foreach ( $categories as $cat ) {
6057                         $catids[] = $cat['categoryId'];
6058                 }
6059
6060                 wp_set_post_categories($post_ID, $catids);
6061
6062                 return true;
6063         }
6064
6065         /**
6066          * Retrieve an array of methods supported by this server.
6067          *
6068          * @since 1.5.0
6069          *
6070          * @return array
6071          */
6072         public function mt_supportedMethods() {
6073                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6074                 do_action( 'xmlrpc_call', 'mt.supportedMethods' );
6075
6076                 return array_keys( $this->methods );
6077         }
6078
6079         /**
6080          * Retrieve an empty array because we don't support per-post text filters.
6081          *
6082          * @since 1.5.0
6083          */
6084         public function mt_supportedTextFilters() {
6085                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6086                 do_action( 'xmlrpc_call', 'mt.supportedTextFilters' );
6087
6088                 /**
6089                  * Filter the MoveableType text filters list for XML-RPC.
6090                  *
6091                  * @since 2.2.0
6092                  *
6093                  * @param array $filters An array of text filters.
6094                  */
6095                 return apply_filters( 'xmlrpc_text_filters', array() );
6096         }
6097
6098         /**
6099          * Retrieve trackbacks sent to a given post.
6100          *
6101          * @since 1.5.0
6102          *
6103          * @global wpdb $wpdb WordPress database abstraction object.
6104          *
6105          * @param int $post_ID
6106          * @return array|IXR_Error
6107          */
6108         public function mt_getTrackbackPings( $post_ID ) {
6109                 global $wpdb;
6110
6111                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6112                 do_action( 'xmlrpc_call', 'mt.getTrackbackPings' );
6113
6114                 $actual_post = get_post($post_ID, ARRAY_A);
6115
6116                 if ( !$actual_post )
6117                         return new IXR_Error(404, __('Sorry, no such post.'));
6118
6119                 $comments = $wpdb->get_results( $wpdb->prepare("SELECT comment_author_url, comment_content, comment_author_IP, comment_type FROM $wpdb->comments WHERE comment_post_ID = %d", $post_ID) );
6120
6121                 if ( !$comments )
6122                         return array();
6123
6124                 $trackback_pings = array();
6125                 foreach ( $comments as $comment ) {
6126                         if ( 'trackback' == $comment->comment_type ) {
6127                                 $content = $comment->comment_content;
6128                                 $title = substr($content, 8, (strpos($content, '</strong>') - 8));
6129                                 $trackback_pings[] = array(
6130                                         'pingTitle' => $title,
6131                                         'pingURL'   => $comment->comment_author_url,
6132                                         'pingIP'    => $comment->comment_author_IP
6133                                 );
6134                         }
6135                 }
6136
6137                 return $trackback_pings;
6138         }
6139
6140         /**
6141          * Sets a post's publish status to 'publish'.
6142          *
6143          * @since 1.5.0
6144          *
6145          * @param array  $args {
6146          *     Method arguments. Note: arguments must be ordered as documented.
6147          *
6148          *     @type int    $post_ID
6149          *     @type string $username
6150          *     @type string $password
6151          * }
6152          * @return int|IXR_Error
6153          */
6154         public function mt_publishPost( $args ) {
6155                 $this->escape( $args );
6156
6157                 $post_ID  = (int) $args[0];
6158                 $username = $args[1];
6159                 $password = $args[2];
6160
6161                 if ( !$user = $this->login($username, $password) )
6162                         return $this->error;
6163
6164                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6165                 do_action( 'xmlrpc_call', 'mt.publishPost' );
6166
6167                 $postdata = get_post($post_ID, ARRAY_A);
6168                 if ( ! $postdata )
6169                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
6170
6171                 if ( !current_user_can('publish_posts') || !current_user_can('edit_post', $post_ID) )
6172                         return new IXR_Error(401, __('Sorry, you cannot publish this post.'));
6173
6174                 $postdata['post_status'] = 'publish';
6175
6176                 // retain old cats
6177                 $cats = wp_get_post_categories($post_ID);
6178                 $postdata['post_category'] = $cats;
6179                 $this->escape($postdata);
6180
6181                 return wp_update_post( $postdata );
6182         }
6183
6184         /* PingBack functions
6185          * specs on www.hixie.ch/specs/pingback/pingback
6186          */
6187
6188         /**
6189          * Retrieves a pingback and registers it.
6190          *
6191          * @since 1.5.0
6192          *
6193          * @global wpdb $wpdb WordPress database abstraction object.
6194          * @global string $wp_version
6195          *
6196          * @param array  $args {
6197          *     Method arguments. Note: arguments must be ordered as documented.
6198          *
6199          *     @type string $pagelinkedfrom
6200          *     @type string $pagelinkedto
6201          * }
6202          * @return string|IXR_Error
6203          */
6204         public function pingback_ping( $args ) {
6205                 global $wpdb, $wp_version;
6206
6207                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6208                 do_action( 'xmlrpc_call', 'pingback.ping' );
6209
6210                 $this->escape( $args );
6211
6212                 $pagelinkedfrom = str_replace( '&amp;', '&', $args[0] );
6213                 $pagelinkedto = str_replace( '&amp;', '&', $args[1] );
6214                 $pagelinkedto = str_replace( '&', '&amp;', $pagelinkedto );
6215
6216                 /**
6217                  * Filter the pingback source URI.
6218                  *
6219                  * @since 3.6.0
6220                  *
6221                  * @param string $pagelinkedfrom URI of the page linked from.
6222                  * @param string $pagelinkedto   URI of the page linked to.
6223                  */
6224                 $pagelinkedfrom = apply_filters( 'pingback_ping_source_uri', $pagelinkedfrom, $pagelinkedto );
6225
6226                 if ( ! $pagelinkedfrom )
6227                         return $this->pingback_error( 0, __( 'A valid URL was not provided.' ) );
6228
6229                 // Check if the page linked to is in our site
6230                 $pos1 = strpos($pagelinkedto, str_replace(array('http://www.','http://','https://www.','https://'), '', get_option('home')));
6231                 if ( !$pos1 )
6232                         return $this->pingback_error( 0, __( 'Is there no link to us?' ) );
6233
6234                 // let's find which post is linked to
6235                 // FIXME: does url_to_postid() cover all these cases already?
6236                 //        if so, then let's use it and drop the old code.
6237                 $urltest = parse_url($pagelinkedto);
6238                 if ( $post_ID = url_to_postid($pagelinkedto) ) {
6239                         // $way
6240                 } elseif ( isset( $urltest['path'] ) && preg_match('#p/[0-9]{1,}#', $urltest['path'], $match) ) {
6241                         // the path defines the post_ID (archives/p/XXXX)
6242                         $blah = explode('/', $match[0]);
6243                         $post_ID = (int) $blah[1];
6244                 } elseif ( isset( $urltest['query'] ) && preg_match('#p=[0-9]{1,}#', $urltest['query'], $match) ) {
6245                         // the querystring defines the post_ID (?p=XXXX)
6246                         $blah = explode('=', $match[0]);
6247                         $post_ID = (int) $blah[1];
6248                 } elseif ( isset($urltest['fragment']) ) {
6249                         // an #anchor is there, it's either...
6250                         if ( intval($urltest['fragment']) ) {
6251                                 // ...an integer #XXXX (simplest case)
6252                                 $post_ID = (int) $urltest['fragment'];
6253                         } elseif ( preg_match('/post-[0-9]+/',$urltest['fragment']) ) {
6254                                 // ...a post id in the form 'post-###'
6255                                 $post_ID = preg_replace('/[^0-9]+/', '', $urltest['fragment']);
6256                         } elseif ( is_string($urltest['fragment']) ) {
6257                                 // ...or a string #title, a little more complicated
6258                                 $title = preg_replace('/[^a-z0-9]/i', '.', $urltest['fragment']);
6259                                 $sql = $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE post_title RLIKE %s", $title );
6260                                 if (! ($post_ID = $wpdb->get_var($sql)) ) {
6261                                         // returning unknown error '0' is better than die()ing
6262                                         return $this->pingback_error( 0, '' );
6263                                 }
6264                         }
6265                 } else {
6266                         // TODO: Attempt to extract a post ID from the given URL
6267                         return $this->pingback_error( 33, __('The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.' ) );
6268                 }
6269                 $post_ID = (int) $post_ID;
6270
6271                 $post = get_post($post_ID);
6272
6273                 if ( !$post ) // Post_ID not found
6274                         return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.' ) );
6275
6276                 if ( $post_ID == url_to_postid($pagelinkedfrom) )
6277                         return $this->pingback_error( 0, __( 'The source URL and the target URL cannot both point to the same resource.' ) );
6278
6279                 // Check if pings are on
6280                 if ( !pings_open($post) )
6281                         return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.' ) );
6282
6283                 // Let's check that the remote site didn't already pingback this entry
6284                 if ( $wpdb->get_results( $wpdb->prepare("SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_author_url = %s", $post_ID, $pagelinkedfrom) ) )
6285                         return $this->pingback_error( 48, __( 'The pingback has already been registered.' ) );
6286
6287                 // very stupid, but gives time to the 'from' server to publish !
6288                 sleep(1);
6289
6290                 $remote_ip = preg_replace( '/[^0-9a-fA-F:., ]/', '', $_SERVER['REMOTE_ADDR'] );
6291
6292                 /** This filter is documented in wp-includes/class-http.php */
6293                 $user_agent = apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ) );
6294
6295                 // Let's check the remote site
6296                 $http_api_args = array(
6297                         'timeout' => 10,
6298                         'redirection' => 0,
6299                         'limit_response_size' => 153600, // 150 KB
6300                         'user-agent' => "$user_agent; verifying pingback from $remote_ip",
6301                         'headers' => array(
6302                                 'X-Pingback-Forwarded-For' => $remote_ip,
6303                         ),
6304                 );
6305
6306                 $request = wp_safe_remote_get( $pagelinkedfrom, $http_api_args );
6307                 $remote_source = $remote_source_original = wp_remote_retrieve_body( $request );
6308
6309                 if ( ! $remote_source ) {
6310                         return $this->pingback_error( 16, __( 'The source URL does not exist.' ) );
6311                 }
6312
6313                 /**
6314                  * Filter the pingback remote source.
6315                  *
6316                  * @since 2.5.0
6317                  *
6318                  * @param string $remote_source Response source for the page linked from.
6319                  * @param string $pagelinkedto  URL of the page linked to.
6320                  */
6321                 $remote_source = apply_filters( 'pre_remote_source', $remote_source, $pagelinkedto );
6322
6323                 // Work around bug in strip_tags():
6324                 $remote_source = str_replace( '<!DOC', '<DOC', $remote_source );
6325                 $remote_source = preg_replace( '/[\r\n\t ]+/', ' ', $remote_source ); // normalize spaces
6326                 $remote_source = preg_replace( "/<\/*(h1|h2|h3|h4|h5|h6|p|th|td|li|dt|dd|pre|caption|input|textarea|button|body)[^>]*>/", "\n\n", $remote_source );
6327
6328                 preg_match( '|<title>([^<]*?)</title>|is', $remote_source, $matchtitle );
6329                 $title = $matchtitle[1];
6330                 if ( empty( $title ) )
6331                         return $this->pingback_error( 32, __('We cannot find a title on that page.' ) );
6332
6333                 $remote_source = strip_tags( $remote_source, '<a>' ); // just keep the tag we need
6334
6335                 $p = explode( "\n\n", $remote_source );
6336
6337                 $preg_target = preg_quote($pagelinkedto, '|');
6338
6339                 foreach ( $p as $para ) {
6340                         if ( strpos($para, $pagelinkedto) !== false ) { // it exists, but is it a link?
6341                                 preg_match("|<a[^>]+?".$preg_target."[^>]*>([^>]+?)</a>|", $para, $context);
6342
6343                                 // If the URL isn't in a link context, keep looking
6344                                 if ( empty($context) )
6345                                         continue;
6346
6347                                 // We're going to use this fake tag to mark the context in a bit
6348                                 // the marker is needed in case the link text appears more than once in the paragraph
6349                                 $excerpt = preg_replace('|\</?wpcontext\>|', '', $para);
6350
6351                                 // prevent really long link text
6352                                 if ( strlen($context[1]) > 100 )
6353                                         $context[1] = substr($context[1], 0, 100) . '&#8230;';
6354
6355                                 $marker = '<wpcontext>'.$context[1].'</wpcontext>';    // set up our marker
6356                                 $excerpt= str_replace($context[0], $marker, $excerpt); // swap out the link for our marker
6357                                 $excerpt = strip_tags($excerpt, '<wpcontext>');        // strip all tags but our context marker
6358                                 $excerpt = trim($excerpt);
6359                                 $preg_marker = preg_quote($marker, '|');
6360                                 $excerpt = preg_replace("|.*?\s(.{0,100}$preg_marker.{0,100})\s.*|s", '$1', $excerpt);
6361                                 $excerpt = strip_tags($excerpt); // YES, again, to remove the marker wrapper
6362                                 break;
6363                         }
6364                 }
6365
6366                 if ( empty($context) ) // Link to target not found
6367                         return $this->pingback_error( 17, __( 'The source URL does not contain a link to the target URL, and so cannot be used as a source.' ) );
6368
6369                 $pagelinkedfrom = str_replace('&', '&amp;', $pagelinkedfrom);
6370
6371                 $context = '[&#8230;] ' . esc_html( $excerpt ) . ' [&#8230;]';
6372                 $pagelinkedfrom = $this->escape( $pagelinkedfrom );
6373
6374                 $comment_post_ID = (int) $post_ID;
6375                 $comment_author = $title;
6376                 $comment_author_email = '';
6377                 $this->escape($comment_author);
6378                 $comment_author_url = $pagelinkedfrom;
6379                 $comment_content = $context;
6380                 $this->escape($comment_content);
6381                 $comment_type = 'pingback';
6382
6383                 $commentdata = compact(
6384                         'comment_post_ID', 'comment_author', 'comment_author_url', 'comment_author_email',
6385                         'comment_content', 'comment_type', 'remote_source', 'remote_source_original'
6386                 );
6387
6388                 $comment_ID = wp_new_comment($commentdata);
6389
6390                 /**
6391                  * Fires after a post pingback has been sent.
6392                  *
6393                  * @since 0.71
6394                  *
6395                  * @param int $comment_ID Comment ID.
6396                  */
6397                 do_action( 'pingback_post', $comment_ID );
6398
6399                 return sprintf(__('Pingback from %1$s to %2$s registered. Keep the web talking! :-)'), $pagelinkedfrom, $pagelinkedto);
6400         }
6401
6402         /**
6403          * Retrieve array of URLs that pingbacked the given URL.
6404          *
6405          * Specs on http://www.aquarionics.com/misc/archives/blogite/0198.html
6406          *
6407          * @since 1.5.0
6408          *
6409          * @global wpdb $wpdb WordPress database abstraction object.
6410          *
6411          * @param string $url
6412          * @return array|IXR_Error
6413          */
6414         public function pingback_extensions_getPingbacks( $url ) {
6415                 global $wpdb;
6416
6417                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6418                 do_action( 'xmlrpc_call', 'pingback.extensions.getPingbacks' );
6419
6420                 $url = $this->escape( $url );
6421
6422                 $post_ID = url_to_postid($url);
6423                 if ( !$post_ID ) {
6424                         // We aren't sure that the resource is available and/or pingback enabled
6425                         return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.' ) );
6426                 }
6427
6428                 $actual_post = get_post($post_ID, ARRAY_A);
6429
6430                 if ( !$actual_post ) {
6431                         // No such post = resource not found
6432                         return $this->pingback_error( 32, __('The specified target URL does not exist.' ) );
6433                 }
6434
6435                 $comments = $wpdb->get_results( $wpdb->prepare("SELECT comment_author_url, comment_content, comment_author_IP, comment_type FROM $wpdb->comments WHERE comment_post_ID = %d", $post_ID) );
6436
6437                 if ( !$comments )
6438                         return array();
6439
6440                 $pingbacks = array();
6441                 foreach ( $comments as $comment ) {
6442                         if ( 'pingback' == $comment->comment_type )
6443                                 $pingbacks[] = $comment->comment_author_url;
6444                 }
6445
6446                 return $pingbacks;
6447         }
6448
6449         /**
6450          * Sends a pingback error based on the given error code and message.
6451          *
6452          * @since 3.6.0
6453          *
6454          * @param int    $code    Error code.
6455          * @param string $message Error message.
6456          * @return IXR_Error Error object.
6457          */
6458         protected function pingback_error( $code, $message ) {
6459                 /**
6460                  * Filter the XML-RPC pingback error return.
6461                  *
6462                  * @since 3.5.1
6463                  *
6464                  * @param IXR_Error $error An IXR_Error object containing the error code and message.
6465                  */
6466                 return apply_filters( 'xmlrpc_pingback_error', new IXR_Error( $code, $message ) );
6467         }
6468 }