Make WordPress Core

Ticket #33425: class-wp-xmlrpc-server.2.php

File class-wp-xmlrpc-server.2.php, 194.1 KB (added by tonyrankin, 9 years ago)

Descriptions added to new fields

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