]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/class-wp-xmlrpc-server.php
08897dc032f12ab4ecca726f38647beea0c412bf
[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          * @access public
192          */
193         public function serve_request() {
194                 $this->IXR_Server($this->methods);
195         }
196
197         /**
198          * Test XMLRPC API by saying, "Hello!" to client.
199          *
200          * @since 1.5.0
201          *
202          * @return string Hello string response.
203          */
204         public function sayHello() {
205                 return 'Hello!';
206         }
207
208         /**
209          * Test XMLRPC API by adding two numbers for client.
210          *
211          * @since 1.5.0
212          *
213          * @param array  $args {
214          *     Method arguments. Note: arguments must be ordered as documented.
215          *
216          *     @type int $number1 A number to add.
217          *     @type int $number2 A second number to add.
218          * }
219          * @return int Sum of the two given numbers.
220          */
221         public function addTwoNumbers( $args ) {
222                 $number1 = $args[0];
223                 $number2 = $args[1];
224                 return $number1 + $number2;
225         }
226
227         /**
228          * Log user in.
229          *
230          * @since 2.8.0
231          *
232          * @param string $username User's username.
233          * @param string $password User's password.
234          * @return WP_User|bool WP_User object if authentication passed, false otherwise
235          */
236         public function login( $username, $password ) {
237                 /*
238                  * Respect old get_option() filters left for back-compat when the 'enable_xmlrpc'
239                  * option was deprecated in 3.5.0. Use the 'xmlrpc_enabled' hook instead.
240                  */
241                 $enabled = apply_filters( 'pre_option_enable_xmlrpc', false );
242                 if ( false === $enabled ) {
243                         $enabled = apply_filters( 'option_enable_xmlrpc', true );
244                 }
245
246                 /**
247                  * Filter whether XML-RPC is enabled.
248                  *
249                  * This is the proper filter for turning off XML-RPC.
250                  *
251                  * @since 3.5.0
252                  *
253                  * @param bool $enabled Whether XML-RPC is enabled. Default true.
254                  */
255                 $enabled = apply_filters( 'xmlrpc_enabled', $enabled );
256
257                 if ( ! $enabled ) {
258                         $this->error = new IXR_Error( 405, sprintf( __( 'XML-RPC services are disabled on this site.' ) ) );
259                         return false;
260                 }
261
262                 if ( $this->auth_failed ) {
263                         $user = new WP_Error( 'login_prevented' );
264                 } else {
265                         $user = wp_authenticate( $username, $password );
266                 }
267
268                 if ( is_wp_error( $user ) ) {
269                         $this->error = new IXR_Error( 403, __( 'Incorrect username or password.' ) );
270
271                         // Flag that authentication has failed once on this wp_xmlrpc_server instance
272                         $this->auth_failed = true;
273
274                         /**
275                          * Filter the XML-RPC user login error message.
276                          *
277                          * @since 3.5.0
278                          *
279                          * @param string  $error The XML-RPC error message.
280                          * @param WP_User $user  WP_User object.
281                          */
282                         $this->error = apply_filters( 'xmlrpc_login_error', $this->error, $user );
283                         return false;
284                 }
285
286                 wp_set_current_user( $user->ID );
287                 return $user;
288         }
289
290         /**
291          * Check user's credentials. Deprecated.
292          *
293          * @since 1.5.0
294          * @deprecated 2.8.0 Use wp_xmlrpc_server::login()
295          * @see wp_xmlrpc_server::login()
296          *
297          * @param string $username User's username.
298          * @param string $password User's password.
299          * @return bool Whether authentication passed.
300          */
301         public function login_pass_ok( $username, $password ) {
302                 return (bool) $this->login( $username, $password );
303         }
304
305         /**
306          * Escape string or array of strings for database.
307          *
308          * @since 1.5.2
309          *
310          * @param string|array $data Escape single string or array of strings.
311          * @return string|void Returns with string is passed, alters by-reference
312          *                     when array is passed.
313          */
314         public function escape( &$data ) {
315                 if ( ! is_array( $data ) )
316                         return wp_slash( $data );
317
318                 foreach ( $data as &$v ) {
319                         if ( is_array( $v ) )
320                                 $this->escape( $v );
321                         elseif ( ! is_object( $v ) )
322                                 $v = wp_slash( $v );
323                 }
324         }
325
326         /**
327          * Retrieve custom fields for post.
328          *
329          * @since 2.5.0
330          *
331          * @param int $post_id Post ID.
332          * @return array Custom fields, if exist.
333          */
334         public function get_custom_fields($post_id) {
335                 $post_id = (int) $post_id;
336
337                 $custom_fields = array();
338
339                 foreach ( (array) has_meta($post_id) as $meta ) {
340                         // Don't expose protected fields.
341                         if ( ! current_user_can( 'edit_post_meta', $post_id , $meta['meta_key'] ) )
342                                 continue;
343
344                         $custom_fields[] = array(
345                                 "id"    => $meta['meta_id'],
346                                 "key"   => $meta['meta_key'],
347                                 "value" => $meta['meta_value']
348                         );
349                 }
350
351                 return $custom_fields;
352         }
353
354         /**
355          * Set custom fields for post.
356          *
357          * @since 2.5.0
358          *
359          * @param int $post_id Post ID.
360          * @param array $fields Custom fields.
361          */
362         public function set_custom_fields($post_id, $fields) {
363                 $post_id = (int) $post_id;
364
365                 foreach ( (array) $fields as $meta ) {
366                         if ( isset($meta['id']) ) {
367                                 $meta['id'] = (int) $meta['id'];
368                                 $pmeta = get_metadata_by_mid( 'post', $meta['id'] );
369                                 if ( isset($meta['key']) ) {
370                                         $meta['key'] = wp_unslash( $meta['key'] );
371                                         if ( $meta['key'] !== $pmeta->meta_key )
372                                                 continue;
373                                         $meta['value'] = wp_unslash( $meta['value'] );
374                                         if ( current_user_can( 'edit_post_meta', $post_id, $meta['key'] ) )
375                                                 update_metadata_by_mid( 'post', $meta['id'], $meta['value'] );
376                                 } elseif ( current_user_can( 'delete_post_meta', $post_id, $pmeta->meta_key ) ) {
377                                         delete_metadata_by_mid( 'post', $meta['id'] );
378                                 }
379                         } elseif ( current_user_can( 'add_post_meta', $post_id, wp_unslash( $meta['key'] ) ) ) {
380                                 add_post_meta( $post_id, $meta['key'], $meta['value'] );
381                         }
382                 }
383         }
384
385         /**
386          * Set up blog options property.
387          *
388          * Passes property through {@see 'xmlrpc_blog_options'} filter.
389          *
390          * @since 2.6.0
391          *
392          * @global string $wp_version
393          */
394         public function initialise_blog_option_info() {
395                 global $wp_version;
396
397                 $this->blog_options = array(
398                         // Read only options
399                         'software_name'     => array(
400                                 'desc'          => __( 'Software Name' ),
401                                 'readonly'      => true,
402                                 'value'         => 'WordPress'
403                         ),
404                         'software_version'  => array(
405                                 'desc'          => __( 'Software Version' ),
406                                 'readonly'      => true,
407                                 'value'         => $wp_version
408                         ),
409                         'blog_url'          => array(
410                                 'desc'          => __( 'WordPress Address (URL)' ),
411                                 'readonly'      => true,
412                                 'option'        => 'siteurl'
413                         ),
414                         'home_url'          => array(
415                                 'desc'          => __( 'Site Address (URL)' ),
416                                 'readonly'      => true,
417                                 'option'        => 'home'
418                         ),
419                         'login_url'          => array(
420                                 'desc'          => __( 'Login Address (URL)' ),
421                                 'readonly'      => true,
422                                 'value'         => wp_login_url( )
423                         ),
424                         'admin_url'          => array(
425                                 'desc'          => __( 'The URL to the admin area' ),
426                                 'readonly'      => true,
427                                 'value'         => get_admin_url( )
428                         ),
429                         'image_default_link_type' => array(
430                                 'desc'          => __( 'Image default link type' ),
431                                 'readonly'      => true,
432                                 'option'        => 'image_default_link_type'
433                         ),
434                         'image_default_size' => array(
435                                 'desc'          => __( 'Image default size' ),
436                                 'readonly'      => true,
437                                 'option'        => 'image_default_size'
438                         ),
439                         'image_default_align' => array(
440                                 'desc'          => __( 'Image default align' ),
441                                 'readonly'      => true,
442                                 'option'        => 'image_default_align'
443                         ),
444                         'template'          => array(
445                                 'desc'          => __( 'Template' ),
446                                 'readonly'      => true,
447                                 'option'        => 'template'
448                         ),
449                         'stylesheet'        => array(
450                                 'desc'          => __( 'Stylesheet' ),
451                                 'readonly'      => true,
452                                 'option'        => 'stylesheet'
453                         ),
454                         'post_thumbnail'    => array(
455                                 'desc'          => __('Post Thumbnail'),
456                                 'readonly'      => true,
457                                 'value'         => current_theme_supports( 'post-thumbnails' )
458                         ),
459
460                         // Updatable options
461                         'time_zone'         => array(
462                                 'desc'          => __( 'Time Zone' ),
463                                 'readonly'      => false,
464                                 'option'        => 'gmt_offset'
465                         ),
466                         'blog_title'        => array(
467                                 'desc'          => __( 'Site Title' ),
468                                 'readonly'      => false,
469                                 'option'        => 'blogname'
470                         ),
471                         'blog_tagline'      => array(
472                                 'desc'          => __( 'Site Tagline' ),
473                                 'readonly'      => false,
474                                 'option'        => 'blogdescription'
475                         ),
476                         'date_format'       => array(
477                                 'desc'          => __( 'Date Format' ),
478                                 'readonly'      => false,
479                                 'option'        => 'date_format'
480                         ),
481                         'time_format'       => array(
482                                 'desc'          => __( 'Time Format' ),
483                                 'readonly'      => false,
484                                 'option'        => 'time_format'
485                         ),
486                         'users_can_register' => array(
487                                 'desc'          => __( 'Allow new users to sign up' ),
488                                 'readonly'      => false,
489                                 'option'        => 'users_can_register'
490                         ),
491                         'thumbnail_size_w'  => array(
492                                 'desc'          => __( 'Thumbnail Width' ),
493                                 'readonly'      => false,
494                                 'option'        => 'thumbnail_size_w'
495                         ),
496                         'thumbnail_size_h'  => array(
497                                 'desc'          => __( 'Thumbnail Height' ),
498                                 'readonly'      => false,
499                                 'option'        => 'thumbnail_size_h'
500                         ),
501                         'thumbnail_crop'    => array(
502                                 'desc'          => __( 'Crop thumbnail to exact dimensions' ),
503                                 'readonly'      => false,
504                                 'option'        => 'thumbnail_crop'
505                         ),
506                         'medium_size_w'     => array(
507                                 'desc'          => __( 'Medium size image width' ),
508                                 'readonly'      => false,
509                                 'option'        => 'medium_size_w'
510                         ),
511                         'medium_size_h'     => array(
512                                 'desc'          => __( 'Medium size image height' ),
513                                 'readonly'      => false,
514                                 'option'        => 'medium_size_h'
515                         ),
516                         'medium_large_size_w'   => array(
517                                 'desc'          => __( 'Medium-Large size image width' ),
518                                 'readonly'      => false,
519                                 'option'        => 'medium_large_size_w'
520                         ),
521                         'medium_large_size_h'   => array(
522                                 'desc'          => __( 'Medium-Large size image height' ),
523                                 'readonly'      => false,
524                                 'option'        => 'medium_large_size_h'
525                         ),
526                         'large_size_w'      => array(
527                                 'desc'          => __( 'Large size image width' ),
528                                 'readonly'      => false,
529                                 'option'        => 'large_size_w'
530                         ),
531                         'large_size_h'      => array(
532                                 'desc'          => __( 'Large size image height' ),
533                                 'readonly'      => false,
534                                 'option'        => 'large_size_h'
535                         ),
536                         'default_comment_status' => array(
537                                 'desc'          => __( 'Allow people to post comments on new articles' ),
538                                 'readonly'      => false,
539                                 'option'        => 'default_comment_status'
540                         ),
541                         'default_ping_status' => array(
542                                 'desc'          => __( 'Allow link notifications from other blogs (pingbacks and trackbacks) on new articles' ),
543                                 'readonly'      => false,
544                                 'option'        => 'default_ping_status'
545                         )
546                 );
547
548                 /**
549                  * Filter the XML-RPC blog options property.
550                  *
551                  * @since 2.6.0
552                  *
553                  * @param array $blog_options An array of XML-RPC blog options.
554                  */
555                 $this->blog_options = apply_filters( 'xmlrpc_blog_options', $this->blog_options );
556         }
557
558         /**
559          * Retrieve the blogs of the user.
560          *
561          * @since 2.6.0
562          *
563          * @param array $args {
564          *     Method arguments. Note: arguments must be ordered as documented.
565          *
566          *     @type string $username Username.
567          *     @type string $password Password.
568          * }
569          * @return array|IXR_Error Array contains:
570          *  - 'isAdmin'
571          *  - 'isPrimary' - whether the blog is the user's primary blog
572          *  - 'url'
573          *  - 'blogid'
574          *  - 'blogName'
575          *  - 'xmlrpc' - url of xmlrpc endpoint
576          */
577         public function wp_getUsersBlogs( $args ) {
578                 // If this isn't on WPMU then just use blogger_getUsersBlogs
579                 if ( !is_multisite() ) {
580                         array_unshift( $args, 1 );
581                         return $this->blogger_getUsersBlogs( $args );
582                 }
583
584                 $this->escape( $args );
585
586                 $username = $args[0];
587                 $password = $args[1];
588
589                 if ( !$user = $this->login($username, $password) )
590                         return $this->error;
591
592                 /**
593                  * Fires after the XML-RPC user has been authenticated but before the rest of
594                  * the method logic begins.
595                  *
596                  * All built-in XML-RPC methods use the action xmlrpc_call, with a parameter
597                  * equal to the method's name, e.g., wp.getUsersBlogs, wp.newPost, etc.
598                  *
599                  * @since 2.5.0
600                  *
601                  * @param string $name The method name.
602                  */
603                 do_action( 'xmlrpc_call', 'wp.getUsersBlogs' );
604
605                 $blogs = (array) get_blogs_of_user( $user->ID );
606                 $struct = array();
607                 $primary_blog_id = 0;
608                 $active_blog = get_active_blog_for_user( $user->ID );
609                 if ( $active_blog ) {
610                         $primary_blog_id = (int) $active_blog->blog_id;
611                 }
612
613                 foreach ( $blogs as $blog ) {
614                         // Don't include blogs that aren't hosted at this site.
615                         if ( $blog->site_id != get_current_site()->id )
616                                 continue;
617
618                         $blog_id = $blog->userblog_id;
619
620                         switch_to_blog( $blog_id );
621
622                         $is_admin = current_user_can( 'manage_options' );
623                         $is_primary = ( (int) $blog_id === $primary_blog_id );
624
625                         $struct[] = array(
626                                 'isAdmin'   => $is_admin,
627                                 'isPrimary' => $is_primary,
628                                 'url'       => home_url( '/' ),
629                                 'blogid'    => (string) $blog_id,
630                                 'blogName'  => get_option( 'blogname' ),
631                                 'xmlrpc'    => site_url( 'xmlrpc.php', 'rpc' ),
632                         );
633
634                         restore_current_blog();
635                 }
636
637                 return $struct;
638         }
639
640         /**
641          * Checks if the method received at least the minimum number of arguments.
642          *
643          * @since 3.4.0
644          * @access protected
645          *
646          * @param string|array $args Sanitize single string or array of strings.
647          * @param int $count         Minimum number of arguments.
648          * @return bool if `$args` contains at least $count arguments.
649          */
650         protected function minimum_args( $args, $count ) {
651                 if ( count( $args ) < $count ) {
652                         $this->error = new IXR_Error( 400, __( 'Insufficient arguments passed to this XML-RPC method.' ) );
653                         return false;
654                 }
655
656                 return true;
657         }
658
659         /**
660          * Prepares taxonomy data for return in an XML-RPC object.
661          *
662          * @access protected
663          *
664          * @param object $taxonomy The unprepared taxonomy data.
665          * @param array $fields    The subset of taxonomy fields to return.
666          * @return array The prepared taxonomy data.
667          */
668         protected function _prepare_taxonomy( $taxonomy, $fields ) {
669                 $_taxonomy = array(
670                         'name' => $taxonomy->name,
671                         'label' => $taxonomy->label,
672                         'hierarchical' => (bool) $taxonomy->hierarchical,
673                         'public' => (bool) $taxonomy->public,
674                         'show_ui' => (bool) $taxonomy->show_ui,
675                         '_builtin' => (bool) $taxonomy->_builtin,
676                 );
677
678                 if ( in_array( 'labels', $fields ) )
679                         $_taxonomy['labels'] = (array) $taxonomy->labels;
680
681                 if ( in_array( 'cap', $fields ) )
682                         $_taxonomy['cap'] = (array) $taxonomy->cap;
683
684                 if ( in_array( 'menu', $fields ) )
685                         $_taxonomy['show_in_menu'] = (bool) $_taxonomy->show_in_menu;
686
687                 if ( in_array( 'object_type', $fields ) )
688                         $_taxonomy['object_type'] = array_unique( (array) $taxonomy->object_type );
689
690                 /**
691                  * Filter XML-RPC-prepared data for the given taxonomy.
692                  *
693                  * @since 3.4.0
694                  *
695                  * @param array  $_taxonomy An array of taxonomy data.
696                  * @param object $taxonomy  Taxonomy object.
697                  * @param array  $fields    The subset of taxonomy fields to return.
698                  */
699                 return apply_filters( 'xmlrpc_prepare_taxonomy', $_taxonomy, $taxonomy, $fields );
700         }
701
702         /**
703          * Prepares term data for return in an XML-RPC object.
704          *
705          * @access protected
706          *
707          * @param array|object $term The unprepared term data.
708          * @return array The prepared term data.
709          */
710         protected function _prepare_term( $term ) {
711                 $_term = $term;
712                 if ( ! is_array( $_term ) )
713                         $_term = get_object_vars( $_term );
714
715                 // For integers which may be larger than XML-RPC supports ensure we return strings.
716                 $_term['term_id'] = strval( $_term['term_id'] );
717                 $_term['term_group'] = strval( $_term['term_group'] );
718                 $_term['term_taxonomy_id'] = strval( $_term['term_taxonomy_id'] );
719                 $_term['parent'] = strval( $_term['parent'] );
720
721                 // Count we are happy to return as an integer because people really shouldn't use terms that much.
722                 $_term['count'] = intval( $_term['count'] );
723
724                 /**
725                  * Filter XML-RPC-prepared data for the given term.
726                  *
727                  * @since 3.4.0
728                  *
729                  * @param array        $_term An array of term data.
730                  * @param array|object $term  Term object or array.
731                  */
732                 return apply_filters( 'xmlrpc_prepare_term', $_term, $term );
733         }
734
735         /**
736          * Convert a WordPress date string to an IXR_Date object.
737          *
738          * @access protected
739          *
740          * @param string $date Date string to convert.
741          * @return IXR_Date IXR_Date object.
742          */
743         protected function _convert_date( $date ) {
744                 if ( $date === '0000-00-00 00:00:00' ) {
745                         return new IXR_Date( '00000000T00:00:00Z' );
746                 }
747                 return new IXR_Date( mysql2date( 'Ymd\TH:i:s', $date, false ) );
748         }
749
750         /**
751          * Convert a WordPress GMT date string to an IXR_Date object.
752          *
753          * @access protected
754          *
755          * @param string $date_gmt WordPress GMT date string.
756          * @param string $date     Date string.
757          * @return IXR_Date IXR_Date object.
758          */
759         protected function _convert_date_gmt( $date_gmt, $date ) {
760                 if ( $date !== '0000-00-00 00:00:00' && $date_gmt === '0000-00-00 00:00:00' ) {
761                         return new IXR_Date( get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $date, false ), 'Ymd\TH:i:s' ) );
762                 }
763                 return $this->_convert_date( $date_gmt );
764         }
765
766         /**
767          * Prepares post data for return in an XML-RPC object.
768          *
769          * @access protected
770          *
771          * @param array $post   The unprepared post data.
772          * @param array $fields The subset of post type fields to return.
773          * @return array The prepared post data.
774          */
775         protected function _prepare_post( $post, $fields ) {
776                 // Holds the data for this post. built up based on $fields.
777                 $_post = array( 'post_id' => strval( $post['ID'] ) );
778
779                 // Prepare common post fields.
780                 $post_fields = array(
781                         'post_title'        => $post['post_title'],
782                         'post_date'         => $this->_convert_date( $post['post_date'] ),
783                         'post_date_gmt'     => $this->_convert_date_gmt( $post['post_date_gmt'], $post['post_date'] ),
784                         'post_modified'     => $this->_convert_date( $post['post_modified'] ),
785                         'post_modified_gmt' => $this->_convert_date_gmt( $post['post_modified_gmt'], $post['post_modified'] ),
786                         'post_status'       => $post['post_status'],
787                         'post_type'         => $post['post_type'],
788                         'post_name'         => $post['post_name'],
789                         'post_author'       => $post['post_author'],
790                         'post_password'     => $post['post_password'],
791                         'post_excerpt'      => $post['post_excerpt'],
792                         'post_content'      => $post['post_content'],
793                         'post_parent'       => strval( $post['post_parent'] ),
794                         'post_mime_type'    => $post['post_mime_type'],
795                         'link'              => get_permalink( $post['ID'] ),
796                         'guid'              => $post['guid'],
797                         'menu_order'        => intval( $post['menu_order'] ),
798                         'comment_status'    => $post['comment_status'],
799                         'ping_status'       => $post['ping_status'],
800                         'sticky'            => ( $post['post_type'] === 'post' && is_sticky( $post['ID'] ) ),
801                 );
802
803                 // Thumbnail.
804                 $post_fields['post_thumbnail'] = array();
805                 $thumbnail_id = get_post_thumbnail_id( $post['ID'] );
806                 if ( $thumbnail_id ) {
807                         $thumbnail_size = current_theme_supports('post-thumbnail') ? 'post-thumbnail' : 'thumbnail';
808                         $post_fields['post_thumbnail'] = $this->_prepare_media_item( get_post( $thumbnail_id ), $thumbnail_size );
809                 }
810
811                 // Consider future posts as published.
812                 if ( $post_fields['post_status'] === 'future' )
813                         $post_fields['post_status'] = 'publish';
814
815                 // Fill in blank post format.
816                 $post_fields['post_format'] = get_post_format( $post['ID'] );
817                 if ( empty( $post_fields['post_format'] ) )
818                         $post_fields['post_format'] = 'standard';
819
820                 // Merge requested $post_fields fields into $_post.
821                 if ( in_array( 'post', $fields ) ) {
822                         $_post = array_merge( $_post, $post_fields );
823                 } else {
824                         $requested_fields = array_intersect_key( $post_fields, array_flip( $fields ) );
825                         $_post = array_merge( $_post, $requested_fields );
826                 }
827
828                 $all_taxonomy_fields = in_array( 'taxonomies', $fields );
829
830                 if ( $all_taxonomy_fields || in_array( 'terms', $fields ) ) {
831                         $post_type_taxonomies = get_object_taxonomies( $post['post_type'], 'names' );
832                         $terms = wp_get_object_terms( $post['ID'], $post_type_taxonomies );
833                         $_post['terms'] = array();
834                         foreach ( $terms as $term ) {
835                                 $_post['terms'][] = $this->_prepare_term( $term );
836                         }
837                 }
838
839                 if ( in_array( 'custom_fields', $fields ) )
840                         $_post['custom_fields'] = $this->get_custom_fields( $post['ID'] );
841
842                 if ( in_array( 'enclosure', $fields ) ) {
843                         $_post['enclosure'] = array();
844                         $enclosures = (array) get_post_meta( $post['ID'], 'enclosure' );
845                         if ( ! empty( $enclosures ) ) {
846                                 $encdata = explode( "\n", $enclosures[0] );
847                                 $_post['enclosure']['url'] = trim( htmlspecialchars( $encdata[0] ) );
848                                 $_post['enclosure']['length'] = (int) trim( $encdata[1] );
849                                 $_post['enclosure']['type'] = trim( $encdata[2] );
850                         }
851                 }
852
853                 /**
854                  * Filter XML-RPC-prepared date for the given post.
855                  *
856                  * @since 3.4.0
857                  *
858                  * @param array $_post  An array of modified post data.
859                  * @param array $post   An array of post data.
860                  * @param array $fields An array of post fields.
861                  */
862                 return apply_filters( 'xmlrpc_prepare_post', $_post, $post, $fields );
863         }
864
865         /**
866          * Prepares post data for return in an XML-RPC object.
867          *
868          * @access protected
869          *
870          * @param object $post_type Post type object.
871          * @param array  $fields    The subset of post fields to return.
872          * @return array The prepared post type data.
873          */
874         protected function _prepare_post_type( $post_type, $fields ) {
875                 $_post_type = array(
876                         'name' => $post_type->name,
877                         'label' => $post_type->label,
878                         'hierarchical' => (bool) $post_type->hierarchical,
879                         'public' => (bool) $post_type->public,
880                         'show_ui' => (bool) $post_type->show_ui,
881                         '_builtin' => (bool) $post_type->_builtin,
882                         'has_archive' => (bool) $post_type->has_archive,
883                         'supports' => get_all_post_type_supports( $post_type->name ),
884                 );
885
886                 if ( in_array( 'labels', $fields ) ) {
887                         $_post_type['labels'] = (array) $post_type->labels;
888                 }
889
890                 if ( in_array( 'cap', $fields ) ) {
891                         $_post_type['cap'] = (array) $post_type->cap;
892                         $_post_type['map_meta_cap'] = (bool) $post_type->map_meta_cap;
893                 }
894
895                 if ( in_array( 'menu', $fields ) ) {
896                         $_post_type['menu_position'] = (int) $post_type->menu_position;
897                         $_post_type['menu_icon'] = $post_type->menu_icon;
898                         $_post_type['show_in_menu'] = (bool) $post_type->show_in_menu;
899                 }
900
901                 if ( in_array( 'taxonomies', $fields ) )
902                         $_post_type['taxonomies'] = get_object_taxonomies( $post_type->name, 'names' );
903
904                 /**
905                  * Filter XML-RPC-prepared date for the given post type.
906                  *
907                  * @since 3.4.0
908                  *
909                  * @param array  $_post_type An array of post type data.
910                  * @param object $post_type  Post type object.
911                  */
912                 return apply_filters( 'xmlrpc_prepare_post_type', $_post_type, $post_type );
913         }
914
915         /**
916          * Prepares media item data for return in an XML-RPC object.
917          *
918          * @access protected
919          *
920          * @param object $media_item     The unprepared media item data.
921          * @param string $thumbnail_size The image size to use for the thumbnail URL.
922          * @return array The prepared media item data.
923          */
924         protected function _prepare_media_item( $media_item, $thumbnail_size = 'thumbnail' ) {
925                 $_media_item = array(
926                         'attachment_id'    => strval( $media_item->ID ),
927                         'date_created_gmt' => $this->_convert_date_gmt( $media_item->post_date_gmt, $media_item->post_date ),
928                         'parent'           => $media_item->post_parent,
929                         'link'             => wp_get_attachment_url( $media_item->ID ),
930                         'title'            => $media_item->post_title,
931                         'caption'          => $media_item->post_excerpt,
932                         'description'      => $media_item->post_content,
933                         'metadata'         => wp_get_attachment_metadata( $media_item->ID ),
934                         'type'             => $media_item->post_mime_type
935                 );
936
937                 $thumbnail_src = image_downsize( $media_item->ID, $thumbnail_size );
938                 if ( $thumbnail_src )
939                         $_media_item['thumbnail'] = $thumbnail_src[0];
940                 else
941                         $_media_item['thumbnail'] = $_media_item['link'];
942
943                 /**
944                  * Filter XML-RPC-prepared data for the given media item.
945                  *
946                  * @since 3.4.0
947                  *
948                  * @param array  $_media_item    An array of media item data.
949                  * @param object $media_item     Media item object.
950                  * @param string $thumbnail_size Image size.
951                  */
952                 return apply_filters( 'xmlrpc_prepare_media_item', $_media_item, $media_item, $thumbnail_size );
953         }
954
955         /**
956          * Prepares page data for return in an XML-RPC object.
957          *
958          * @access protected
959          *
960          * @param object $page The unprepared page data.
961          * @return array The prepared page data.
962          */
963         protected function _prepare_page( $page ) {
964                 // Get all of the page content and link.
965                 $full_page = get_extended( $page->post_content );
966                 $link = get_permalink( $page->ID );
967
968                 // Get info the page parent if there is one.
969                 $parent_title = "";
970                 if ( ! empty( $page->post_parent ) ) {
971                         $parent = get_post( $page->post_parent );
972                         $parent_title = $parent->post_title;
973                 }
974
975                 // Determine comment and ping settings.
976                 $allow_comments = comments_open( $page->ID ) ? 1 : 0;
977                 $allow_pings = pings_open( $page->ID ) ? 1 : 0;
978
979                 // Format page date.
980                 $page_date = $this->_convert_date( $page->post_date );
981                 $page_date_gmt = $this->_convert_date_gmt( $page->post_date_gmt, $page->post_date );
982
983                 // Pull the categories info together.
984                 $categories = array();
985                 if ( is_object_in_taxonomy( 'page', 'category' ) ) {
986                         foreach ( wp_get_post_categories( $page->ID ) as $cat_id ) {
987                                 $categories[] = get_cat_name( $cat_id );
988                         }
989                 }
990
991                 // Get the author info.
992                 $author = get_userdata( $page->post_author );
993
994                 $page_template = get_page_template_slug( $page->ID );
995                 if ( empty( $page_template ) )
996                         $page_template = 'default';
997
998                 $_page = array(
999                         'dateCreated'            => $page_date,
1000                         'userid'                 => $page->post_author,
1001                         'page_id'                => $page->ID,
1002                         'page_status'            => $page->post_status,
1003                         'description'            => $full_page['main'],
1004                         'title'                  => $page->post_title,
1005                         'link'                   => $link,
1006                         'permaLink'              => $link,
1007                         'categories'             => $categories,
1008                         'excerpt'                => $page->post_excerpt,
1009                         'text_more'              => $full_page['extended'],
1010                         'mt_allow_comments'      => $allow_comments,
1011                         'mt_allow_pings'         => $allow_pings,
1012                         'wp_slug'                => $page->post_name,
1013                         'wp_password'            => $page->post_password,
1014                         'wp_author'              => $author->display_name,
1015                         'wp_page_parent_id'      => $page->post_parent,
1016                         'wp_page_parent_title'   => $parent_title,
1017                         'wp_page_order'          => $page->menu_order,
1018                         'wp_author_id'           => (string) $author->ID,
1019                         'wp_author_display_name' => $author->display_name,
1020                         'date_created_gmt'       => $page_date_gmt,
1021                         'custom_fields'          => $this->get_custom_fields( $page->ID ),
1022                         'wp_page_template'       => $page_template
1023                 );
1024
1025                 /**
1026                  * Filter XML-RPC-prepared data for the given page.
1027                  *
1028                  * @since 3.4.0
1029                  *
1030                  * @param array   $_page An array of page data.
1031                  * @param WP_Post $page  Page object.
1032                  */
1033                 return apply_filters( 'xmlrpc_prepare_page', $_page, $page );
1034         }
1035
1036         /**
1037          * Prepares comment data for return in an XML-RPC object.
1038          *
1039          * @access protected
1040          *
1041          * @param object $comment The unprepared comment data.
1042          * @return array The prepared comment data.
1043          */
1044         protected function _prepare_comment( $comment ) {
1045                 // Format page date.
1046                 $comment_date_gmt = $this->_convert_date_gmt( $comment->comment_date_gmt, $comment->comment_date );
1047
1048                 if ( '0' == $comment->comment_approved ) {
1049                         $comment_status = 'hold';
1050                 } elseif ( 'spam' == $comment->comment_approved ) {
1051                         $comment_status = 'spam';
1052                 } elseif ( '1' == $comment->comment_approved ) {
1053                         $comment_status = 'approve';
1054                 } else {
1055                         $comment_status = $comment->comment_approved;
1056                 }
1057                 $_comment = array(
1058                         'date_created_gmt' => $comment_date_gmt,
1059                         'user_id'          => $comment->user_id,
1060                         'comment_id'       => $comment->comment_ID,
1061                         'parent'           => $comment->comment_parent,
1062                         'status'           => $comment_status,
1063                         'content'          => $comment->comment_content,
1064                         'link'             => get_comment_link($comment),
1065                         'post_id'          => $comment->comment_post_ID,
1066                         'post_title'       => get_the_title($comment->comment_post_ID),
1067                         'author'           => $comment->comment_author,
1068                         'author_url'       => $comment->comment_author_url,
1069                         'author_email'     => $comment->comment_author_email,
1070                         'author_ip'        => $comment->comment_author_IP,
1071                         'type'             => $comment->comment_type,
1072                 );
1073
1074                 /**
1075                  * Filter XML-RPC-prepared data for the given comment.
1076                  *
1077                  * @since 3.4.0
1078                  *
1079                  * @param array      $_comment An array of prepared comment data.
1080                  * @param WP_Comment $comment  Comment object.
1081                  */
1082                 return apply_filters( 'xmlrpc_prepare_comment', $_comment, $comment );
1083         }
1084
1085         /**
1086          * Prepares user data for return in an XML-RPC object.
1087          *
1088          * @access protected
1089          *
1090          * @param WP_User $user   The unprepared user object.
1091          * @param array   $fields The subset of user fields to return.
1092          * @return array The prepared user data.
1093          */
1094         protected function _prepare_user( $user, $fields ) {
1095                 $_user = array( 'user_id' => strval( $user->ID ) );
1096
1097                 $user_fields = array(
1098                         'username'          => $user->user_login,
1099                         'first_name'        => $user->user_firstname,
1100                         'last_name'         => $user->user_lastname,
1101                         'registered'        => $this->_convert_date( $user->user_registered ),
1102                         'bio'               => $user->user_description,
1103                         'email'             => $user->user_email,
1104                         'nickname'          => $user->nickname,
1105                         'nicename'          => $user->user_nicename,
1106                         'url'               => $user->user_url,
1107                         'display_name'      => $user->display_name,
1108                         'roles'             => $user->roles,
1109                 );
1110
1111                 if ( in_array( 'all', $fields ) ) {
1112                         $_user = array_merge( $_user, $user_fields );
1113                 } else {
1114                         if ( in_array( 'basic', $fields ) ) {
1115                                 $basic_fields = array( 'username', 'email', 'registered', 'display_name', 'nicename' );
1116                                 $fields = array_merge( $fields, $basic_fields );
1117                         }
1118                         $requested_fields = array_intersect_key( $user_fields, array_flip( $fields ) );
1119                         $_user = array_merge( $_user, $requested_fields );
1120                 }
1121
1122                 /**
1123                  * Filter XML-RPC-prepared data for the given user.
1124                  *
1125                  * @since 3.5.0
1126                  *
1127                  * @param array   $_user  An array of user data.
1128                  * @param WP_User $user   User object.
1129                  * @param array   $fields An array of user fields.
1130                  */
1131                 return apply_filters( 'xmlrpc_prepare_user', $_user, $user, $fields );
1132         }
1133
1134         /**
1135          * Create a new post for any registered post type.
1136          *
1137          * @since 3.4.0
1138          *
1139          * @link http://en.wikipedia.org/wiki/RSS_enclosure for information on RSS enclosures.
1140          *
1141          * @param array  $args {
1142          *     Method arguments. Note: top-level arguments must be ordered as documented.
1143          *
1144          *     @type int    $blog_id        Blog ID (unused).
1145          *     @type string $username       Username.
1146          *     @type string $password       Password.
1147          *     @type array  $content_struct {
1148          *         Content struct for adding a new post. See wp_insert_post() for information on
1149          *         additional post fields
1150          *
1151          *         @type string $post_type      Post type. Default 'post'.
1152          *         @type string $post_status    Post status. Default 'draft'
1153          *         @type string $post_title     Post title.
1154          *         @type int    $post_author    Post author ID.
1155          *         @type string $post_excerpt   Post excerpt.
1156          *         @type string $post_content   Post content.
1157          *         @type string $post_date_gmt  Post date in GMT.
1158          *         @type string $post_date      Post date.
1159          *         @type string $post_password  Post password (20-character limit).
1160          *         @type string $comment_status Post comment enabled status. Accepts 'open' or 'closed'.
1161          *         @type string $ping_status    Post ping status. Accepts 'open' or 'closed'.
1162          *         @type bool   $sticky         Whether the post should be sticky. Automatically false if
1163          *                                      `$post_status` is 'private'.
1164          *         @type int    $post_thumbnail ID of an image to use as the post thumbnail/featured image.
1165          *         @type array  $custom_fields  Array of meta key/value pairs to add to the post.
1166          *         @type array  $terms          Associative array with taxonomy names as keys and arrays
1167          *                                      of term IDs as values.
1168          *         @type array  $terms_names    Associative array with taxonomy names as keys and arrays
1169          *                                      of term names as values.
1170          *         @type array  $enclosure      {
1171          *             Array of feed enclosure data to add to post meta.
1172          *
1173          *             @type string $url    URL for the feed enclosure.
1174          *             @type int    $length Size in bytes of the enclosure.
1175          *             @type string $type   Mime-type for the enclosure.
1176          *         }
1177          *     }
1178          * }
1179          * @return int|IXR_Error Post ID on success, IXR_Error instance otherwise.
1180          */
1181         public function wp_newPost( $args ) {
1182                 if ( ! $this->minimum_args( $args, 4 ) )
1183                         return $this->error;
1184
1185                 $this->escape( $args );
1186
1187                 $username       = $args[1];
1188                 $password       = $args[2];
1189                 $content_struct = $args[3];
1190
1191                 if ( ! $user = $this->login( $username, $password ) )
1192                         return $this->error;
1193
1194                 // convert the date field back to IXR form
1195                 if ( isset( $content_struct['post_date'] ) && ! ( $content_struct['post_date'] instanceof IXR_Date ) ) {
1196                         $content_struct['post_date'] = $this->_convert_date( $content_struct['post_date'] );
1197                 }
1198
1199                 // ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct,
1200                 // since _insert_post will ignore the non-GMT date if the GMT date is set
1201                 if ( isset( $content_struct['post_date_gmt'] ) && ! ( $content_struct['post_date_gmt'] instanceof IXR_Date ) ) {
1202                         if ( $content_struct['post_date_gmt'] == '0000-00-00 00:00:00' || isset( $content_struct['post_date'] ) ) {
1203                                 unset( $content_struct['post_date_gmt'] );
1204                         } else {
1205                                 $content_struct['post_date_gmt'] = $this->_convert_date( $content_struct['post_date_gmt'] );
1206                         }
1207                 }
1208
1209                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1210                 do_action( 'xmlrpc_call', 'wp.newPost' );
1211
1212                 unset( $content_struct['ID'] );
1213
1214                 return $this->_insert_post( $user, $content_struct );
1215         }
1216
1217         /**
1218          * Helper method for filtering out elements from an array.
1219          *
1220          * @since 3.4.0
1221          *
1222          * @param int $count Number to compare to one.
1223          */
1224         private function _is_greater_than_one( $count ) {
1225                 return $count > 1;
1226         }
1227
1228         /**
1229          * Encapsulate the logic for sticking a post
1230          * and determining if the user has permission to do so
1231          *
1232          * @since 4.3.0
1233          * @access private
1234          *
1235          * @param array $post_data
1236          * @param bool  $update
1237          * @return void|IXR_Error
1238          */
1239         private function _toggle_sticky( $post_data, $update = false ) {
1240                 $post_type = get_post_type_object( $post_data['post_type'] );
1241
1242                 // Private and password-protected posts cannot be stickied.
1243                 if ( 'private' === $post_data['post_status'] || ! empty( $post_data['post_password'] ) ) {
1244                         // Error if the client tried to stick the post, otherwise, silently unstick.
1245                         if ( ! empty( $post_data['sticky'] ) ) {
1246                                 return new IXR_Error( 401, __( 'Sorry, you cannot stick a private post.' ) );
1247                         }
1248
1249                         if ( $update ) {
1250                                 unstick_post( $post_data['ID'] );
1251                         }
1252                 } elseif ( isset( $post_data['sticky'] ) )  {
1253                         if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
1254                                 return new IXR_Error( 401, __( 'Sorry, you are not allowed to stick this post.' ) );
1255                         }
1256
1257                         $sticky = wp_validate_boolean( $post_data['sticky'] );
1258                         if ( $sticky ) {
1259                                 stick_post( $post_data['ID'] );
1260                         } else {
1261                                 unstick_post( $post_data['ID'] );
1262                         }
1263                 }
1264         }
1265
1266         /**
1267          * Helper method for wp_newPost() and wp_editPost(), containing shared logic.
1268          *
1269          * @since 3.4.0
1270          * @access protected
1271          *
1272          * @see wp_insert_post()
1273          *
1274          * @param WP_User         $user           The post author if post_author isn't set in $content_struct.
1275          * @param array|IXR_Error $content_struct Post data to insert.
1276          * @return IXR_Error|string
1277          */
1278         protected function _insert_post( $user, $content_struct ) {
1279                 $defaults = array( 'post_status' => 'draft', 'post_type' => 'post', 'post_author' => 0,
1280                         'post_password' => '', 'post_excerpt' => '', 'post_content' => '', 'post_title' => '' );
1281
1282                 $post_data = wp_parse_args( $content_struct, $defaults );
1283
1284                 $post_type = get_post_type_object( $post_data['post_type'] );
1285                 if ( ! $post_type )
1286                         return new IXR_Error( 403, __( 'Invalid post type' ) );
1287
1288                 $update = ! empty( $post_data['ID'] );
1289
1290                 if ( $update ) {
1291                         if ( ! get_post( $post_data['ID'] ) )
1292                                 return new IXR_Error( 401, __( 'Invalid post ID.' ) );
1293                         if ( ! current_user_can( 'edit_post', $post_data['ID'] ) )
1294                                 return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
1295                         if ( $post_data['post_type'] != get_post_type( $post_data['ID'] ) )
1296                                 return new IXR_Error( 401, __( 'The post type may not be changed.' ) );
1297                 } else {
1298                         if ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( $post_type->cap->edit_posts ) )
1299                                 return new IXR_Error( 401, __( 'Sorry, you are not allowed to post on this site.' ) );
1300                 }
1301
1302                 switch ( $post_data['post_status'] ) {
1303                         case 'draft':
1304                         case 'pending':
1305                                 break;
1306                         case 'private':
1307                                 if ( ! current_user_can( $post_type->cap->publish_posts ) )
1308                                         return new IXR_Error( 401, __( 'Sorry, you are not allowed to create private posts in this post type' ) );
1309                                 break;
1310                         case 'publish':
1311                         case 'future':
1312                                 if ( ! current_user_can( $post_type->cap->publish_posts ) )
1313                                         return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts in this post type' ) );
1314                                 break;
1315                         default:
1316                                 if ( ! get_post_status_object( $post_data['post_status'] ) )
1317                                         $post_data['post_status'] = 'draft';
1318                         break;
1319                 }
1320
1321                 if ( ! empty( $post_data['post_password'] ) && ! current_user_can( $post_type->cap->publish_posts ) )
1322                         return new IXR_Error( 401, __( 'Sorry, you are not allowed to create password protected posts in this post type' ) );
1323
1324                 $post_data['post_author'] = absint( $post_data['post_author'] );
1325                 if ( ! empty( $post_data['post_author'] ) && $post_data['post_author'] != $user->ID ) {
1326                         if ( ! current_user_can( $post_type->cap->edit_others_posts ) )
1327                                 return new IXR_Error( 401, __( 'You are not allowed to create posts as this user.' ) );
1328
1329                         $author = get_userdata( $post_data['post_author'] );
1330
1331                         if ( ! $author )
1332                                 return new IXR_Error( 404, __( 'Invalid author ID.' ) );
1333                 } else {
1334                         $post_data['post_author'] = $user->ID;
1335                 }
1336
1337                 if ( isset( $post_data['comment_status'] ) && $post_data['comment_status'] != 'open' && $post_data['comment_status'] != 'closed' )
1338                         unset( $post_data['comment_status'] );
1339
1340                 if ( isset( $post_data['ping_status'] ) && $post_data['ping_status'] != 'open' && $post_data['ping_status'] != 'closed' )
1341                         unset( $post_data['ping_status'] );
1342
1343                 // Do some timestamp voodoo.
1344                 if ( ! empty( $post_data['post_date_gmt'] ) ) {
1345                         // We know this is supposed to be GMT, so we're going to slap that Z on there by force.
1346                         $dateCreated = rtrim( $post_data['post_date_gmt']->getIso(), 'Z' ) . 'Z';
1347                 } elseif ( ! empty( $post_data['post_date'] ) ) {
1348                         $dateCreated = $post_data['post_date']->getIso();
1349                 }
1350
1351                 if ( ! empty( $dateCreated ) ) {
1352                         $post_data['post_date'] = iso8601_to_datetime( $dateCreated );
1353                         $post_data['post_date_gmt'] = get_gmt_from_date( $post_data['post_date'] );
1354                 }
1355
1356                 if ( ! isset( $post_data['ID'] ) )
1357                         $post_data['ID'] = get_default_post_to_edit( $post_data['post_type'], true )->ID;
1358                 $post_ID = $post_data['ID'];
1359
1360                 if ( $post_data['post_type'] == 'post' ) {
1361                         $error = $this->_toggle_sticky( $post_data, $update );
1362                         if ( $error ) {
1363                                 return $error;
1364                         }
1365                 }
1366
1367                 if ( isset( $post_data['post_thumbnail'] ) ) {
1368                         // empty value deletes, non-empty value adds/updates.
1369                         if ( ! $post_data['post_thumbnail'] )
1370                                 delete_post_thumbnail( $post_ID );
1371                         elseif ( ! get_post( absint( $post_data['post_thumbnail'] ) ) )
1372                                 return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
1373                         set_post_thumbnail( $post_ID, $post_data['post_thumbnail'] );
1374                         unset( $content_struct['post_thumbnail'] );
1375                 }
1376
1377                 if ( isset( $post_data['custom_fields'] ) )
1378                         $this->set_custom_fields( $post_ID, $post_data['custom_fields'] );
1379
1380                 if ( isset( $post_data['terms'] ) || isset( $post_data['terms_names'] ) ) {
1381                         $post_type_taxonomies = get_object_taxonomies( $post_data['post_type'], 'objects' );
1382
1383                         // Accumulate term IDs from terms and terms_names.
1384                         $terms = array();
1385
1386                         // First validate the terms specified by ID.
1387                         if ( isset( $post_data['terms'] ) && is_array( $post_data['terms'] ) ) {
1388                                 $taxonomies = array_keys( $post_data['terms'] );
1389
1390                                 // Validating term ids.
1391                                 foreach ( $taxonomies as $taxonomy ) {
1392                                         if ( ! array_key_exists( $taxonomy , $post_type_taxonomies ) )
1393                                                 return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
1394
1395                                         if ( ! current_user_can( $post_type_taxonomies[$taxonomy]->cap->assign_terms ) )
1396                                                 return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
1397
1398                                         $term_ids = $post_data['terms'][$taxonomy];
1399                                         $terms[ $taxonomy ] = array();
1400                                         foreach ( $term_ids as $term_id ) {
1401                                                 $term = get_term_by( 'id', $term_id, $taxonomy );
1402
1403                                                 if ( ! $term )
1404                                                         return new IXR_Error( 403, __( 'Invalid term ID' ) );
1405
1406                                                 $terms[$taxonomy][] = (int) $term_id;
1407                                         }
1408                                 }
1409                         }
1410
1411                         // Now validate terms specified by name.
1412                         if ( isset( $post_data['terms_names'] ) && is_array( $post_data['terms_names'] ) ) {
1413                                 $taxonomies = array_keys( $post_data['terms_names'] );
1414
1415                                 foreach ( $taxonomies as $taxonomy ) {
1416                                         if ( ! array_key_exists( $taxonomy , $post_type_taxonomies ) )
1417                                                 return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
1418
1419                                         if ( ! current_user_can( $post_type_taxonomies[$taxonomy]->cap->assign_terms ) )
1420                                                 return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
1421
1422                                         /*
1423                                          * For hierarchical taxonomies, we can't assign a term when multiple terms
1424                                          * in the hierarchy share the same name.
1425                                          */
1426                                         $ambiguous_terms = array();
1427                                         if ( is_taxonomy_hierarchical( $taxonomy ) ) {
1428                                                 $tax_term_names = get_terms( $taxonomy, array( 'fields' => 'names', 'hide_empty' => false ) );
1429
1430                                                 // Count the number of terms with the same name.
1431                                                 $tax_term_names_count = array_count_values( $tax_term_names );
1432
1433                                                 // Filter out non-ambiguous term names.
1434                                                 $ambiguous_tax_term_counts = array_filter( $tax_term_names_count, array( $this, '_is_greater_than_one') );
1435
1436                                                 $ambiguous_terms = array_keys( $ambiguous_tax_term_counts );
1437                                         }
1438
1439                                         $term_names = $post_data['terms_names'][$taxonomy];
1440                                         foreach ( $term_names as $term_name ) {
1441                                                 if ( in_array( $term_name, $ambiguous_terms ) )
1442                                                         return new IXR_Error( 401, __( 'Ambiguous term name used in a hierarchical taxonomy. Please use term ID instead.' ) );
1443
1444                                                 $term = get_term_by( 'name', $term_name, $taxonomy );
1445
1446                                                 if ( ! $term ) {
1447                                                         // Term doesn't exist, so check that the user is allowed to create new terms.
1448                                                         if ( ! current_user_can( $post_type_taxonomies[$taxonomy]->cap->edit_terms ) )
1449                                                                 return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a term to one of the given taxonomies.' ) );
1450
1451                                                         // Create the new term.
1452                                                         $term_info = wp_insert_term( $term_name, $taxonomy );
1453                                                         if ( is_wp_error( $term_info ) )
1454                                                                 return new IXR_Error( 500, $term_info->get_error_message() );
1455
1456                                                         $terms[$taxonomy][] = (int) $term_info['term_id'];
1457                                                 } else {
1458                                                         $terms[$taxonomy][] = (int) $term->term_id;
1459                                                 }
1460                                         }
1461                                 }
1462                         }
1463
1464                         $post_data['tax_input'] = $terms;
1465                         unset( $post_data['terms'], $post_data['terms_names'] );
1466                 } else {
1467                         // Do not allow direct submission of 'tax_input', clients must use 'terms' and/or 'terms_names'.
1468                         unset( $post_data['tax_input'], $post_data['post_category'], $post_data['tags_input'] );
1469                 }
1470
1471                 if ( isset( $post_data['post_format'] ) ) {
1472                         $format = set_post_format( $post_ID, $post_data['post_format'] );
1473
1474                         if ( is_wp_error( $format ) )
1475                                 return new IXR_Error( 500, $format->get_error_message() );
1476
1477                         unset( $post_data['post_format'] );
1478                 }
1479
1480                 // Handle enclosures.
1481                 $enclosure = isset( $post_data['enclosure'] ) ? $post_data['enclosure'] : null;
1482                 $this->add_enclosure_if_new( $post_ID, $enclosure );
1483
1484                 $this->attach_uploads( $post_ID, $post_data['post_content'] );
1485
1486                 /**
1487                  * Filter post data array to be inserted via XML-RPC.
1488                  *
1489                  * @since 3.4.0
1490                  *
1491                  * @param array $post_data      Parsed array of post data.
1492                  * @param array $content_struct Post data array.
1493                  */
1494                 $post_data = apply_filters( 'xmlrpc_wp_insert_post_data', $post_data, $content_struct );
1495
1496                 $post_ID = $update ? wp_update_post( $post_data, true ) : wp_insert_post( $post_data, true );
1497                 if ( is_wp_error( $post_ID ) )
1498                         return new IXR_Error( 500, $post_ID->get_error_message() );
1499
1500                 if ( ! $post_ID )
1501                         return new IXR_Error( 401, __( 'Sorry, your entry could not be posted. Something wrong happened.' ) );
1502
1503                 return strval( $post_ID );
1504         }
1505
1506         /**
1507          * Edit a post for any registered post type.
1508          *
1509          * The $content_struct parameter only needs to contain fields that
1510          * should be changed. All other fields will retain their existing values.
1511          *
1512          * @since 3.4.0
1513          *
1514          * @param array  $args {
1515          *     Method arguments. Note: arguments must be ordered as documented.
1516          *
1517          *     @type int    $blog_id        Blog ID (unused).
1518          *     @type string $username       Username.
1519          *     @type string $password       Password.
1520          *     @type int    $post_id        Post ID.
1521          *     @type array  $content_struct Extra content arguments.
1522          * }
1523          * @return true|IXR_Error True on success, IXR_Error on failure.
1524          */
1525         public function wp_editPost( $args ) {
1526                 if ( ! $this->minimum_args( $args, 5 ) )
1527                         return $this->error;
1528
1529                 $this->escape( $args );
1530
1531                 $username       = $args[1];
1532                 $password       = $args[2];
1533                 $post_id        = (int) $args[3];
1534                 $content_struct = $args[4];
1535
1536                 if ( ! $user = $this->login( $username, $password ) )
1537                         return $this->error;
1538
1539                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1540                 do_action( 'xmlrpc_call', 'wp.editPost' );
1541
1542                 $post = get_post( $post_id, ARRAY_A );
1543
1544                 if ( empty( $post['ID'] ) )
1545                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1546
1547                 if ( isset( $content_struct['if_not_modified_since'] ) ) {
1548                         // If the post has been modified since the date provided, return an error.
1549                         if ( mysql2date( 'U', $post['post_modified_gmt'] ) > $content_struct['if_not_modified_since']->getTimestamp() ) {
1550                                 return new IXR_Error( 409, __( 'There is a revision of this post that is more recent.' ) );
1551                         }
1552                 }
1553
1554                 // Convert the date field back to IXR form.
1555                 $post['post_date'] = $this->_convert_date( $post['post_date'] );
1556
1557                 /*
1558                  * Ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct,
1559                  * since _insert_post() will ignore the non-GMT date if the GMT date is set.
1560                  */
1561                 if ( $post['post_date_gmt'] == '0000-00-00 00:00:00' || isset( $content_struct['post_date'] ) )
1562                         unset( $post['post_date_gmt'] );
1563                 else
1564                         $post['post_date_gmt'] = $this->_convert_date( $post['post_date_gmt'] );
1565
1566                 $this->escape( $post );
1567                 $merged_content_struct = array_merge( $post, $content_struct );
1568
1569                 $retval = $this->_insert_post( $user, $merged_content_struct );
1570                 if ( $retval instanceof IXR_Error )
1571                         return $retval;
1572
1573                 return true;
1574         }
1575
1576         /**
1577          * Delete a post for any registered post type.
1578          *
1579          * @since 3.4.0
1580          *
1581          * @see wp_delete_post()
1582          *
1583          * @param array  $args {
1584          *     Method arguments. Note: arguments must be ordered as documented.
1585          *
1586          *     @type int    $blog_id  Blog ID (unused).
1587          *     @type string $username Username.
1588          *     @type string $password Password.
1589          *     @type int    $post_id  Post ID.
1590          * }
1591          * @return true|IXR_Error True on success, IXR_Error instance on failure.
1592          */
1593         public function wp_deletePost( $args ) {
1594                 if ( ! $this->minimum_args( $args, 4 ) )
1595                         return $this->error;
1596
1597                 $this->escape( $args );
1598
1599                 $username   = $args[1];
1600                 $password   = $args[2];
1601                 $post_id    = (int) $args[3];
1602
1603                 if ( ! $user = $this->login( $username, $password ) )
1604                         return $this->error;
1605
1606                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1607                 do_action( 'xmlrpc_call', 'wp.deletePost' );
1608
1609                 $post = get_post( $post_id, ARRAY_A );
1610                 if ( empty( $post['ID'] ) ) {
1611                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1612                 }
1613
1614                 if ( ! current_user_can( 'delete_post', $post_id ) ) {
1615                         return new IXR_Error( 401, __( 'Sorry, you do not have the right to delete this post.' ) );
1616                 }
1617
1618                 $result = wp_delete_post( $post_id );
1619
1620                 if ( ! $result ) {
1621                         return new IXR_Error( 500, __( 'The post cannot be deleted.' ) );
1622                 }
1623
1624                 return true;
1625         }
1626
1627         /**
1628          * Retrieve a post.
1629          *
1630          * @since 3.4.0
1631          *
1632          * The optional $fields parameter specifies what fields will be included
1633          * in the response array. This should be a list of field names. 'post_id' will
1634          * always be included in the response regardless of the value of $fields.
1635          *
1636          * Instead of, or in addition to, individual field names, conceptual group
1637          * names can be used to specify multiple fields. The available conceptual
1638          * groups are 'post' (all basic fields), 'taxonomies', 'custom_fields',
1639          * and 'enclosure'.
1640          *
1641          * @see get_post()
1642          *
1643          * @param array $args {
1644          *     Method arguments. Note: arguments must be ordered as documented.
1645          *
1646          *     @type int    $blog_id  Blog ID (unused).
1647          *     @type string $username Username.
1648          *     @type string $password Password.
1649          *     @type int    $post_id  Post ID.
1650          *     @type array  $fields   The subset of post type fields to return.
1651          * }
1652          * @return array|IXR_Error Array contains (based on $fields parameter):
1653          *  - 'post_id'
1654          *  - 'post_title'
1655          *  - 'post_date'
1656          *  - 'post_date_gmt'
1657          *  - 'post_modified'
1658          *  - 'post_modified_gmt'
1659          *  - 'post_status'
1660          *  - 'post_type'
1661          *  - 'post_name'
1662          *  - 'post_author'
1663          *  - 'post_password'
1664          *  - 'post_excerpt'
1665          *  - 'post_content'
1666          *  - 'link'
1667          *  - 'comment_status'
1668          *  - 'ping_status'
1669          *  - 'sticky'
1670          *  - 'custom_fields'
1671          *  - 'terms'
1672          *  - 'categories'
1673          *  - 'tags'
1674          *  - 'enclosure'
1675          */
1676         public function wp_getPost( $args ) {
1677                 if ( ! $this->minimum_args( $args, 4 ) )
1678                         return $this->error;
1679
1680                 $this->escape( $args );
1681
1682                 $username = $args[1];
1683                 $password = $args[2];
1684                 $post_id  = (int) $args[3];
1685
1686                 if ( isset( $args[4] ) ) {
1687                         $fields = $args[4];
1688                 } else {
1689                         /**
1690                          * Filter the list of post query fields used by the given XML-RPC method.
1691                          *
1692                          * @since 3.4.0
1693                          *
1694                          * @param array  $fields Array of post fields. Default array contains 'post', 'terms', and 'custom_fields'.
1695                          * @param string $method Method name.
1696                          */
1697                         $fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPost' );
1698                 }
1699
1700                 if ( ! $user = $this->login( $username, $password ) )
1701                         return $this->error;
1702
1703                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1704                 do_action( 'xmlrpc_call', 'wp.getPost' );
1705
1706                 $post = get_post( $post_id, ARRAY_A );
1707
1708                 if ( empty( $post['ID'] ) )
1709                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1710
1711                 if ( ! current_user_can( 'edit_post', $post_id ) )
1712                         return new IXR_Error( 401, __( 'Sorry, you cannot edit this post.' ) );
1713
1714                 return $this->_prepare_post( $post, $fields );
1715         }
1716
1717         /**
1718          * Retrieve posts.
1719          *
1720          * @since 3.4.0
1721          *
1722          * @see wp_get_recent_posts()
1723          * @see wp_getPost() for more on `$fields`
1724          * @see get_posts() for more on `$filter` values
1725          *
1726          * @param array $args {
1727          *     Method arguments. Note: arguments must be ordered as documented.
1728          *
1729          *     @type int    $blog_id  Blog ID (unused).
1730          *     @type string $username Username.
1731          *     @type string $password Password.
1732          *     @type array  $filter   Optional. Modifies the query used to retrieve posts. Accepts 'post_type',
1733          *                            'post_status', 'number', 'offset', 'orderby', 's', and 'order'.
1734          *                            Default empty array.
1735          *     @type array  $fields   Optional. The subset of post type fields to return in the response array.
1736          * }
1737          * @return array|IXR_Error Array contains a collection of posts.
1738          */
1739         public function wp_getPosts( $args ) {
1740                 if ( ! $this->minimum_args( $args, 3 ) )
1741                         return $this->error;
1742
1743                 $this->escape( $args );
1744
1745                 $username = $args[1];
1746                 $password = $args[2];
1747                 $filter   = isset( $args[3] ) ? $args[3] : array();
1748
1749                 if ( isset( $args[4] ) ) {
1750                         $fields = $args[4];
1751                 } else {
1752                         /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1753                         $fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPosts' );
1754                 }
1755
1756                 if ( ! $user = $this->login( $username, $password ) )
1757                         return $this->error;
1758
1759                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1760                 do_action( 'xmlrpc_call', 'wp.getPosts' );
1761
1762                 $query = array();
1763
1764                 if ( isset( $filter['post_type'] ) ) {
1765                         $post_type = get_post_type_object( $filter['post_type'] );
1766                         if ( ! ( (bool) $post_type ) )
1767                                 return new IXR_Error( 403, __( 'The post type specified is not valid' ) );
1768                 } else {
1769                         $post_type = get_post_type_object( 'post' );
1770                 }
1771
1772                 if ( ! current_user_can( $post_type->cap->edit_posts ) )
1773                         return new IXR_Error( 401, __( 'You are not allowed to edit posts in this post type.' ));
1774
1775                 $query['post_type'] = $post_type->name;
1776
1777                 if ( isset( $filter['post_status'] ) )
1778                         $query['post_status'] = $filter['post_status'];
1779
1780                 if ( isset( $filter['number'] ) )
1781                         $query['numberposts'] = absint( $filter['number'] );
1782
1783                 if ( isset( $filter['offset'] ) )
1784                         $query['offset'] = absint( $filter['offset'] );
1785
1786                 if ( isset( $filter['orderby'] ) ) {
1787                         $query['orderby'] = $filter['orderby'];
1788
1789                         if ( isset( $filter['order'] ) )
1790                                 $query['order'] = $filter['order'];
1791                 }
1792
1793                 if ( isset( $filter['s'] ) ) {
1794                         $query['s'] = $filter['s'];
1795                 }
1796
1797                 $posts_list = wp_get_recent_posts( $query );
1798
1799                 if ( ! $posts_list )
1800                         return array();
1801
1802                 // Holds all the posts data.
1803                 $struct = array();
1804
1805                 foreach ( $posts_list as $post ) {
1806                         if ( ! current_user_can( 'edit_post', $post['ID'] ) )
1807                                 continue;
1808
1809                         $struct[] = $this->_prepare_post( $post, $fields );
1810                 }
1811
1812                 return $struct;
1813         }
1814
1815         /**
1816          * Create a new term.
1817          *
1818          * @since 3.4.0
1819          *
1820          * @see wp_insert_term()
1821          *
1822          * @param array $args {
1823          *     Method arguments. Note: arguments must be ordered as documented.
1824          *
1825          *     @type int    $blog_id        Blog ID (unused).
1826          *     @type string $username       Username.
1827          *     @type string $password       Password.
1828          *     @type array  $content_struct Content struct for adding a new term. The struct must contain
1829          *                                  the term 'name' and 'taxonomy'. Optional accepted values include
1830          *                                  'parent', 'description', and 'slug'.
1831          * }
1832          * @return int|IXR_Error The term ID on success, or an IXR_Error object on failure.
1833          */
1834         public function wp_newTerm( $args ) {
1835                 if ( ! $this->minimum_args( $args, 4 ) )
1836                         return $this->error;
1837
1838                 $this->escape( $args );
1839
1840                 $username       = $args[1];
1841                 $password       = $args[2];
1842                 $content_struct = $args[3];
1843
1844                 if ( ! $user = $this->login( $username, $password ) )
1845                         return $this->error;
1846
1847                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1848                 do_action( 'xmlrpc_call', 'wp.newTerm' );
1849
1850                 if ( ! taxonomy_exists( $content_struct['taxonomy'] ) )
1851                         return new IXR_Error( 403, __( 'Invalid taxonomy' ) );
1852
1853                 $taxonomy = get_taxonomy( $content_struct['taxonomy'] );
1854
1855                 if ( ! current_user_can( $taxonomy->cap->manage_terms ) )
1856                         return new IXR_Error( 401, __( 'You are not allowed to create terms in this taxonomy.' ) );
1857
1858                 $taxonomy = (array) $taxonomy;
1859
1860                 // hold the data of the term
1861                 $term_data = array();
1862
1863                 $term_data['name'] = trim( $content_struct['name'] );
1864                 if ( empty( $term_data['name'] ) )
1865                         return new IXR_Error( 403, __( 'The term name cannot be empty.' ) );
1866
1867                 if ( isset( $content_struct['parent'] ) ) {
1868                         if ( ! $taxonomy['hierarchical'] )
1869                                 return new IXR_Error( 403, __( 'This taxonomy is not hierarchical.' ) );
1870
1871                         $parent_term_id = (int) $content_struct['parent'];
1872                         $parent_term = get_term( $parent_term_id , $taxonomy['name'] );
1873
1874                         if ( is_wp_error( $parent_term ) )
1875                                 return new IXR_Error( 500, $parent_term->get_error_message() );
1876
1877                         if ( ! $parent_term )
1878                                 return new IXR_Error( 403, __( 'Parent term does not exist.' ) );
1879
1880                         $term_data['parent'] = $content_struct['parent'];
1881                 }
1882
1883                 if ( isset( $content_struct['description'] ) )
1884                         $term_data['description'] = $content_struct['description'];
1885
1886                 if ( isset( $content_struct['slug'] ) )
1887                         $term_data['slug'] = $content_struct['slug'];
1888
1889                 $term = wp_insert_term( $term_data['name'] , $taxonomy['name'] , $term_data );
1890
1891                 if ( is_wp_error( $term ) )
1892                         return new IXR_Error( 500, $term->get_error_message() );
1893
1894                 if ( ! $term )
1895                         return new IXR_Error( 500, __( 'Sorry, your term could not be created. Something wrong happened.' ) );
1896
1897                 return strval( $term['term_id'] );
1898         }
1899
1900         /**
1901          * Edit a term.
1902          *
1903          * @since 3.4.0
1904          *
1905          * @see wp_update_term()
1906          *
1907          * @param array $args {
1908          *     Method arguments. Note: arguments must be ordered as documented.
1909          *
1910          *     @type int    $blog_id        Blog ID (unused).
1911          *     @type string $username       Username.
1912          *     @type string $password       Password.
1913          *     @type int    $term_id        Term ID.
1914          *     @type array  $content_struct Content struct for editing a term. The struct must contain the
1915          *                                  term ''taxonomy'. Optional accepted values include 'name', 'parent',
1916          *                                  'description', and 'slug'.
1917          * }
1918          * @return true|IXR_Error True on success, IXR_Error instance on failure.
1919          */
1920         public function wp_editTerm( $args ) {
1921                 if ( ! $this->minimum_args( $args, 5 ) )
1922                         return $this->error;
1923
1924                 $this->escape( $args );
1925
1926                 $username       = $args[1];
1927                 $password       = $args[2];
1928                 $term_id        = (int) $args[3];
1929                 $content_struct = $args[4];
1930
1931                 if ( ! $user = $this->login( $username, $password ) )
1932                         return $this->error;
1933
1934                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1935                 do_action( 'xmlrpc_call', 'wp.editTerm' );
1936
1937                 if ( ! taxonomy_exists( $content_struct['taxonomy'] ) )
1938                         return new IXR_Error( 403, __( 'Invalid taxonomy' ) );
1939
1940                 $taxonomy = get_taxonomy( $content_struct['taxonomy'] );
1941
1942                 if ( ! current_user_can( $taxonomy->cap->edit_terms ) )
1943                         return new IXR_Error( 401, __( 'You are not allowed to edit terms in this taxonomy.' ) );
1944
1945                 $taxonomy = (array) $taxonomy;
1946
1947                 // hold the data of the term
1948                 $term_data = array();
1949
1950                 $term = get_term( $term_id , $content_struct['taxonomy'] );
1951
1952                 if ( is_wp_error( $term ) )
1953                         return new IXR_Error( 500, $term->get_error_message() );
1954
1955                 if ( ! $term )
1956                         return new IXR_Error( 404, __( 'Invalid term ID' ) );
1957
1958                 if ( isset( $content_struct['name'] ) ) {
1959                         $term_data['name'] = trim( $content_struct['name'] );
1960
1961                         if ( empty( $term_data['name'] ) )
1962                                 return new IXR_Error( 403, __( 'The term name cannot be empty.' ) );
1963                 }
1964
1965                 if ( ! empty( $content_struct['parent'] ) ) {
1966                         if ( ! $taxonomy['hierarchical'] )
1967                                 return new IXR_Error( 403, __( "This taxonomy is not hierarchical so you can't set a parent." ) );
1968
1969                         $parent_term_id = (int) $content_struct['parent'];
1970                         $parent_term = get_term( $parent_term_id , $taxonomy['name'] );
1971
1972                         if ( is_wp_error( $parent_term ) )
1973                                 return new IXR_Error( 500, $parent_term->get_error_message() );
1974
1975                         if ( ! $parent_term )
1976                                 return new IXR_Error( 403, __( 'Parent term does not exist.' ) );
1977
1978                         $term_data['parent'] = $content_struct['parent'];
1979                 }
1980
1981                 if ( isset( $content_struct['description'] ) )
1982                         $term_data['description'] = $content_struct['description'];
1983
1984                 if ( isset( $content_struct['slug'] ) )
1985                         $term_data['slug'] = $content_struct['slug'];
1986
1987                 $term = wp_update_term( $term_id , $taxonomy['name'] , $term_data );
1988
1989                 if ( is_wp_error( $term ) )
1990                         return new IXR_Error( 500, $term->get_error_message() );
1991
1992                 if ( ! $term )
1993                         return new IXR_Error( 500, __( 'Sorry, editing the term failed.' ) );
1994
1995                 return true;
1996         }
1997
1998         /**
1999          * Delete a term.
2000          *
2001          * @since 3.4.0
2002          *
2003          * @see wp_delete_term()
2004          *
2005          * @param array  $args {
2006          *     Method arguments. Note: arguments must be ordered as documented.
2007          *
2008          *     @type int    $blog_id      Blog ID (unused).
2009          *     @type string $username     Username.
2010          *     @type string $password     Password.
2011          *     @type string $taxnomy_name Taxonomy name.
2012          *     @type int    $term_id      Term ID.
2013          * }
2014          * @return bool|IXR_Error True on success, IXR_Error instance on failure.
2015          */
2016         public function wp_deleteTerm( $args ) {
2017                 if ( ! $this->minimum_args( $args, 5 ) )
2018                         return $this->error;
2019
2020                 $this->escape( $args );
2021
2022                 $username           = $args[1];
2023                 $password           = $args[2];
2024                 $taxonomy           = $args[3];
2025                 $term_id            = (int) $args[4];
2026
2027                 if ( ! $user = $this->login( $username, $password ) )
2028                         return $this->error;
2029
2030                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2031                 do_action( 'xmlrpc_call', 'wp.deleteTerm' );
2032
2033                 if ( ! taxonomy_exists( $taxonomy ) )
2034                         return new IXR_Error( 403, __( 'Invalid taxonomy' ) );
2035
2036                 $taxonomy = get_taxonomy( $taxonomy );
2037
2038                 if ( ! current_user_can( $taxonomy->cap->delete_terms ) )
2039                         return new IXR_Error( 401, __( 'You are not allowed to delete terms in this taxonomy.' ) );
2040
2041                 $term = get_term( $term_id, $taxonomy->name );
2042
2043                 if ( is_wp_error( $term ) )
2044                         return new IXR_Error( 500, $term->get_error_message() );
2045
2046                 if ( ! $term )
2047                         return new IXR_Error( 404, __( 'Invalid term ID' ) );
2048
2049                 $result = wp_delete_term( $term_id, $taxonomy->name );
2050
2051                 if ( is_wp_error( $result ) )
2052                         return new IXR_Error( 500, $term->get_error_message() );
2053
2054                 if ( ! $result )
2055                         return new IXR_Error( 500, __( 'Sorry, deleting the term failed.' ) );
2056
2057                 return $result;
2058         }
2059
2060         /**
2061          * Retrieve a term.
2062          *
2063          * @since 3.4.0
2064          *
2065          * @see get_term()
2066          *
2067          * @param array  $args {
2068          *     Method arguments. Note: arguments must be ordered as documented.
2069          *
2070          *     @type int    $blog_id  Blog ID (unused).
2071          *     @type string $username Username.
2072          *     @type string $password Password.
2073          *     @type string $taxnomy  Taxonomy name.
2074          *     @type string $term_id  Term ID.
2075          * }
2076          * @return array|IXR_Error IXR_Error on failure, array on success, containing:
2077          *  - 'term_id'
2078          *  - 'name'
2079          *  - 'slug'
2080          *  - 'term_group'
2081          *  - 'term_taxonomy_id'
2082          *  - 'taxonomy'
2083          *  - 'description'
2084          *  - 'parent'
2085          *  - 'count'
2086          */
2087         public function wp_getTerm( $args ) {
2088                 if ( ! $this->minimum_args( $args, 5 ) )
2089                         return $this->error;
2090
2091                 $this->escape( $args );
2092
2093                 $username           = $args[1];
2094                 $password           = $args[2];
2095                 $taxonomy           = $args[3];
2096                 $term_id            = (int) $args[4];
2097
2098                 if ( ! $user = $this->login( $username, $password ) )
2099                         return $this->error;
2100
2101                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2102                 do_action( 'xmlrpc_call', 'wp.getTerm' );
2103
2104                 if ( ! taxonomy_exists( $taxonomy ) )
2105                         return new IXR_Error( 403, __( 'Invalid taxonomy' ) );
2106
2107                 $taxonomy = get_taxonomy( $taxonomy );
2108
2109                 if ( ! current_user_can( $taxonomy->cap->assign_terms ) )
2110                         return new IXR_Error( 401, __( 'You are not allowed to assign terms in this taxonomy.' ) );
2111
2112                 $term = get_term( $term_id , $taxonomy->name, ARRAY_A );
2113
2114                 if ( is_wp_error( $term ) )
2115                         return new IXR_Error( 500, $term->get_error_message() );
2116
2117                 if ( ! $term )
2118                         return new IXR_Error( 404, __( 'Invalid term ID' ) );
2119
2120                 return $this->_prepare_term( $term );
2121         }
2122
2123         /**
2124          * Retrieve all terms for a taxonomy.
2125          *
2126          * @since 3.4.0
2127          *
2128          * The optional $filter parameter modifies the query used to retrieve terms.
2129          * Accepted keys are 'number', 'offset', 'orderby', 'order', 'hide_empty', and 'search'.
2130          *
2131          * @see get_terms()
2132          *
2133          * @param array  $args {
2134          *     Method arguments. Note: arguments must be ordered as documented.
2135          *
2136          *     @type int    $blog_id  Blog ID (unused).
2137          *     @type string $username Username.
2138          *     @type string $password Password.
2139          *     @type string $taxnomy  Taxonomy name.
2140          *     @type array  $filter   Optional. Modifies the query used to retrieve posts. Accepts 'number',
2141          *                            'offset', 'orderby', 'order', 'hide_empty', and 'search'. Default empty array.
2142          * }
2143          * @return array|IXR_Error An associative array of terms data on success, IXR_Error instance otherwise.
2144          */
2145         public function wp_getTerms( $args ) {
2146                 if ( ! $this->minimum_args( $args, 4 ) )
2147                         return $this->error;
2148
2149                 $this->escape( $args );
2150
2151                 $username       = $args[1];
2152                 $password       = $args[2];
2153                 $taxonomy       = $args[3];
2154                 $filter         = isset( $args[4] ) ? $args[4] : array();
2155
2156                 if ( ! $user = $this->login( $username, $password ) )
2157                         return $this->error;
2158
2159                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2160                 do_action( 'xmlrpc_call', 'wp.getTerms' );
2161
2162                 if ( ! taxonomy_exists( $taxonomy ) )
2163                         return new IXR_Error( 403, __( 'Invalid taxonomy' ) );
2164
2165                 $taxonomy = get_taxonomy( $taxonomy );
2166
2167                 if ( ! current_user_can( $taxonomy->cap->assign_terms ) )
2168                         return new IXR_Error( 401, __( 'You are not allowed to assign terms in this taxonomy.' ) );
2169
2170                 $query = array();
2171
2172                 if ( isset( $filter['number'] ) )
2173                         $query['number'] = absint( $filter['number'] );
2174
2175                 if ( isset( $filter['offset'] ) )
2176                         $query['offset'] = absint( $filter['offset'] );
2177
2178                 if ( isset( $filter['orderby'] ) ) {
2179                         $query['orderby'] = $filter['orderby'];
2180
2181                         if ( isset( $filter['order'] ) )
2182                                 $query['order'] = $filter['order'];
2183                 }
2184
2185                 if ( isset( $filter['hide_empty'] ) )
2186                         $query['hide_empty'] = $filter['hide_empty'];
2187                 else
2188                         $query['get'] = 'all';
2189
2190                 if ( isset( $filter['search'] ) )
2191                         $query['search'] = $filter['search'];
2192
2193                 $terms = get_terms( $taxonomy->name, $query );
2194
2195                 if ( is_wp_error( $terms ) )
2196                         return new IXR_Error( 500, $terms->get_error_message() );
2197
2198                 $struct = array();
2199
2200                 foreach ( $terms as $term ) {
2201                         $struct[] = $this->_prepare_term( $term );
2202                 }
2203
2204                 return $struct;
2205         }
2206
2207         /**
2208          * Retrieve a taxonomy.
2209          *
2210          * @since 3.4.0
2211          *
2212          * @see get_taxonomy()
2213          *
2214          * @param array  $args {
2215          *     Method arguments. Note: arguments must be ordered as documented.
2216          *
2217          *     @type int    $blog_id  Blog ID (unused).
2218          *     @type string $username Username.
2219          *     @type string $password Password.
2220          *     @type string $taxnomy  Taxonomy name.
2221          *     @type array  $fields   Optional. Array of taxonomy fields to limit to in the return.
2222          *                            Accepts 'labels', 'cap', 'menu', and 'object_type'.
2223          *                            Default empty array.
2224          * }
2225          * @return array|IXR_Error An array of taxonomy data on success, IXR_Error instance otherwise.
2226          */
2227         public function wp_getTaxonomy( $args ) {
2228                 if ( ! $this->minimum_args( $args, 4 ) )
2229                         return $this->error;
2230
2231                 $this->escape( $args );
2232
2233                 $username = $args[1];
2234                 $password = $args[2];
2235                 $taxonomy = $args[3];
2236
2237                 if ( isset( $args[4] ) ) {
2238                         $fields = $args[4];
2239                 } else {
2240                         /**
2241                          * Filter the taxonomy query fields used by the given XML-RPC method.
2242                          *
2243                          * @since 3.4.0
2244                          *
2245                          * @param array  $fields An array of taxonomy fields to retrieve.
2246                          * @param string $method The method name.
2247                          */
2248                         $fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomy' );
2249                 }
2250
2251                 if ( ! $user = $this->login( $username, $password ) )
2252                         return $this->error;
2253
2254                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2255                 do_action( 'xmlrpc_call', 'wp.getTaxonomy' );
2256
2257                 if ( ! taxonomy_exists( $taxonomy ) )
2258                         return new IXR_Error( 403, __( 'Invalid taxonomy' ) );
2259
2260                 $taxonomy = get_taxonomy( $taxonomy );
2261
2262                 if ( ! current_user_can( $taxonomy->cap->assign_terms ) )
2263                         return new IXR_Error( 401, __( 'You are not allowed to assign terms in this taxonomy.' ) );
2264
2265                 return $this->_prepare_taxonomy( $taxonomy, $fields );
2266         }
2267
2268         /**
2269          * Retrieve all taxonomies.
2270          *
2271          * @since 3.4.0
2272          *
2273          * @see get_taxonomies()
2274          *
2275          * @param array  $args {
2276          *     Method arguments. Note: arguments must be ordered as documented.
2277          *
2278          *     @type int    $blog_id  Blog ID (unused).
2279          *     @type string $username Username.
2280          *     @type string $password Password.
2281          *     @type array  $filter   Optional. An array of arguments for retrieving taxonomies.
2282          *     @type array  $fields   Optional. The subset of taxonomy fields to return.
2283          * }
2284          * @return array|IXR_Error An associative array of taxonomy data with returned fields determined
2285          *                         by `$fields`, or an IXR_Error instance on failure.
2286          */
2287         public function wp_getTaxonomies( $args ) {
2288                 if ( ! $this->minimum_args( $args, 3 ) )
2289                         return $this->error;
2290
2291                 $this->escape( $args );
2292
2293                 $username = $args[1];
2294                 $password = $args[2];
2295                 $filter   = isset( $args[3] ) ? $args[3] : array( 'public' => true );
2296
2297                 if ( isset( $args[4] ) ) {
2298                         $fields = $args[4];
2299                 } else {
2300                         /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2301                         $fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomies' );
2302                 }
2303
2304                 if ( ! $user = $this->login( $username, $password ) )
2305                         return $this->error;
2306
2307                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2308                 do_action( 'xmlrpc_call', 'wp.getTaxonomies' );
2309
2310                 $taxonomies = get_taxonomies( $filter, 'objects' );
2311
2312                 // holds all the taxonomy data
2313                 $struct = array();
2314
2315                 foreach ( $taxonomies as $taxonomy ) {
2316                         // capability check for post_types
2317                         if ( ! current_user_can( $taxonomy->cap->assign_terms ) )
2318                                 continue;
2319
2320                         $struct[] = $this->_prepare_taxonomy( $taxonomy, $fields );
2321                 }
2322
2323                 return $struct;
2324         }
2325
2326         /**
2327          * Retrieve a user.
2328          *
2329          * The optional $fields parameter specifies what fields will be included
2330          * in the response array. This should be a list of field names. 'user_id' will
2331          * always be included in the response regardless of the value of $fields.
2332          *
2333          * Instead of, or in addition to, individual field names, conceptual group
2334          * names can be used to specify multiple fields. The available conceptual
2335          * groups are 'basic' and 'all'.
2336          *
2337          * @uses get_userdata()
2338          *
2339          * @param array  $args {
2340          *     Method arguments. Note: arguments must be ordered as documented.
2341          *
2342          *     @type int    $blog_id (unused)
2343          *     @type string $username
2344          *     @type string $password
2345          *     @type int    $user_id
2346          *     @type array  $fields (optional)
2347          * }
2348          * @return array|IXR_Error Array contains (based on $fields parameter):
2349          *  - 'user_id'
2350          *  - 'username'
2351          *  - 'first_name'
2352          *  - 'last_name'
2353          *  - 'registered'
2354          *  - 'bio'
2355          *  - 'email'
2356          *  - 'nickname'
2357          *  - 'nicename'
2358          *  - 'url'
2359          *  - 'display_name'
2360          *  - 'roles'
2361          */
2362         public function wp_getUser( $args ) {
2363                 if ( ! $this->minimum_args( $args, 4 ) )
2364                         return $this->error;
2365
2366                 $this->escape( $args );
2367
2368                 $username = $args[1];
2369                 $password = $args[2];
2370                 $user_id  = (int) $args[3];
2371
2372                 if ( isset( $args[4] ) ) {
2373                         $fields = $args[4];
2374                 } else {
2375                         /**
2376                          * Filter the default user query fields used by the given XML-RPC method.
2377                          *
2378                          * @since 3.5.0
2379                          *
2380                          * @param array  $fields User query fields for given method. Default 'all'.
2381                          * @param string $method The method name.
2382                          */
2383                         $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUser' );
2384                 }
2385
2386                 if ( ! $user = $this->login( $username, $password ) )
2387                         return $this->error;
2388
2389                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2390                 do_action( 'xmlrpc_call', 'wp.getUser' );
2391
2392                 if ( ! current_user_can( 'edit_user', $user_id ) )
2393                         return new IXR_Error( 401, __( 'Sorry, you cannot edit users.' ) );
2394
2395                 $user_data = get_userdata( $user_id );
2396
2397                 if ( ! $user_data )
2398                         return new IXR_Error( 404, __( 'Invalid user ID.' ) );
2399
2400                 return $this->_prepare_user( $user_data, $fields );
2401         }
2402
2403         /**
2404          * Retrieve users.
2405          *
2406          * The optional $filter parameter modifies the query used to retrieve users.
2407          * Accepted keys are 'number' (default: 50), 'offset' (default: 0), 'role',
2408          * 'who', 'orderby', and 'order'.
2409          *
2410          * The optional $fields parameter specifies what fields will be included
2411          * in the response array.
2412          *
2413          * @uses get_users()
2414          * @see wp_getUser() for more on $fields and return values
2415          *
2416          * @param array  $args {
2417          *     Method arguments. Note: arguments must be ordered as documented.
2418          *
2419          *     @type int    $blog_id (unused)
2420          *     @type string $username
2421          *     @type string $password
2422          *     @type array  $filter (optional)
2423          *     @type array  $fields (optional)
2424          * }
2425          * @return array|IXR_Error users data
2426          */
2427         public function wp_getUsers( $args ) {
2428                 if ( ! $this->minimum_args( $args, 3 ) )
2429                         return $this->error;
2430
2431                 $this->escape( $args );
2432
2433                 $username = $args[1];
2434                 $password = $args[2];
2435                 $filter   = isset( $args[3] ) ? $args[3] : array();
2436
2437                 if ( isset( $args[4] ) ) {
2438                         $fields = $args[4];
2439                 } else {
2440                         /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2441                         $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUsers' );
2442                 }
2443
2444                 if ( ! $user = $this->login( $username, $password ) )
2445                         return $this->error;
2446
2447                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2448                 do_action( 'xmlrpc_call', 'wp.getUsers' );
2449
2450                 if ( ! current_user_can( 'list_users' ) )
2451                         return new IXR_Error( 401, __( 'You are not allowed to browse users.' ) );
2452
2453                 $query = array( 'fields' => 'all_with_meta' );
2454
2455                 $query['number'] = ( isset( $filter['number'] ) ) ? absint( $filter['number'] ) : 50;
2456                 $query['offset'] = ( isset( $filter['offset'] ) ) ? absint( $filter['offset'] ) : 0;
2457
2458                 if ( isset( $filter['orderby'] ) ) {
2459                         $query['orderby'] = $filter['orderby'];
2460
2461                         if ( isset( $filter['order'] ) )
2462                                 $query['order'] = $filter['order'];
2463                 }
2464
2465                 if ( isset( $filter['role'] ) ) {
2466                         if ( get_role( $filter['role'] ) === null )
2467                                 return new IXR_Error( 403, __( 'The role specified is not valid' ) );
2468
2469                         $query['role'] = $filter['role'];
2470                 }
2471
2472                 if ( isset( $filter['who'] ) ) {
2473                         $query['who'] = $filter['who'];
2474                 }
2475
2476                 $users = get_users( $query );
2477
2478                 $_users = array();
2479                 foreach ( $users as $user_data ) {
2480                         if ( current_user_can( 'edit_user', $user_data->ID ) )
2481                                 $_users[] = $this->_prepare_user( $user_data, $fields );
2482                 }
2483                 return $_users;
2484         }
2485
2486         /**
2487          * Retrieve information about the requesting user.
2488          *
2489          * @uses get_userdata()
2490          *
2491          * @param array  $args {
2492          *     Method arguments. Note: arguments must be ordered as documented.
2493          *
2494          *     @type int    $blog_id (unused)
2495          *     @type string $username
2496          *     @type string $password
2497          *     @type array  $fields (optional)
2498          * }
2499          * @return array|IXR_Error (@see wp_getUser)
2500          */
2501         public function wp_getProfile( $args ) {
2502                 if ( ! $this->minimum_args( $args, 3 ) )
2503                         return $this->error;
2504
2505                 $this->escape( $args );
2506
2507                 $username = $args[1];
2508                 $password = $args[2];
2509
2510                 if ( isset( $args[3] ) ) {
2511                         $fields = $args[3];
2512                 } else {
2513                         /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2514                         $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getProfile' );
2515                 }
2516
2517                 if ( ! $user = $this->login( $username, $password ) )
2518                         return $this->error;
2519
2520                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2521                 do_action( 'xmlrpc_call', 'wp.getProfile' );
2522
2523                 if ( ! current_user_can( 'edit_user', $user->ID ) )
2524                         return new IXR_Error( 401, __( 'Sorry, you cannot edit your profile.' ) );
2525
2526                 $user_data = get_userdata( $user->ID );
2527
2528                 return $this->_prepare_user( $user_data, $fields );
2529         }
2530
2531         /**
2532          * Edit user's profile.
2533          *
2534          * @uses wp_update_user()
2535          *
2536          * @param array  $args {
2537          *     Method arguments. Note: arguments must be ordered as documented.
2538          *
2539          *     @type int    $blog_id (unused)
2540          *     @type string $username
2541          *     @type string $password
2542          *     @type array  $content_struct It can optionally contain:
2543          *      - 'first_name'
2544          *      - 'last_name'
2545          *      - 'website'
2546          *      - 'display_name'
2547          *      - 'nickname'
2548          *      - 'nicename'
2549          *      - 'bio'
2550          * }
2551          * @return true|IXR_Error True, on success.
2552          */
2553         public function wp_editProfile( $args ) {
2554                 if ( ! $this->minimum_args( $args, 4 ) )
2555                         return $this->error;
2556
2557                 $this->escape( $args );
2558
2559                 $username       = $args[1];
2560                 $password       = $args[2];
2561                 $content_struct = $args[3];
2562
2563                 if ( ! $user = $this->login( $username, $password ) )
2564                         return $this->error;
2565
2566                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2567                 do_action( 'xmlrpc_call', 'wp.editProfile' );
2568
2569                 if ( ! current_user_can( 'edit_user', $user->ID ) )
2570                         return new IXR_Error( 401, __( 'Sorry, you cannot edit your profile.' ) );
2571
2572                 // holds data of the user
2573                 $user_data = array();
2574                 $user_data['ID'] = $user->ID;
2575
2576                 // only set the user details if it was given
2577                 if ( isset( $content_struct['first_name'] ) )
2578                         $user_data['first_name'] = $content_struct['first_name'];
2579
2580                 if ( isset( $content_struct['last_name'] ) )
2581                         $user_data['last_name'] = $content_struct['last_name'];
2582
2583                 if ( isset( $content_struct['url'] ) )
2584                         $user_data['user_url'] = $content_struct['url'];
2585
2586                 if ( isset( $content_struct['display_name'] ) )
2587                         $user_data['display_name'] = $content_struct['display_name'];
2588
2589                 if ( isset( $content_struct['nickname'] ) )
2590                         $user_data['nickname'] = $content_struct['nickname'];
2591
2592                 if ( isset( $content_struct['nicename'] ) )
2593                         $user_data['user_nicename'] = $content_struct['nicename'];
2594
2595                 if ( isset( $content_struct['bio'] ) )
2596                         $user_data['description'] = $content_struct['bio'];
2597
2598                 $result = wp_update_user( $user_data );
2599
2600                 if ( is_wp_error( $result ) )
2601                         return new IXR_Error( 500, $result->get_error_message() );
2602
2603                 if ( ! $result )
2604                         return new IXR_Error( 500, __( 'Sorry, the user cannot be updated.' ) );
2605
2606                 return true;
2607         }
2608
2609         /**
2610          * Retrieve page.
2611          *
2612          * @since 2.2.0
2613          *
2614          * @param array  $args {
2615          *     Method arguments. Note: arguments must be ordered as documented.
2616          *
2617          *     @type int    $blog_id (unused)
2618          *     @type int    $page_id
2619          *     @type string $username
2620          *     @type string $password
2621          * }
2622          * @return array|IXR_Error
2623          */
2624         public function wp_getPage( $args ) {
2625                 $this->escape( $args );
2626
2627                 $page_id  = (int) $args[1];
2628                 $username = $args[2];
2629                 $password = $args[3];
2630
2631                 if ( !$user = $this->login($username, $password) ) {
2632                         return $this->error;
2633                 }
2634
2635                 $page = get_post($page_id);
2636                 if ( ! $page )
2637                         return new IXR_Error( 404, __( 'Invalid post ID.' ) );
2638
2639                 if ( !current_user_can( 'edit_page', $page_id ) )
2640                         return new IXR_Error( 401, __( 'Sorry, you cannot edit this page.' ) );
2641
2642                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2643                 do_action( 'xmlrpc_call', 'wp.getPage' );
2644
2645                 // If we found the page then format the data.
2646                 if ( $page->ID && ($page->post_type == 'page') ) {
2647                         return $this->_prepare_page( $page );
2648                 }
2649                 // If the page doesn't exist indicate that.
2650                 else {
2651                         return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
2652                 }
2653         }
2654
2655         /**
2656          * Retrieve Pages.
2657          *
2658          * @since 2.2.0
2659          *
2660          * @param array  $args {
2661          *     Method arguments. Note: arguments must be ordered as documented.
2662          *
2663          *     @type int    $blog_id (unused)
2664          *     @type string $username
2665          *     @type string $password
2666          *     @type int    $num_pages
2667          * }
2668          * @return array|IXR_Error
2669          */
2670         public function wp_getPages( $args ) {
2671                 $this->escape( $args );
2672
2673                 $username  = $args[1];
2674                 $password  = $args[2];
2675                 $num_pages = isset($args[3]) ? (int) $args[3] : 10;
2676
2677                 if ( !$user = $this->login($username, $password) )
2678                         return $this->error;
2679
2680                 if ( !current_user_can( 'edit_pages' ) )
2681                         return new IXR_Error( 401, __( 'Sorry, you cannot edit pages.' ) );
2682
2683                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2684                 do_action( 'xmlrpc_call', 'wp.getPages' );
2685
2686                 $pages = get_posts( array('post_type' => 'page', 'post_status' => 'any', 'numberposts' => $num_pages) );
2687                 $num_pages = count($pages);
2688
2689                 // If we have pages, put together their info.
2690                 if ( $num_pages >= 1 ) {
2691                         $pages_struct = array();
2692
2693                         foreach ($pages as $page) {
2694                                 if ( current_user_can( 'edit_page', $page->ID ) )
2695                                         $pages_struct[] = $this->_prepare_page( $page );
2696                         }
2697
2698                         return $pages_struct;
2699                 }
2700
2701                 return array();
2702         }
2703
2704         /**
2705          * Create new page.
2706          *
2707          * @since 2.2.0
2708          *
2709          * @see wp_xmlrpc_server::mw_newPost()
2710          *
2711          * @param array  $args {
2712          *     Method arguments. Note: arguments must be ordered as documented.
2713          *
2714          *     @type int    $blog_id (unused)
2715          *     @type string $username
2716          *     @type string $password
2717          *     @type array  $content_struct
2718          * }
2719          * @return int|IXR_Error
2720          */
2721         public function wp_newPage( $args ) {
2722                 // Items not escaped here will be escaped in newPost.
2723                 $username = $this->escape( $args[1] );
2724                 $password = $this->escape( $args[2] );
2725
2726                 if ( !$user = $this->login($username, $password) )
2727                         return $this->error;
2728
2729                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2730                 do_action( 'xmlrpc_call', 'wp.newPage' );
2731
2732                 // Mark this as content for a page.
2733                 $args[3]["post_type"] = 'page';
2734
2735                 // Let mw_newPost do all of the heavy lifting.
2736                 return $this->mw_newPost( $args );
2737         }
2738
2739         /**
2740          * Delete page.
2741          *
2742          * @since 2.2.0
2743          *
2744          * @param array  $args {
2745          *     Method arguments. Note: arguments must be ordered as documented.
2746          *
2747          *     @type int    $blog_id (unused)
2748          *     @type string $username
2749          *     @type string $password
2750          *     @type int    $page_id
2751          * }
2752          * @return true|IXR_Error True, if success.
2753          */
2754         public function wp_deletePage( $args ) {
2755                 $this->escape( $args );
2756
2757                 $username = $args[1];
2758                 $password = $args[2];
2759                 $page_id  = (int) $args[3];
2760
2761                 if ( !$user = $this->login($username, $password) )
2762                         return $this->error;
2763
2764                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2765                 do_action( 'xmlrpc_call', 'wp.deletePage' );
2766
2767                 // Get the current page based on the page_id and
2768                 // make sure it is a page and not a post.
2769                 $actual_page = get_post($page_id, ARRAY_A);
2770                 if ( !$actual_page || ($actual_page['post_type'] != 'page') )
2771                         return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
2772
2773                 // Make sure the user can delete pages.
2774                 if ( !current_user_can('delete_page', $page_id) )
2775                         return new IXR_Error( 401, __( 'Sorry, you do not have the right to delete this page.' ) );
2776
2777                 // Attempt to delete the page.
2778                 $result = wp_delete_post($page_id);
2779                 if ( !$result )
2780                         return new IXR_Error( 500, __( 'Failed to delete the page.' ) );
2781
2782                 /**
2783                  * Fires after a page has been successfully deleted via XML-RPC.
2784                  *
2785                  * @since 3.4.0
2786                  *
2787                  * @param int   $page_id ID of the deleted page.
2788                  * @param array $args    An array of arguments to delete the page.
2789                  */
2790                 do_action( 'xmlrpc_call_success_wp_deletePage', $page_id, $args );
2791
2792                 return true;
2793         }
2794
2795         /**
2796          * Edit page.
2797          *
2798          * @since 2.2.0
2799          *
2800          * @param array  $args {
2801          *     Method arguments. Note: arguments must be ordered as documented.
2802          *
2803          *     @type int    $blog_id (unused)
2804          *     @type int    $page_id
2805          *     @type string $username
2806          *     @type string $password
2807          *     @type string $content
2808          *     @type string $publish
2809          * }
2810          * @return array|IXR_Error
2811          */
2812         public function wp_editPage( $args ) {
2813                 // Items will be escaped in mw_editPost.
2814                 $page_id  = (int) $args[1];
2815                 $username = $args[2];
2816                 $password = $args[3];
2817                 $content  = $args[4];
2818                 $publish  = $args[5];
2819
2820                 $escaped_username = $this->escape( $username );
2821                 $escaped_password = $this->escape( $password );
2822
2823                 if ( !$user = $this->login( $escaped_username, $escaped_password ) ) {
2824                         return $this->error;
2825                 }
2826
2827                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2828                 do_action( 'xmlrpc_call', 'wp.editPage' );
2829
2830                 // Get the page data and make sure it is a page.
2831                 $actual_page = get_post($page_id, ARRAY_A);
2832                 if ( !$actual_page || ($actual_page['post_type'] != 'page') )
2833                         return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
2834
2835                 // Make sure the user is allowed to edit pages.
2836                 if ( !current_user_can('edit_page', $page_id) )
2837                         return new IXR_Error( 401, __( 'Sorry, you do not have the right to edit this page.' ) );
2838
2839                 // Mark this as content for a page.
2840                 $content['post_type'] = 'page';
2841
2842                 // Arrange args in the way mw_editPost understands.
2843                 $args = array(
2844                         $page_id,
2845                         $username,
2846                         $password,
2847                         $content,
2848                         $publish
2849                 );
2850
2851                 // Let mw_editPost do all of the heavy lifting.
2852                 return $this->mw_editPost( $args );
2853         }
2854
2855         /**
2856          * Retrieve page list.
2857          *
2858          * @since 2.2.0
2859          *
2860          * @global wpdb $wpdb WordPress database abstraction object.
2861          *
2862          * @param array  $args {
2863          *     Method arguments. Note: arguments must be ordered as documented.
2864          *
2865          *     @type int    $blog_id (unused)
2866          *     @type string $username
2867          *     @type string $password
2868          * }
2869          * @return array|IXR_Error
2870          */
2871         public function wp_getPageList( $args ) {
2872                 global $wpdb;
2873
2874                 $this->escape( $args );
2875
2876                 $username = $args[1];
2877                 $password = $args[2];
2878
2879                 if ( !$user = $this->login($username, $password) )
2880                         return $this->error;
2881
2882                 if ( !current_user_can( 'edit_pages' ) )
2883                         return new IXR_Error( 401, __( 'Sorry, you cannot edit pages.' ) );
2884
2885                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2886                 do_action( 'xmlrpc_call', 'wp.getPageList' );
2887
2888                 // Get list of pages ids and titles
2889                 $page_list = $wpdb->get_results("
2890                         SELECT ID page_id,
2891                                 post_title page_title,
2892                                 post_parent page_parent_id,
2893                                 post_date_gmt,
2894                                 post_date,
2895                                 post_status
2896                         FROM {$wpdb->posts}
2897                         WHERE post_type = 'page'
2898                         ORDER BY ID
2899                 ");
2900
2901                 // The date needs to be formatted properly.
2902                 $num_pages = count($page_list);
2903                 for ( $i = 0; $i < $num_pages; $i++ ) {
2904                         $page_list[$i]->dateCreated = $this->_convert_date(  $page_list[$i]->post_date );
2905                         $page_list[$i]->date_created_gmt = $this->_convert_date_gmt( $page_list[$i]->post_date_gmt, $page_list[$i]->post_date );
2906
2907                         unset($page_list[$i]->post_date_gmt);
2908                         unset($page_list[$i]->post_date);
2909                         unset($page_list[$i]->post_status);
2910                 }
2911
2912                 return $page_list;
2913         }
2914
2915         /**
2916          * Retrieve authors list.
2917          *
2918          * @since 2.2.0
2919          *
2920          * @param array  $args {
2921          *     Method arguments. Note: arguments must be ordered as documented.
2922          *
2923          *     @type int    $blog_id (unused)
2924          *     @type string $username
2925          *     @type string $password
2926          * }
2927          * @return array|IXR_Error
2928          */
2929         public function wp_getAuthors( $args ) {
2930                 $this->escape( $args );
2931
2932                 $username = $args[1];
2933                 $password = $args[2];
2934
2935                 if ( !$user = $this->login($username, $password) )
2936                         return $this->error;
2937
2938                 if ( !current_user_can('edit_posts') )
2939                         return new IXR_Error( 401, __( 'Sorry, you cannot edit posts on this site.' ) );
2940
2941                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2942                 do_action( 'xmlrpc_call', 'wp.getAuthors' );
2943
2944                 $authors = array();
2945                 foreach ( get_users( array( 'fields' => array('ID','user_login','display_name') ) ) as $user ) {
2946                         $authors[] = array(
2947                                 'user_id'       => $user->ID,
2948                                 'user_login'    => $user->user_login,
2949                                 'display_name'  => $user->display_name
2950                         );
2951                 }
2952
2953                 return $authors;
2954         }
2955
2956         /**
2957          * Get list of all tags
2958          *
2959          * @since 2.7.0
2960          *
2961          * @param array  $args {
2962          *     Method arguments. Note: arguments must be ordered as documented.
2963          *
2964          *     @type int    $blog_id (unused)
2965          *     @type string $username
2966          *     @type string $password
2967          * }
2968          * @return array|IXR_Error
2969          */
2970         public function wp_getTags( $args ) {
2971                 $this->escape( $args );
2972
2973                 $username = $args[1];
2974                 $password = $args[2];
2975
2976                 if ( !$user = $this->login($username, $password) )
2977                         return $this->error;
2978
2979                 if ( !current_user_can( 'edit_posts' ) )
2980                         return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view tags.' ) );
2981
2982                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2983                 do_action( 'xmlrpc_call', 'wp.getKeywords' );
2984
2985                 $tags = array();
2986
2987                 if ( $all_tags = get_tags() ) {
2988                         foreach ( (array) $all_tags as $tag ) {
2989                                 $struct = array();
2990                                 $struct['tag_id']                       = $tag->term_id;
2991                                 $struct['name']                         = $tag->name;
2992                                 $struct['count']                        = $tag->count;
2993                                 $struct['slug']                         = $tag->slug;
2994                                 $struct['html_url']                     = esc_html( get_tag_link( $tag->term_id ) );
2995                                 $struct['rss_url']                      = esc_html( get_tag_feed_link( $tag->term_id ) );
2996
2997                                 $tags[] = $struct;
2998                         }
2999                 }
3000
3001                 return $tags;
3002         }
3003
3004         /**
3005          * Create new category.
3006          *
3007          * @since 2.2.0
3008          *
3009          * @param array  $args {
3010          *     Method arguments. Note: arguments must be ordered as documented.
3011          *
3012          *     @type int    $blog_id (unused)
3013          *     @type string $username
3014          *     @type string $password
3015          *     @type array  $category
3016          * }
3017          * @return int|IXR_Error Category ID.
3018          */
3019         public function wp_newCategory( $args ) {
3020                 $this->escape( $args );
3021
3022                 $username = $args[1];
3023                 $password = $args[2];
3024                 $category = $args[3];
3025
3026                 if ( !$user = $this->login($username, $password) )
3027                         return $this->error;
3028
3029                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3030                 do_action( 'xmlrpc_call', 'wp.newCategory' );
3031
3032                 // Make sure the user is allowed to add a category.
3033                 if ( !current_user_can('manage_categories') )
3034                         return new IXR_Error(401, __('Sorry, you do not have the right to add a category.'));
3035
3036                 // If no slug was provided make it empty so that
3037                 // WordPress will generate one.
3038                 if ( empty($category['slug']) )
3039                         $category['slug'] = '';
3040
3041                 // If no parent_id was provided make it empty
3042                 // so that it will be a top level page (no parent).
3043                 if ( !isset($category['parent_id']) )
3044                         $category['parent_id'] = '';
3045
3046                 // If no description was provided make it empty.
3047                 if ( empty($category["description"]) )
3048                         $category["description"] = "";
3049
3050                 $new_category = array(
3051                         'cat_name'                              => $category['name'],
3052                         'category_nicename'             => $category['slug'],
3053                         'category_parent'               => $category['parent_id'],
3054                         'category_description'  => $category['description']
3055                 );
3056
3057                 $cat_id = wp_insert_category($new_category, true);
3058                 if ( is_wp_error( $cat_id ) ) {
3059                         if ( 'term_exists' == $cat_id->get_error_code() )
3060                                 return (int) $cat_id->get_error_data();
3061                         else
3062                                 return new IXR_Error(500, __('Sorry, the new category failed.'));
3063                 } elseif ( ! $cat_id ) {
3064                         return new IXR_Error(500, __('Sorry, the new category failed.'));
3065                 }
3066
3067                 /**
3068                  * Fires after a new category has been successfully created via XML-RPC.
3069                  *
3070                  * @since 3.4.0
3071                  *
3072                  * @param int   $cat_id ID of the new category.
3073                  * @param array $args   An array of new category arguments.
3074                  */
3075                 do_action( 'xmlrpc_call_success_wp_newCategory', $cat_id, $args );
3076
3077                 return $cat_id;
3078         }
3079
3080         /**
3081          * Remove category.
3082          *
3083          * @since 2.5.0
3084          *
3085          * @param array  $args {
3086          *     Method arguments. Note: arguments must be ordered as documented.
3087          *
3088          *     @type int    $blog_id (unused)
3089          *     @type string $username
3090          *     @type string $password
3091          *     @type int    $category_id
3092          * }
3093          * @return bool|IXR_Error See {@link wp_delete_term()} for return info.
3094          */
3095         public function wp_deleteCategory( $args ) {
3096                 $this->escape( $args );
3097
3098                 $username    = $args[1];
3099                 $password    = $args[2];
3100                 $category_id = (int) $args[3];
3101
3102                 if ( !$user = $this->login($username, $password) )
3103                         return $this->error;
3104
3105                 /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3106                 do_action( 'xmlrpc_call', 'wp.deleteCategory' );
3107
3108                 if ( !current_user_can('manage_categories') )
3109                         return new IXR_Error( 401, __( 'Sorry, you do not have the right to delete a category.' ) );
3110
3111                 $status = wp_delete_term( $category_id, 'category' );
3112
3113                 if ( true == $status ) {
3114                         /**
3115                          * Fires after a category has been successfully deleted via XML-RPC.
3116                          *
3117                          * @since 3.4.0
3118                          *
3119    &nbs