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

Erat himenaeos neque id sagittis massa. Hac suscipit pulvinar dignissim platea magnis eu. Don tellus a pharetra inceptos efficitur dui pulvinar. Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent pulvinar odio volutpat parturient. Quisque risus finibus suspendisse mus purus magnis facilisi condimentum consectetur dui. Curae elit suspendisse cursus vehicula.

Turpis taciti class non vel pretium quis pulvinar tempor lobortis nunc. Libero phasellus parturient sapien volutpat malesuada ornare. Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae. Porta est tempor ex eget feugiat vulputate ipsum. Justo nec iaculis habitant diam arcu fermentum.

We offer comprehen sive emplo ment services such as assistance wit employer compliance.Our company is your strategic HR partner as instead of HR. john smithson

Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae.

Exploring Learning Landscapes in Academic

Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent.

]]>
https://cardgames4educators.com/masters-in-english-how-english-speaker/feed/ 1