Issues (4967)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/wp-includes/class-wp-xmlrpc-server.php (67 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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::login().
18
 *
19
 * @package WordPress
20
 * @subpackage Publishing
21
 * @since 1.5.0
22
 */
23
class wp_xmlrpc_server extends IXR_Server {
24
	/**
25
	 * Methods.
26
	 *
27
	 * @access public
28
	 * @var array
29
	 */
30
	public $methods;
31
32
	/**
33
	 * Blog options.
34
	 *
35
	 * @access public
36
	 * @var array
37
	 */
38
	public $blog_options;
39
40
	/**
41
	 * IXR_Error instance.
42
	 *
43
	 * @access public
44
	 * @var IXR_Error
45
	 */
46
	public $error;
47
48
	/**
49
	 * Flags that the user authentication has failed in this instance of wp_xmlrpc_server.
50
	 *
51
	 * @access protected
52
	 * @var bool
53
	 */
54
	protected $auth_failed = false;
55
56
	/**
57
	 * Registers all of the XMLRPC methods that XMLRPC server understands.
58
	 *
59
	 * Sets up server and method property. Passes XMLRPC
60
	 * methods through the {@see 'xmlrpc_methods'} filter to allow plugins to extend
61
	 * or replace XML-RPC methods.
62
	 *
63
	 * @since 1.5.0
64
	 */
65
	public function __construct() {
66
		$this->methods = array(
67
			// WordPress API
68
			'wp.getUsersBlogs'		=> 'this:wp_getUsersBlogs',
69
			'wp.newPost'			=> 'this:wp_newPost',
70
			'wp.editPost'			=> 'this:wp_editPost',
71
			'wp.deletePost'			=> 'this:wp_deletePost',
72
			'wp.getPost'			=> 'this:wp_getPost',
73
			'wp.getPosts'			=> 'this:wp_getPosts',
74
			'wp.newTerm'			=> 'this:wp_newTerm',
75
			'wp.editTerm'			=> 'this:wp_editTerm',
76
			'wp.deleteTerm'			=> 'this:wp_deleteTerm',
77
			'wp.getTerm'			=> 'this:wp_getTerm',
78
			'wp.getTerms'			=> 'this:wp_getTerms',
79
			'wp.getTaxonomy'		=> 'this:wp_getTaxonomy',
80
			'wp.getTaxonomies'		=> 'this:wp_getTaxonomies',
81
			'wp.getUser'			=> 'this:wp_getUser',
82
			'wp.getUsers'			=> 'this:wp_getUsers',
83
			'wp.getProfile'			=> 'this:wp_getProfile',
84
			'wp.editProfile'		=> 'this:wp_editProfile',
85
			'wp.getPage'			=> 'this:wp_getPage',
86
			'wp.getPages'			=> 'this:wp_getPages',
87
			'wp.newPage'			=> 'this:wp_newPage',
88
			'wp.deletePage'			=> 'this:wp_deletePage',
89
			'wp.editPage'			=> 'this:wp_editPage',
90
			'wp.getPageList'		=> 'this:wp_getPageList',
91
			'wp.getAuthors'			=> 'this:wp_getAuthors',
92
			'wp.getCategories'		=> 'this:mw_getCategories',		// Alias
93
			'wp.getTags'			=> 'this:wp_getTags',
94
			'wp.newCategory'		=> 'this:wp_newCategory',
95
			'wp.deleteCategory'		=> 'this:wp_deleteCategory',
96
			'wp.suggestCategories'	=> 'this:wp_suggestCategories',
97
			'wp.uploadFile'			=> 'this:mw_newMediaObject',	// Alias
98
			'wp.deleteFile'			=> 'this:wp_deletePost',		// Alias
99
			'wp.getCommentCount'	=> 'this:wp_getCommentCount',
100
			'wp.getPostStatusList'	=> 'this:wp_getPostStatusList',
101
			'wp.getPageStatusList'	=> 'this:wp_getPageStatusList',
102
			'wp.getPageTemplates'	=> 'this:wp_getPageTemplates',
103
			'wp.getOptions'			=> 'this:wp_getOptions',
104
			'wp.setOptions'			=> 'this:wp_setOptions',
105
			'wp.getComment'			=> 'this:wp_getComment',
106
			'wp.getComments'		=> 'this:wp_getComments',
107
			'wp.deleteComment'		=> 'this:wp_deleteComment',
108
			'wp.editComment'		=> 'this:wp_editComment',
109
			'wp.newComment'			=> 'this:wp_newComment',
110
			'wp.getCommentStatusList' => 'this:wp_getCommentStatusList',
111
			'wp.getMediaItem'		=> 'this:wp_getMediaItem',
112
			'wp.getMediaLibrary'	=> 'this:wp_getMediaLibrary',
113
			'wp.getPostFormats'     => 'this:wp_getPostFormats',
114
			'wp.getPostType'		=> 'this:wp_getPostType',
115
			'wp.getPostTypes'		=> 'this:wp_getPostTypes',
116
			'wp.getRevisions'		=> 'this:wp_getRevisions',
117
			'wp.restoreRevision'	=> 'this:wp_restoreRevision',
118
119
			// Blogger API
120
			'blogger.getUsersBlogs' => 'this:blogger_getUsersBlogs',
121
			'blogger.getUserInfo' => 'this:blogger_getUserInfo',
122
			'blogger.getPost' => 'this:blogger_getPost',
123
			'blogger.getRecentPosts' => 'this:blogger_getRecentPosts',
124
			'blogger.newPost' => 'this:blogger_newPost',
125
			'blogger.editPost' => 'this:blogger_editPost',
126
			'blogger.deletePost' => 'this:blogger_deletePost',
127
128
			// MetaWeblog API (with MT extensions to structs)
129
			'metaWeblog.newPost' => 'this:mw_newPost',
130
			'metaWeblog.editPost' => 'this:mw_editPost',
131
			'metaWeblog.getPost' => 'this:mw_getPost',
132
			'metaWeblog.getRecentPosts' => 'this:mw_getRecentPosts',
133
			'metaWeblog.getCategories' => 'this:mw_getCategories',
134
			'metaWeblog.newMediaObject' => 'this:mw_newMediaObject',
135
136
			// MetaWeblog API aliases for Blogger API
137
			// see http://www.xmlrpc.com/stories/storyReader$2460
138
			'metaWeblog.deletePost' => 'this:blogger_deletePost',
139
			'metaWeblog.getUsersBlogs' => 'this:blogger_getUsersBlogs',
140
141
			// MovableType API
142
			'mt.getCategoryList' => 'this:mt_getCategoryList',
143
			'mt.getRecentPostTitles' => 'this:mt_getRecentPostTitles',
144
			'mt.getPostCategories' => 'this:mt_getPostCategories',
145
			'mt.setPostCategories' => 'this:mt_setPostCategories',
146
			'mt.supportedMethods' => 'this:mt_supportedMethods',
147
			'mt.supportedTextFilters' => 'this:mt_supportedTextFilters',
148
			'mt.getTrackbackPings' => 'this:mt_getTrackbackPings',
149
			'mt.publishPost' => 'this:mt_publishPost',
150
151
			// PingBack
152
			'pingback.ping' => 'this:pingback_ping',
153
			'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks',
154
155
			'demo.sayHello' => 'this:sayHello',
156
			'demo.addTwoNumbers' => 'this:addTwoNumbers'
157
		);
158
159
		$this->initialise_blog_option_info();
160
161
		/**
162
		 * Filters the methods exposed by the XML-RPC server.
163
		 *
164
		 * This filter can be used to add new methods, and remove built-in methods.
165
		 *
166
		 * @since 1.5.0
167
		 *
168
		 * @param array $methods An array of XML-RPC methods.
169
		 */
170
		$this->methods = apply_filters( 'xmlrpc_methods', $this->methods );
0 ignored issues
show
Documentation Bug introduced by
It seems like apply_filters('xmlrpc_methods', $this->methods) of type * is incompatible with the declared type array of property $methods.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
171
	}
172
173
	/**
174
	 * Make private/protected methods readable for backward compatibility.
175
	 *
176
	 * @since 4.0.0
177
	 * @access public
178
	 *
179
	 * @param callable $name      Method to call.
180
	 * @param array    $arguments Arguments to pass when calling.
181
	 * @return array|IXR_Error|false Return value of the callback, false otherwise.
182
	 */
183
	public function __call( $name, $arguments ) {
184
		if ( '_multisite_getUsersBlogs' === $name ) {
185
			return call_user_func_array( array( $this, $name ), $arguments );
186
		}
187
		return false;
188
	}
189
190
	/**
191
	 * Serves the XML-RPC request.
192
	 *
193
	 * @since 2.9.0
194
	 * @access public
195
	 */
196
	public function serve_request() {
197
		$this->IXR_Server($this->methods);
198
	}
199
200
	/**
201
	 * Test XMLRPC API by saying, "Hello!" to client.
202
	 *
203
	 * @since 1.5.0
204
	 *
205
	 * @return string Hello string response.
206
	 */
207
	public function sayHello() {
208
		return 'Hello!';
209
	}
210
211
	/**
212
	 * Test XMLRPC API by adding two numbers for client.
213
	 *
214
	 * @since 1.5.0
215
	 *
216
	 * @param array  $args {
217
	 *     Method arguments. Note: arguments must be ordered as documented.
218
	 *
219
	 *     @type int $number1 A number to add.
220
	 *     @type int $number2 A second number to add.
221
	 * }
222
	 * @return int Sum of the two given numbers.
223
	 */
224
	public function addTwoNumbers( $args ) {
225
		$number1 = $args[0];
226
		$number2 = $args[1];
227
		return $number1 + $number2;
228
	}
229
230
	/**
231
	 * Log user in.
232
	 *
233
	 * @since 2.8.0
234
	 *
235
	 * @param string $username User's username.
236
	 * @param string $password User's password.
237
	 * @return WP_User|bool WP_User object if authentication passed, false otherwise
0 ignored issues
show
Should the return type not be false|WP_User|WP_Error?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
238
	 */
239
	public function login( $username, $password ) {
240
		/*
241
		 * Respect old get_option() filters left for back-compat when the 'enable_xmlrpc'
242
		 * option was deprecated in 3.5.0. Use the 'xmlrpc_enabled' hook instead.
243
		 */
244
		$enabled = apply_filters( 'pre_option_enable_xmlrpc', false );
245
		if ( false === $enabled ) {
246
			$enabled = apply_filters( 'option_enable_xmlrpc', true );
247
		}
248
249
		/**
250
		 * Filters whether XML-RPC methods requiring authentication are enabled.
251
		 *
252
		 * Contrary to the way it's named, this filter does not control whether XML-RPC is *fully*
253
		 * enabled, rather, it only controls whether XML-RPC methods requiring authentication - such
254
		 * as for publishing purposes - are enabled.
255
		 *
256
		 * Further, the filter does not control whether pingbacks or other custom endpoints that don't
257
		 * require authentication are enabled. This behavior is expected, and due to how parity was matched
258
		 * with the `enable_xmlrpc` UI option the filter replaced when it was introduced in 3.5.
259
		 *
260
		 * To disable XML-RPC methods that require authentication, use:
261
		 *
262
		 *     add_filter( 'xmlrpc_enabled', '__return_false' );
263
		 *
264
		 * For more granular control over all XML-RPC methods and requests, see the {@see 'xmlrpc_methods'}
265
		 * and {@see 'xmlrpc_element_limit'} hooks.
266
		 *
267
		 * @since 3.5.0
268
		 *
269
		 * @param bool $enabled Whether XML-RPC is enabled. Default true.
270
		 */
271
		$enabled = apply_filters( 'xmlrpc_enabled', $enabled );
272
273 View Code Duplication
		if ( ! $enabled ) {
274
			$this->error = new IXR_Error( 405, sprintf( __( 'XML-RPC services are disabled on this site.' ) ) );
275
			return false;
276
		}
277
278
		if ( $this->auth_failed ) {
279
			$user = new WP_Error( 'login_prevented' );
280
		} else {
281
			$user = wp_authenticate( $username, $password );
282
		}
283
284
		if ( is_wp_error( $user ) ) {
285
			$this->error = new IXR_Error( 403, __( 'Incorrect username or password.' ) );
286
287
			// Flag that authentication has failed once on this wp_xmlrpc_server instance
288
			$this->auth_failed = true;
289
290
			/**
291
			 * Filters the XML-RPC user login error message.
292
			 *
293
			 * @since 3.5.0
294
			 *
295
			 * @param string  $error The XML-RPC error message.
296
			 * @param WP_User $user  WP_User object.
297
			 */
298
			$this->error = apply_filters( 'xmlrpc_login_error', $this->error, $user );
299
			return false;
300
		}
301
302
		wp_set_current_user( $user->ID );
303
		return $user;
304
	}
305
306
	/**
307
	 * Check user's credentials. Deprecated.
308
	 *
309
	 * @since 1.5.0
310
	 * @deprecated 2.8.0 Use wp_xmlrpc_server::login()
311
	 * @see wp_xmlrpc_server::login()
312
	 *
313
	 * @param string $username User's username.
314
	 * @param string $password User's password.
315
	 * @return bool Whether authentication passed.
316
	 */
317
	public function login_pass_ok( $username, $password ) {
318
		return (bool) $this->login( $username, $password );
319
	}
320
321
	/**
322
	 * Escape string or array of strings for database.
323
	 *
324
	 * @since 1.5.2
325
	 *
326
	 * @param string|array $data Escape single string or array of strings.
327
	 * @return string|void Returns with string is passed, alters by-reference
0 ignored issues
show
Should the return type not be array|string|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
328
	 *                     when array is passed.
329
	 */
330
	public function escape( &$data ) {
331
		if ( ! is_array( $data ) )
332
			return wp_slash( $data );
333
334
		foreach ( $data as &$v ) {
335
			if ( is_array( $v ) )
336
				$this->escape( $v );
337
			elseif ( ! is_object( $v ) )
338
				$v = wp_slash( $v );
339
		}
340
	}
341
342
	/**
343
	 * Retrieve custom fields for post.
344
	 *
345
	 * @since 2.5.0
346
	 *
347
	 * @param int $post_id Post ID.
348
	 * @return array Custom fields, if exist.
349
	 */
350 View Code Duplication
	public function get_custom_fields($post_id) {
351
		$post_id = (int) $post_id;
352
353
		$custom_fields = array();
354
355
		foreach ( (array) has_meta($post_id) as $meta ) {
356
			// Don't expose protected fields.
357
			if ( ! current_user_can( 'edit_post_meta', $post_id , $meta['meta_key'] ) )
358
				continue;
359
360
			$custom_fields[] = array(
361
				"id"    => $meta['meta_id'],
362
				"key"   => $meta['meta_key'],
363
				"value" => $meta['meta_value']
364
			);
365
		}
366
367
		return $custom_fields;
368
	}
369
370
	/**
371
	 * Set custom fields for post.
372
	 *
373
	 * @since 2.5.0
374
	 *
375
	 * @param int $post_id Post ID.
376
	 * @param array $fields Custom fields.
377
	 */
378
	public function set_custom_fields($post_id, $fields) {
379
		$post_id = (int) $post_id;
380
381
		foreach ( (array) $fields as $meta ) {
382
			if ( isset($meta['id']) ) {
383
				$meta['id'] = (int) $meta['id'];
384
				$pmeta = get_metadata_by_mid( 'post', $meta['id'] );
385
386
				if ( ! $pmeta || $pmeta->post_id != $post_id ) {
387
					continue;
388
				}
389
390 View Code Duplication
				if ( isset($meta['key']) ) {
391
					$meta['key'] = wp_unslash( $meta['key'] );
392
					if ( $meta['key'] !== $pmeta->meta_key )
393
						continue;
394
					$meta['value'] = wp_unslash( $meta['value'] );
395
					if ( current_user_can( 'edit_post_meta', $post_id, $meta['key'] ) )
396
						update_metadata_by_mid( 'post', $meta['id'], $meta['value'] );
0 ignored issues
show
It seems like $meta['value'] can also be of type array; however, update_metadata_by_mid() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
397
				} elseif ( current_user_can( 'delete_post_meta', $post_id, $pmeta->meta_key ) ) {
398
					delete_metadata_by_mid( 'post', $meta['id'] );
399
				}
400
			} elseif ( current_user_can( 'add_post_meta', $post_id, wp_unslash( $meta['key'] ) ) ) {
401
				add_post_meta( $post_id, $meta['key'], $meta['value'] );
402
			}
403
		}
404
	}
405
406
	/**
407
	 * Retrieve custom fields for a term.
408
	 *
409
	 * @since 4.9.0
410
	 *
411
	 * @param int $post_id Post ID.
0 ignored issues
show
There is no parameter named $post_id. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
412
	 * @return array Array of custom fields, if they exist.
413
	 */
414 View Code Duplication
	public function get_term_custom_fields( $term_id ) {
415
		$term_id = (int) $term_id;
416
417
		$custom_fields = array();
418
419
		foreach ( (array) has_term_meta( $term_id ) as $meta ) {
420
421
			if ( ! current_user_can( 'edit_term_meta', $term_id ) ) {
422
				continue;
423
			}
424
425
			$custom_fields[] = array(
426
				'id'    => $meta['meta_id'],
427
				'key'   => $meta['meta_key'],
428
				'value' => $meta['meta_value'],
429
			);
430
		}
431
432
		return $custom_fields;
433
	}
434
435
	/**
436
	 * Set custom fields for a term.
437
	 *
438
	 * @since 4.9.0
439
	 *
440
	 * @param int $post_id Post ID.
0 ignored issues
show
There is no parameter named $post_id. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
441
	 * @param array $fields Custom fields.
442
	 */
443
	public function set_term_custom_fields( $term_id, $fields ) {
444
		$term_id = (int) $term_id;
445
446
		foreach ( (array) $fields as $meta ) {
447
			if ( isset( $meta['id'] ) ) {
448
				$meta['id'] = (int) $meta['id'];
449
				$pmeta = get_metadata_by_mid( 'term', $meta['id'] );
450 View Code Duplication
				if ( isset( $meta['key'] ) ) {
451
					$meta['key'] = wp_unslash( $meta['key'] );
452
					if ( $meta['key'] !== $pmeta->meta_key ) {
453
						continue;
454
					}
455
					$meta['value'] = wp_unslash( $meta['value'] );
456
					if ( current_user_can( 'edit_term_meta', $term_id ) ) {
457
						update_metadata_by_mid( 'term', $meta['id'], $meta['value'] );
0 ignored issues
show
It seems like $meta['value'] can also be of type array; however, update_metadata_by_mid() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
458
					}
459
				} elseif ( current_user_can( 'delete_term_meta', $term_id ) ) {
460
					delete_metadata_by_mid( 'term', $meta['id'] );
461
				}
462
			} elseif ( current_user_can( 'add_term_meta', $term_id ) ) {
463
				add_term_meta( $term_id, $meta['key'], $meta['value'] );
464
			}
465
		}
466
	}
467
468
	/**
469
	 * Set up blog options property.
470
	 *
471
	 * Passes property through {@see 'xmlrpc_blog_options'} filter.
472
	 *
473
	 * @since 2.6.0
474
	 */
475
	public function initialise_blog_option_info() {
476
		$this->blog_options = array(
477
			// Read only options
478
			'software_name'     => array(
479
				'desc'          => __( 'Software Name' ),
480
				'readonly'      => true,
481
				'value'         => 'WordPress'
482
			),
483
			'software_version'  => array(
484
				'desc'          => __( 'Software Version' ),
485
				'readonly'      => true,
486
				'value'         => get_bloginfo( 'version' )
487
			),
488
			'blog_url'          => array(
489
				'desc'          => __( 'WordPress Address (URL)' ),
490
				'readonly'      => true,
491
				'option'        => 'siteurl'
492
			),
493
			'home_url'          => array(
494
				'desc'          => __( 'Site Address (URL)' ),
495
				'readonly'      => true,
496
				'option'        => 'home'
497
			),
498
			'login_url'          => array(
499
				'desc'          => __( 'Login Address (URL)' ),
500
				'readonly'      => true,
501
				'value'         => wp_login_url( )
502
			),
503
			'admin_url'          => array(
504
				'desc'          => __( 'The URL to the admin area' ),
505
				'readonly'      => true,
506
				'value'         => get_admin_url( )
507
			),
508
			'image_default_link_type' => array(
509
				'desc'          => __( 'Image default link type' ),
510
				'readonly'      => true,
511
				'option'        => 'image_default_link_type'
512
			),
513
			'image_default_size' => array(
514
				'desc'          => __( 'Image default size' ),
515
				'readonly'      => true,
516
				'option'        => 'image_default_size'
517
			),
518
			'image_default_align' => array(
519
				'desc'          => __( 'Image default align' ),
520
				'readonly'      => true,
521
				'option'        => 'image_default_align'
522
			),
523
			'template'          => array(
524
				'desc'          => __( 'Template' ),
525
				'readonly'      => true,
526
				'option'        => 'template'
527
			),
528
			'stylesheet'        => array(
529
				'desc'          => __( 'Stylesheet' ),
530
				'readonly'      => true,
531
				'option'        => 'stylesheet'
532
			),
533
			'post_thumbnail'    => array(
534
				'desc'          => __('Post Thumbnail'),
535
				'readonly'      => true,
536
				'value'         => current_theme_supports( 'post-thumbnails' )
537
			),
538
539
			// Updatable options
540
			'time_zone'         => array(
541
				'desc'          => __( 'Time Zone' ),
542
				'readonly'      => false,
543
				'option'        => 'gmt_offset'
544
			),
545
			'blog_title'        => array(
546
				'desc'          => __( 'Site Title' ),
547
				'readonly'      => false,
548
				'option'        => 'blogname'
549
			),
550
			'blog_tagline'      => array(
551
				'desc'          => __( 'Site Tagline' ),
552
				'readonly'      => false,
553
				'option'        => 'blogdescription'
554
			),
555
			'date_format'       => array(
556
				'desc'          => __( 'Date Format' ),
557
				'readonly'      => false,
558
				'option'        => 'date_format'
559
			),
560
			'time_format'       => array(
561
				'desc'          => __( 'Time Format' ),
562
				'readonly'      => false,
563
				'option'        => 'time_format'
564
			),
565
			'users_can_register' => array(
566
				'desc'          => __( 'Allow new users to sign up' ),
567
				'readonly'      => false,
568
				'option'        => 'users_can_register'
569
			),
570
			'thumbnail_size_w'  => array(
571
				'desc'          => __( 'Thumbnail Width' ),
572
				'readonly'      => false,
573
				'option'        => 'thumbnail_size_w'
574
			),
575
			'thumbnail_size_h'  => array(
576
				'desc'          => __( 'Thumbnail Height' ),
577
				'readonly'      => false,
578
				'option'        => 'thumbnail_size_h'
579
			),
580
			'thumbnail_crop'    => array(
581
				'desc'          => __( 'Crop thumbnail to exact dimensions' ),
582
				'readonly'      => false,
583
				'option'        => 'thumbnail_crop'
584
			),
585
			'medium_size_w'     => array(
586
				'desc'          => __( 'Medium size image width' ),
587
				'readonly'      => false,
588
				'option'        => 'medium_size_w'
589
			),
590
			'medium_size_h'     => array(
591
				'desc'          => __( 'Medium size image height' ),
592
				'readonly'      => false,
593
				'option'        => 'medium_size_h'
594
			),
595
			'medium_large_size_w'   => array(
596
				'desc'          => __( 'Medium-Large size image width' ),
597
				'readonly'      => false,
598
				'option'        => 'medium_large_size_w'
599
			),
600
			'medium_large_size_h'   => array(
601
				'desc'          => __( 'Medium-Large size image height' ),
602
				'readonly'      => false,
603
				'option'        => 'medium_large_size_h'
604
			),
605
			'large_size_w'      => array(
606
				'desc'          => __( 'Large size image width' ),
607
				'readonly'      => false,
608
				'option'        => 'large_size_w'
609
			),
610
			'large_size_h'      => array(
611
				'desc'          => __( 'Large size image height' ),
612
				'readonly'      => false,
613
				'option'        => 'large_size_h'
614
			),
615
			'default_comment_status' => array(
616
				'desc'          => __( 'Allow people to post comments on new articles' ),
617
				'readonly'      => false,
618
				'option'        => 'default_comment_status'
619
			),
620
			'default_ping_status' => array(
621
				'desc'          => __( 'Allow link notifications from other blogs (pingbacks and trackbacks) on new articles' ),
622
				'readonly'      => false,
623
				'option'        => 'default_ping_status'
624
			)
625
		);
626
627
		/**
628
		 * Filters the XML-RPC blog options property.
629
		 *
630
		 * @since 2.6.0
631
		 *
632
		 * @param array $blog_options An array of XML-RPC blog options.
633
		 */
634
		$this->blog_options = apply_filters( 'xmlrpc_blog_options', $this->blog_options );
0 ignored issues
show
Documentation Bug introduced by
It seems like apply_filters('xmlrpc_bl...', $this->blog_options) of type * is incompatible with the declared type array of property $blog_options.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
635
	}
636
637
	/**
638
	 * Retrieve the blogs of the user.
639
	 *
640
	 * @since 2.6.0
641
	 *
642
	 * @param array $args {
643
	 *     Method arguments. Note: arguments must be ordered as documented.
644
	 *
645
	 *     @type string $username Username.
646
	 *     @type string $password Password.
647
	 * }
648
	 * @return array|IXR_Error Array contains:
649
	 *  - 'isAdmin'
650
	 *  - 'isPrimary' - whether the blog is the user's primary blog
651
	 *  - 'url'
652
	 *  - 'blogid'
653
	 *  - 'blogName'
654
	 *  - 'xmlrpc' - url of xmlrpc endpoint
655
	 */
656
	public function wp_getUsersBlogs( $args ) {
657
		if ( ! $this->minimum_args( $args, 2 ) ) {
658
			return $this->error;
659
		}
660
661
		// If this isn't on WPMU then just use blogger_getUsersBlogs
662
		if ( !is_multisite() ) {
663
			array_unshift( $args, 1 );
664
			return $this->blogger_getUsersBlogs( $args );
665
		}
666
667
		$this->escape( $args );
668
669
		$username = $args[0];
670
		$password = $args[1];
671
672
		if ( !$user = $this->login($username, $password) )
673
			return $this->error;
674
675
		/**
676
		 * Fires after the XML-RPC user has been authenticated but before the rest of
677
		 * the method logic begins.
678
		 *
679
		 * All built-in XML-RPC methods use the action xmlrpc_call, with a parameter
680
		 * equal to the method's name, e.g., wp.getUsersBlogs, wp.newPost, etc.
681
		 *
682
		 * @since 2.5.0
683
		 *
684
		 * @param string $name The method name.
685
		 */
686
		do_action( 'xmlrpc_call', 'wp.getUsersBlogs' );
687
688
		$blogs = (array) get_blogs_of_user( $user->ID );
689
		$struct = array();
690
		$primary_blog_id = 0;
691
		$active_blog = get_active_blog_for_user( $user->ID );
692
		if ( $active_blog ) {
693
			$primary_blog_id = (int) $active_blog->blog_id;
694
		}
695
696
		foreach ( $blogs as $blog ) {
697
			// Don't include blogs that aren't hosted at this site.
698
			if ( $blog->site_id != get_current_network_id() )
699
				continue;
700
701
			$blog_id = $blog->userblog_id;
702
703
			switch_to_blog( $blog_id );
704
705
			$is_admin = current_user_can( 'manage_options' );
706
			$is_primary = ( (int) $blog_id === $primary_blog_id );
707
708
			$struct[] = array(
709
				'isAdmin'   => $is_admin,
710
				'isPrimary' => $is_primary,
711
				'url'       => home_url( '/' ),
712
				'blogid'    => (string) $blog_id,
713
				'blogName'  => get_option( 'blogname' ),
714
				'xmlrpc'    => site_url( 'xmlrpc.php', 'rpc' ),
715
			);
716
717
			restore_current_blog();
718
		}
719
720
		return $struct;
721
	}
722
723
	/**
724
	 * Checks if the method received at least the minimum number of arguments.
725
	 *
726
	 * @since 3.4.0
727
	 * @access protected
728
	 *
729
	 * @param string|array $args Sanitize single string or array of strings.
730
	 * @param int $count         Minimum number of arguments.
731
	 * @return bool if `$args` contains at least $count arguments.
732
	 */
733
	protected function minimum_args( $args, $count ) {
734 View Code Duplication
		if ( count( $args ) < $count ) {
735
			$this->error = new IXR_Error( 400, __( 'Insufficient arguments passed to this XML-RPC method.' ) );
736
			return false;
737
		}
738
739
		return true;
740
	}
741
742
	/**
743
	 * Prepares taxonomy data for return in an XML-RPC object.
744
	 *
745
	 * @access protected
746
	 *
747
	 * @param object $taxonomy The unprepared taxonomy data.
748
	 * @param array $fields    The subset of taxonomy fields to return.
749
	 * @return array The prepared taxonomy data.
750
	 */
751
	protected function _prepare_taxonomy( $taxonomy, $fields ) {
752
		$_taxonomy = array(
753
			'name' => $taxonomy->name,
754
			'label' => $taxonomy->label,
755
			'hierarchical' => (bool) $taxonomy->hierarchical,
756
			'public' => (bool) $taxonomy->public,
757
			'show_ui' => (bool) $taxonomy->show_ui,
758
			'_builtin' => (bool) $taxonomy->_builtin,
759
		);
760
761
		if ( in_array( 'labels', $fields ) )
762
			$_taxonomy['labels'] = (array) $taxonomy->labels;
763
764
		if ( in_array( 'cap', $fields ) )
765
			$_taxonomy['cap'] = (array) $taxonomy->cap;
766
767
		if ( in_array( 'menu', $fields ) )
768
			$_taxonomy['show_in_menu'] = (bool) $_taxonomy->show_in_menu;
769
770
		if ( in_array( 'object_type', $fields ) )
771
			$_taxonomy['object_type'] = array_unique( (array) $taxonomy->object_type );
772
773
		/**
774
		 * Filters XML-RPC-prepared data for the given taxonomy.
775
		 *
776
		 * @since 3.4.0
777
		 *
778
		 * @param array       $_taxonomy An array of taxonomy data.
779
		 * @param WP_Taxonomy $taxonomy  Taxonomy object.
780
		 * @param array       $fields    The subset of taxonomy fields to return.
781
		 */
782
		return apply_filters( 'xmlrpc_prepare_taxonomy', $_taxonomy, $taxonomy, $fields );
783
	}
784
785
	/**
786
	 * Prepares term data for return in an XML-RPC object.
787
	 *
788
	 * @access protected
789
	 *
790
	 * @param array|object $term The unprepared term data.
791
	 * @return array The prepared term data.
792
	 */
793
	protected function _prepare_term( $term ) {
794
		$_term = $term;
795
		if ( ! is_array( $_term ) )
796
			$_term = get_object_vars( $_term );
797
798
		// For integers which may be larger than XML-RPC supports ensure we return strings.
799
		$_term['term_id'] = strval( $_term['term_id'] );
800
		$_term['term_group'] = strval( $_term['term_group'] );
801
		$_term['term_taxonomy_id'] = strval( $_term['term_taxonomy_id'] );
802
		$_term['parent'] = strval( $_term['parent'] );
803
804
		// Count we are happy to return as an integer because people really shouldn't use terms that much.
805
		$_term['count'] = intval( $_term['count'] );
806
807
		// Get term meta.
808
		$_term['custom_fields'] = $this->get_term_custom_fields( $_term['term_id'] );
809
810
		/**
811
		 * Filters XML-RPC-prepared data for the given term.
812
		 *
813
		 * @since 3.4.0
814
		 *
815
		 * @param array        $_term An array of term data.
816
		 * @param array|object $term  Term object or array.
817
		 */
818
		return apply_filters( 'xmlrpc_prepare_term', $_term, $term );
819
	}
820
821
	/**
822
	 * Convert a WordPress date string to an IXR_Date object.
823
	 *
824
	 * @access protected
825
	 *
826
	 * @param string $date Date string to convert.
827
	 * @return IXR_Date IXR_Date object.
828
	 */
829
	protected function _convert_date( $date ) {
830
		if ( $date === '0000-00-00 00:00:00' ) {
831
			return new IXR_Date( '00000000T00:00:00Z' );
832
		}
833
		return new IXR_Date( mysql2date( 'Ymd\TH:i:s', $date, false ) );
834
	}
835
836
	/**
837
	 * Convert a WordPress GMT date string to an IXR_Date object.
838
	 *
839
	 * @access protected
840
	 *
841
	 * @param string $date_gmt WordPress GMT date string.
842
	 * @param string $date     Date string.
843
	 * @return IXR_Date IXR_Date object.
844
	 */
845
	protected function _convert_date_gmt( $date_gmt, $date ) {
846
		if ( $date !== '0000-00-00 00:00:00' && $date_gmt === '0000-00-00 00:00:00' ) {
847
			return new IXR_Date( get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $date, false ), 'Ymd\TH:i:s' ) );
848
		}
849
		return $this->_convert_date( $date_gmt );
850
	}
851
852
	/**
853
	 * Prepares post data for return in an XML-RPC object.
854
	 *
855
	 * @access protected
856
	 *
857
	 * @param array $post   The unprepared post data.
858
	 * @param array $fields The subset of post type fields to return.
859
	 * @return array The prepared post data.
860
	 */
861
	protected function _prepare_post( $post, $fields ) {
862
		// Holds the data for this post. built up based on $fields.
863
		$_post = array( 'post_id' => strval( $post['ID'] ) );
864
865
		// Prepare common post fields.
866
		$post_fields = array(
867
			'post_title'        => $post['post_title'],
868
			'post_date'         => $this->_convert_date( $post['post_date'] ),
869
			'post_date_gmt'     => $this->_convert_date_gmt( $post['post_date_gmt'], $post['post_date'] ),
870
			'post_modified'     => $this->_convert_date( $post['post_modified'] ),
871
			'post_modified_gmt' => $this->_convert_date_gmt( $post['post_modified_gmt'], $post['post_modified'] ),
872
			'post_status'       => $post['post_status'],
873
			'post_type'         => $post['post_type'],
874
			'post_name'         => $post['post_name'],
875
			'post_author'       => $post['post_author'],
876
			'post_password'     => $post['post_password'],
877
			'post_excerpt'      => $post['post_excerpt'],
878
			'post_content'      => $post['post_content'],
879
			'post_parent'       => strval( $post['post_parent'] ),
880
			'post_mime_type'    => $post['post_mime_type'],
881
			'link'              => get_permalink( $post['ID'] ),
882
			'guid'              => $post['guid'],
883
			'menu_order'        => intval( $post['menu_order'] ),
884
			'comment_status'    => $post['comment_status'],
885
			'ping_status'       => $post['ping_status'],
886
			'sticky'            => ( $post['post_type'] === 'post' && is_sticky( $post['ID'] ) ),
887
		);
888
889
		// Thumbnail.
890
		$post_fields['post_thumbnail'] = array();
891
		$thumbnail_id = get_post_thumbnail_id( $post['ID'] );
892
		if ( $thumbnail_id ) {
893
			$thumbnail_size = current_theme_supports('post-thumbnail') ? 'post-thumbnail' : 'thumbnail';
894
			$post_fields['post_thumbnail'] = $this->_prepare_media_item( get_post( $thumbnail_id ), $thumbnail_size );
0 ignored issues
show
It seems like get_post($thumbnail_id) targeting get_post() can also be of type array or null; however, wp_xmlrpc_server::_prepare_media_item() does only seem to accept object, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
895
		}
896
897
		// Consider future posts as published.
898
		if ( $post_fields['post_status'] === 'future' )
899
			$post_fields['post_status'] = 'publish';
900
901
		// Fill in blank post format.
902
		$post_fields['post_format'] = get_post_format( $post['ID'] );
903
		if ( empty( $post_fields['post_format'] ) )
904
			$post_fields['post_format'] = 'standard';
905
906
		// Merge requested $post_fields fields into $_post.
907
		if ( in_array( 'post', $fields ) ) {
908
			$_post = array_merge( $_post, $post_fields );
909
		} else {
910
			$requested_fields = array_intersect_key( $post_fields, array_flip( $fields ) );
911
			$_post = array_merge( $_post, $requested_fields );
912
		}
913
914
		$all_taxonomy_fields = in_array( 'taxonomies', $fields );
915
916
		if ( $all_taxonomy_fields || in_array( 'terms', $fields ) ) {
917
			$post_type_taxonomies = get_object_taxonomies( $post['post_type'], 'names' );
918
			$terms = wp_get_object_terms( $post['ID'], $post_type_taxonomies );
919
			$_post['terms'] = array();
920
			foreach ( $terms as $term ) {
0 ignored issues
show
The expression $terms of type array|object<WP_Error> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
921
				$_post['terms'][] = $this->_prepare_term( $term );
922
			}
923
		}
924
925
		if ( in_array( 'custom_fields', $fields ) )
926
			$_post['custom_fields'] = $this->get_custom_fields( $post['ID'] );
927
928
		if ( in_array( 'enclosure', $fields ) ) {
929
			$_post['enclosure'] = array();
930
			$enclosures = (array) get_post_meta( $post['ID'], 'enclosure' );
931
			if ( ! empty( $enclosures ) ) {
932
				$encdata = explode( "\n", $enclosures[0] );
933
				$_post['enclosure']['url'] = trim( htmlspecialchars( $encdata[0] ) );
934
				$_post['enclosure']['length'] = (int) trim( $encdata[1] );
935
				$_post['enclosure']['type'] = trim( $encdata[2] );
936
			}
937
		}
938
939
		/**
940
		 * Filters XML-RPC-prepared date for the given post.
941
		 *
942
		 * @since 3.4.0
943
		 *
944
		 * @param array $_post  An array of modified post data.
945
		 * @param array $post   An array of post data.
946
		 * @param array $fields An array of post fields.
947
		 */
948
		return apply_filters( 'xmlrpc_prepare_post', $_post, $post, $fields );
949
	}
950
951
	/**
952
	 * Prepares post data for return in an XML-RPC object.
953
	 *
954
	 * @since 3.4.0
955
	 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
956
	 * @access protected
957
	 *
958
	 * @param WP_Post_Type $post_type Post type object.
959
	 * @param array        $fields    The subset of post fields to return.
960
	 * @return array The prepared post type data.
961
	 */
962
	protected function _prepare_post_type( $post_type, $fields ) {
963
		$_post_type = array(
964
			'name' => $post_type->name,
965
			'label' => $post_type->label,
966
			'hierarchical' => (bool) $post_type->hierarchical,
967
			'public' => (bool) $post_type->public,
968
			'show_ui' => (bool) $post_type->show_ui,
969
			'_builtin' => (bool) $post_type->_builtin,
970
			'has_archive' => (bool) $post_type->has_archive,
971
			'supports' => get_all_post_type_supports( $post_type->name ),
972
		);
973
974
		if ( in_array( 'labels', $fields ) ) {
975
			$_post_type['labels'] = (array) $post_type->labels;
976
		}
977
978
		if ( in_array( 'cap', $fields ) ) {
979
			$_post_type['cap'] = (array) $post_type->cap;
980
			$_post_type['map_meta_cap'] = (bool) $post_type->map_meta_cap;
981
		}
982
983
		if ( in_array( 'menu', $fields ) ) {
984
			$_post_type['menu_position'] = (int) $post_type->menu_position;
985
			$_post_type['menu_icon'] = $post_type->menu_icon;
986
			$_post_type['show_in_menu'] = (bool) $post_type->show_in_menu;
987
		}
988
989
		if ( in_array( 'taxonomies', $fields ) )
990
			$_post_type['taxonomies'] = get_object_taxonomies( $post_type->name, 'names' );
991
992
		/**
993
		 * Filters XML-RPC-prepared date for the given post type.
994
		 *
995
		 * @since 3.4.0
996
		 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
997
		 *
998
		 * @param array        $_post_type An array of post type data.
999
		 * @param WP_Post_Type $post_type  Post type object.
1000
		 */
1001
		return apply_filters( 'xmlrpc_prepare_post_type', $_post_type, $post_type );
1002
	}
1003
1004
	/**
1005
	 * Prepares media item data for return in an XML-RPC object.
1006
	 *
1007
	 * @access protected
1008
	 *
1009
	 * @param object $media_item     The unprepared media item data.
1010
	 * @param string $thumbnail_size The image size to use for the thumbnail URL.
1011
	 * @return array The prepared media item data.
1012
	 */
1013
	protected function _prepare_media_item( $media_item, $thumbnail_size = 'thumbnail' ) {
1014
		$_media_item = array(
1015
			'attachment_id'    => strval( $media_item->ID ),
1016
			'date_created_gmt' => $this->_convert_date_gmt( $media_item->post_date_gmt, $media_item->post_date ),
1017
			'parent'           => $media_item->post_parent,
1018
			'link'             => wp_get_attachment_url( $media_item->ID ),
1019
			'title'            => $media_item->post_title,
1020
			'caption'          => $media_item->post_excerpt,
1021
			'description'      => $media_item->post_content,
1022
			'metadata'         => wp_get_attachment_metadata( $media_item->ID ),
1023
			'type'             => $media_item->post_mime_type
1024
		);
1025
1026
		$thumbnail_src = image_downsize( $media_item->ID, $thumbnail_size );
1027
		if ( $thumbnail_src )
1028
			$_media_item['thumbnail'] = $thumbnail_src[0];
1029
		else
1030
			$_media_item['thumbnail'] = $_media_item['link'];
1031
1032
		/**
1033
		 * Filters XML-RPC-prepared data for the given media item.
1034
		 *
1035
		 * @since 3.4.0
1036
		 *
1037
		 * @param array  $_media_item    An array of media item data.
1038
		 * @param object $media_item     Media item object.
1039
		 * @param string $thumbnail_size Image size.
1040
		 */
1041
		return apply_filters( 'xmlrpc_prepare_media_item', $_media_item, $media_item, $thumbnail_size );
1042
	}
1043
1044
	/**
1045
	 * Prepares page data for return in an XML-RPC object.
1046
	 *
1047
	 * @access protected
1048
	 *
1049
	 * @param object $page The unprepared page data.
1050
	 * @return array The prepared page data.
1051
	 */
1052
	protected function _prepare_page( $page ) {
1053
		// Get all of the page content and link.
1054
		$full_page = get_extended( $page->post_content );
1055
		$link = get_permalink( $page->ID );
1056
1057
		// Get info the page parent if there is one.
1058
		$parent_title = "";
1059
		if ( ! empty( $page->post_parent ) ) {
1060
			$parent = get_post( $page->post_parent );
1061
			$parent_title = $parent->post_title;
1062
		}
1063
1064
		// Determine comment and ping settings.
1065
		$allow_comments = comments_open( $page->ID ) ? 1 : 0;
1066
		$allow_pings = pings_open( $page->ID ) ? 1 : 0;
1067
1068
		// Format page date.
1069
		$page_date = $this->_convert_date( $page->post_date );
1070
		$page_date_gmt = $this->_convert_date_gmt( $page->post_date_gmt, $page->post_date );
1071
1072
		// Pull the categories info together.
1073
		$categories = array();
1074
		if ( is_object_in_taxonomy( 'page', 'category' ) ) {
1075
			foreach ( wp_get_post_categories( $page->ID ) as $cat_id ) {
0 ignored issues
show
The expression wp_get_post_categories($page->ID) of type array|object<WP_Error> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1076
				$categories[] = get_cat_name( $cat_id );
1077
			}
1078
		}
1079
1080
		// Get the author info.
1081
		$author = get_userdata( $page->post_author );
1082
1083
		$page_template = get_page_template_slug( $page->ID );
1084
		if ( empty( $page_template ) )
1085
			$page_template = 'default';
1086
1087
		$_page = array(
1088
			'dateCreated'            => $page_date,
1089
			'userid'                 => $page->post_author,
1090
			'page_id'                => $page->ID,
1091
			'page_status'            => $page->post_status,
1092
			'description'            => $full_page['main'],
1093
			'title'                  => $page->post_title,
1094
			'link'                   => $link,
1095
			'permaLink'              => $link,
1096
			'categories'             => $categories,
1097
			'excerpt'                => $page->post_excerpt,
1098
			'text_more'              => $full_page['extended'],
1099
			'mt_allow_comments'      => $allow_comments,
1100
			'mt_allow_pings'         => $allow_pings,
1101
			'wp_slug'                => $page->post_name,
1102
			'wp_password'            => $page->post_password,
1103
			'wp_author'              => $author->display_name,
1104
			'wp_page_parent_id'      => $page->post_parent,
1105
			'wp_page_parent_title'   => $parent_title,
1106
			'wp_page_order'          => $page->menu_order,
1107
			'wp_author_id'           => (string) $author->ID,
1108
			'wp_author_display_name' => $author->display_name,
1109
			'date_created_gmt'       => $page_date_gmt,
1110
			'custom_fields'          => $this->get_custom_fields( $page->ID ),
1111
			'wp_page_template'       => $page_template
1112
		);
1113
1114
		/**
1115
		 * Filters XML-RPC-prepared data for the given page.
1116
		 *
1117
		 * @since 3.4.0
1118
		 *
1119
		 * @param array   $_page An array of page data.
1120
		 * @param WP_Post $page  Page object.
1121
		 */
1122
		return apply_filters( 'xmlrpc_prepare_page', $_page, $page );
1123
	}
1124
1125
	/**
1126
	 * Prepares comment data for return in an XML-RPC object.
1127
	 *
1128
	 * @access protected
1129
	 *
1130
	 * @param object $comment The unprepared comment data.
1131
	 * @return array The prepared comment data.
1132
	 */
1133
	protected function _prepare_comment( $comment ) {
1134
		// Format page date.
1135
		$comment_date_gmt = $this->_convert_date_gmt( $comment->comment_date_gmt, $comment->comment_date );
1136
1137
		if ( '0' == $comment->comment_approved ) {
1138
			$comment_status = 'hold';
1139
		} elseif ( 'spam' == $comment->comment_approved ) {
1140
			$comment_status = 'spam';
1141
		} elseif ( '1' == $comment->comment_approved ) {
1142
			$comment_status = 'approve';
1143
		} else {
1144
			$comment_status = $comment->comment_approved;
1145
		}
1146
		$_comment = array(
1147
			'date_created_gmt' => $comment_date_gmt,
1148
			'user_id'          => $comment->user_id,
1149
			'comment_id'       => $comment->comment_ID,
1150
			'parent'           => $comment->comment_parent,
1151
			'status'           => $comment_status,
1152
			'content'          => $comment->comment_content,
1153
			'link'             => get_comment_link($comment),
1154
			'post_id'          => $comment->comment_post_ID,
1155
			'post_title'       => get_the_title($comment->comment_post_ID),
1156
			'author'           => $comment->comment_author,
1157
			'author_url'       => $comment->comment_author_url,
1158
			'author_email'     => $comment->comment_author_email,
1159
			'author_ip'        => $comment->comment_author_IP,
1160
			'type'             => $comment->comment_type,
1161
		);
1162
1163
		/**
1164
		 * Filters XML-RPC-prepared data for the given comment.
1165
		 *
1166
		 * @since 3.4.0
1167
		 *
1168
		 * @param array      $_comment An array of prepared comment data.
1169
		 * @param WP_Comment $comment  Comment object.
1170
		 */
1171
		return apply_filters( 'xmlrpc_prepare_comment', $_comment, $comment );
1172
	}
1173
1174
	/**
1175
	 * Prepares user data for return in an XML-RPC object.
1176
	 *
1177
	 * @access protected
1178
	 *
1179
	 * @param WP_User $user   The unprepared user object.
1180
	 * @param array   $fields The subset of user fields to return.
1181
	 * @return array The prepared user data.
1182
	 */
1183
	protected function _prepare_user( $user, $fields ) {
1184
		$_user = array( 'user_id' => strval( $user->ID ) );
1185
1186
		$user_fields = array(
1187
			'username'          => $user->user_login,
1188
			'first_name'        => $user->user_firstname,
1189
			'last_name'         => $user->user_lastname,
1190
			'registered'        => $this->_convert_date( $user->user_registered ),
1191
			'bio'               => $user->user_description,
1192
			'email'             => $user->user_email,
1193
			'nickname'          => $user->nickname,
1194
			'nicename'          => $user->user_nicename,
1195
			'url'               => $user->user_url,
1196
			'display_name'      => $user->display_name,
1197
			'roles'             => $user->roles,
1198
		);
1199
1200
		if ( in_array( 'all', $fields ) ) {
1201
			$_user = array_merge( $_user, $user_fields );
1202
		} else {
1203
			if ( in_array( 'basic', $fields ) ) {
1204
				$basic_fields = array( 'username', 'email', 'registered', 'display_name', 'nicename' );
1205
				$fields = array_merge( $fields, $basic_fields );
1206
			}
1207
			$requested_fields = array_intersect_key( $user_fields, array_flip( $fields ) );
1208
			$_user = array_merge( $_user, $requested_fields );
1209
		}
1210
1211
		/**
1212
		 * Filters XML-RPC-prepared data for the given user.
1213
		 *
1214
		 * @since 3.5.0
1215
		 *
1216
		 * @param array   $_user  An array of user data.
1217
		 * @param WP_User $user   User object.
1218
		 * @param array   $fields An array of user fields.
1219
		 */
1220
		return apply_filters( 'xmlrpc_prepare_user', $_user, $user, $fields );
1221
	}
1222
1223
	/**
1224
	 * Create a new post for any registered post type.
1225
	 *
1226
	 * @since 3.4.0
1227
	 *
1228
	 * @link https://en.wikipedia.org/wiki/RSS_enclosure for information on RSS enclosures.
1229
	 *
1230
	 * @param array  $args {
1231
	 *     Method arguments. Note: top-level arguments must be ordered as documented.
1232
	 *
1233
	 *     @type int    $blog_id        Blog ID (unused).
1234
	 *     @type string $username       Username.
1235
	 *     @type string $password       Password.
1236
	 *     @type array  $content_struct {
1237
	 *         Content struct for adding a new post. See wp_insert_post() for information on
1238
	 *         additional post fields
1239
	 *
1240
	 *         @type string $post_type      Post type. Default 'post'.
1241
	 *         @type string $post_status    Post status. Default 'draft'
1242
	 *         @type string $post_title     Post title.
1243
	 *         @type int    $post_author    Post author ID.
1244
	 *         @type string $post_excerpt   Post excerpt.
1245
	 *         @type string $post_content   Post content.
1246
	 *         @type string $post_date_gmt  Post date in GMT.
1247
	 *         @type string $post_date      Post date.
1248
	 *         @type string $post_password  Post password (20-character limit).
1249
	 *         @type string $comment_status Post comment enabled status. Accepts 'open' or 'closed'.
1250
	 *         @type string $ping_status    Post ping status. Accepts 'open' or 'closed'.
1251
	 *         @type bool   $sticky         Whether the post should be sticky. Automatically false if
1252
	 *                                      `$post_status` is 'private'.
1253
	 *         @type int    $post_thumbnail ID of an image to use as the post thumbnail/featured image.
1254
	 *         @type array  $custom_fields  Array of meta key/value pairs to add to the post.
1255
	 *         @type array  $terms          Associative array with taxonomy names as keys and arrays
1256
	 *                                      of term IDs as values.
1257
	 *         @type array  $terms_names    Associative array with taxonomy names as keys and arrays
1258
	 *                                      of term names as values.
1259
	 *         @type array  $enclosure      {
1260
	 *             Array of feed enclosure data to add to post meta.
1261
	 *
1262
	 *             @type string $url    URL for the feed enclosure.
1263
	 *             @type int    $length Size in bytes of the enclosure.
1264
	 *             @type string $type   Mime-type for the enclosure.
1265
	 *         }
1266
	 *     }
1267
	 * }
1268
	 * @return int|IXR_Error Post ID on success, IXR_Error instance otherwise.
0 ignored issues
show
Should the return type not be IXR_Error|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1269
	 */
1270
	public function wp_newPost( $args ) {
1271
		if ( ! $this->minimum_args( $args, 4 ) )
1272
			return $this->error;
1273
1274
		$this->escape( $args );
1275
1276
		$username       = $args[1];
1277
		$password       = $args[2];
1278
		$content_struct = $args[3];
1279
1280
		if ( ! $user = $this->login( $username, $password ) )
1281
			return $this->error;
1282
1283
		// convert the date field back to IXR form
1284
		if ( isset( $content_struct['post_date'] ) && ! ( $content_struct['post_date'] instanceof IXR_Date ) ) {
1285
			$content_struct['post_date'] = $this->_convert_date( $content_struct['post_date'] );
1286
		}
1287
1288
		// ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct,
1289
		// since _insert_post will ignore the non-GMT date if the GMT date is set
1290
		if ( isset( $content_struct['post_date_gmt'] ) && ! ( $content_struct['post_date_gmt'] instanceof IXR_Date ) ) {
1291 View Code Duplication
			if ( $content_struct['post_date_gmt'] == '0000-00-00 00:00:00' || isset( $content_struct['post_date'] ) ) {
1292
				unset( $content_struct['post_date_gmt'] );
1293
			} else {
1294
				$content_struct['post_date_gmt'] = $this->_convert_date( $content_struct['post_date_gmt'] );
1295
			}
1296
		}
1297
1298
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1299
		do_action( 'xmlrpc_call', 'wp.newPost' );
1300
1301
		unset( $content_struct['ID'] );
1302
1303
		return $this->_insert_post( $user, $content_struct );
1304
	}
1305
1306
	/**
1307
	 * Helper method for filtering out elements from an array.
1308
	 *
1309
	 * @since 3.4.0
1310
	 *
1311
	 * @param int $count Number to compare to one.
1312
	 */
1313
	private function _is_greater_than_one( $count ) {
1314
		return $count > 1;
1315
	}
1316
1317
	/**
1318
	 * Encapsulate the logic for sticking a post
1319
	 * and determining if the user has permission to do so
1320
	 *
1321
	 * @since 4.3.0
1322
	 * @access private
1323
	 *
1324
	 * @param array $post_data
1325
	 * @param bool  $update
1326
	 * @return void|IXR_Error
1327
	 */
1328
	private function _toggle_sticky( $post_data, $update = false ) {
1329
		$post_type = get_post_type_object( $post_data['post_type'] );
1330
1331
		// Private and password-protected posts cannot be stickied.
1332
		if ( 'private' === $post_data['post_status'] || ! empty( $post_data['post_password'] ) ) {
1333
			// Error if the client tried to stick the post, otherwise, silently unstick.
1334
			if ( ! empty( $post_data['sticky'] ) ) {
1335
				return new IXR_Error( 401, __( 'Sorry, you cannot stick a private post.' ) );
1336
			}
1337
1338
			if ( $update ) {
1339
				unstick_post( $post_data['ID'] );
1340
			}
1341
		} elseif ( isset( $post_data['sticky'] ) )  {
1342
			if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
1343
				return new IXR_Error( 401, __( 'Sorry, you are not allowed to make posts sticky.' ) );
1344
			}
1345
1346
			$sticky = wp_validate_boolean( $post_data['sticky'] );
1347
			if ( $sticky ) {
1348
				stick_post( $post_data['ID'] );
1349
			} else {
1350
				unstick_post( $post_data['ID'] );
1351
			}
1352
		}
1353
	}
1354
1355
	/**
1356
	 * Helper method for wp_newPost() and wp_editPost(), containing shared logic.
1357
	 *
1358
	 * @since 3.4.0
1359
	 * @access protected
1360
	 *
1361
	 * @see wp_insert_post()
1362
	 *
1363
	 * @param WP_User         $user           The post author if post_author isn't set in $content_struct.
1364
	 * @param array|IXR_Error $content_struct Post data to insert.
1365
	 * @return IXR_Error|string
1366
	 */
1367
	protected function _insert_post( $user, $content_struct ) {
1368
		$defaults = array(
1369
			'post_status'    => 'draft',
1370
			'post_type'      => 'post',
1371
			'post_author'    => null,
1372
			'post_password'  => null,
1373
			'post_excerpt'   => null,
1374
			'post_content'   => null,
1375
			'post_title'     => null,
1376
			'post_date'      => null,
1377
			'post_date_gmt'  => null,
1378
			'post_format'    => null,
1379
			'post_name'      => null,
1380
			'post_thumbnail' => null,
1381
			'post_parent'    => null,
1382
			'ping_status'    => null,
1383
			'comment_status' => null,
1384
			'custom_fields'  => null,
1385
			'terms_names'    => null,
1386
			'terms'          => null,
1387
			'sticky'         => null,
1388
			'enclosure'      => null,
1389
			'ID'             => null,
1390
		);
1391
1392
		$post_data = wp_parse_args( array_intersect_key( $content_struct, $defaults ), $defaults );
1393
1394
		$post_type = get_post_type_object( $post_data['post_type'] );
1395
		if ( ! $post_type )
1396
			return new IXR_Error( 403, __( 'Invalid post type.' ) );
1397
1398
		$update = ! empty( $post_data['ID'] );
1399
1400
		if ( $update ) {
1401
			if ( ! get_post( $post_data['ID'] ) )
1402
				return new IXR_Error( 401, __( 'Invalid post ID.' ) );
1403
			if ( ! current_user_can( 'edit_post', $post_data['ID'] ) )
1404
				return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
1405
			if ( $post_data['post_type'] != get_post_type( $post_data['ID'] ) )
1406
				return new IXR_Error( 401, __( 'The post type may not be changed.' ) );
1407 View Code Duplication
		} else {
1408
			if ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( $post_type->cap->edit_posts ) )
1409
				return new IXR_Error( 401, __( 'Sorry, you are not allowed to post on this site.' ) );
1410
		}
1411
1412
		switch ( $post_data['post_status'] ) {
1413
			case 'draft':
1414
			case 'pending':
1415
				break;
1416 View Code Duplication
			case 'private':
1417
				if ( ! current_user_can( $post_type->cap->publish_posts ) )
1418
					return new IXR_Error( 401, __( 'Sorry, you are not allowed to create private posts in this post type.' ) );
1419
				break;
1420
			case 'publish':
1421 View Code Duplication
			case 'future':
1422
				if ( ! current_user_can( $post_type->cap->publish_posts ) )
1423
					return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts in this post type.' ) );
1424
				break;
1425
			default:
1426
				if ( ! get_post_status_object( $post_data['post_status'] ) )
1427
					$post_data['post_status'] = 'draft';
1428
			break;
1429
		}
1430
1431
		if ( ! empty( $post_data['post_password'] ) && ! current_user_can( $post_type->cap->publish_posts ) )
1432
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to create password protected posts in this post type.' ) );
1433
1434
		$post_data['post_author'] = absint( $post_data['post_author'] );
1435
		if ( ! empty( $post_data['post_author'] ) && $post_data['post_author'] != $user->ID ) {
1436
			if ( ! current_user_can( $post_type->cap->edit_others_posts ) )
1437
				return new IXR_Error( 401, __( 'Sorry, you are not allowed to create posts as this user.' ) );
1438
1439
			$author = get_userdata( $post_data['post_author'] );
1440
1441
			if ( ! $author )
1442
				return new IXR_Error( 404, __( 'Invalid author ID.' ) );
1443
		} else {
1444
			$post_data['post_author'] = $user->ID;
1445
		}
1446
1447 View Code Duplication
		if ( isset( $post_data['comment_status'] ) && $post_data['comment_status'] != 'open' && $post_data['comment_status'] != 'closed' )
1448
			unset( $post_data['comment_status'] );
1449
1450 View Code Duplication
		if ( isset( $post_data['ping_status'] ) && $post_data['ping_status'] != 'open' && $post_data['ping_status'] != 'closed' )
1451
			unset( $post_data['ping_status'] );
1452
1453
		// Do some timestamp voodoo.
1454 View Code Duplication
		if ( ! empty( $post_data['post_date_gmt'] ) ) {
1455
			// We know this is supposed to be GMT, so we're going to slap that Z on there by force.
1456
			$dateCreated = rtrim( $post_data['post_date_gmt']->getIso(), 'Z' ) . 'Z';
0 ignored issues
show
The method getIso cannot be called on $post_data['post_date_gmt'] (of type integer|double).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
1457
		} elseif ( ! empty( $post_data['post_date'] ) ) {
1458
			$dateCreated = $post_data['post_date']->getIso();
0 ignored issues
show
The method getIso cannot be called on $post_data['post_date'] (of type integer|double).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
1459
		}
1460
1461
		// Default to not flagging the post date to be edited unless it's intentional.
1462
		$post_data['edit_date'] = false;
1463
1464
		if ( ! empty( $dateCreated ) ) {
1465
			$post_data['post_date'] = get_date_from_gmt( iso8601_to_datetime( $dateCreated ) );
1466
			$post_data['post_date_gmt'] = iso8601_to_datetime( $dateCreated, 'GMT' );
1467
1468
			// Flag the post date to be edited.
1469
			$post_data['edit_date'] = true;
1470
		}
1471
1472
		if ( ! isset( $post_data['ID'] ) )
1473
			$post_data['ID'] = get_default_post_to_edit( $post_data['post_type'], true )->ID;
1474
		$post_ID = $post_data['ID'];
1475
1476
		if ( $post_data['post_type'] == 'post' ) {
1477
			$error = $this->_toggle_sticky( $post_data, $update );
1478
			if ( $error ) {
1479
				return $error;
1480
			}
1481
		}
1482
1483
		if ( isset( $post_data['post_thumbnail'] ) ) {
1484
			// empty value deletes, non-empty value adds/updates.
1485
			if ( ! $post_data['post_thumbnail'] )
1486
				delete_post_thumbnail( $post_ID );
1487
			elseif ( ! get_post( absint( $post_data['post_thumbnail'] ) ) )
1488
				return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
1489
			set_post_thumbnail( $post_ID, $post_data['post_thumbnail'] );
1490
			unset( $content_struct['post_thumbnail'] );
1491
		}
1492
1493
		if ( isset( $post_data['custom_fields'] ) )
1494
			$this->set_custom_fields( $post_ID, $post_data['custom_fields'] );
1495
1496
		if ( isset( $post_data['terms'] ) || isset( $post_data['terms_names'] ) ) {
1497
			$post_type_taxonomies = get_object_taxonomies( $post_data['post_type'], 'objects' );
1498
1499
			// Accumulate term IDs from terms and terms_names.
1500
			$terms = array();
1501
1502
			// First validate the terms specified by ID.
1503
			if ( isset( $post_data['terms'] ) && is_array( $post_data['terms'] ) ) {
1504
				$taxonomies = array_keys( $post_data['terms'] );
1505
1506
				// Validating term ids.
1507
				foreach ( $taxonomies as $taxonomy ) {
1508
					if ( ! array_key_exists( $taxonomy , $post_type_taxonomies ) )
1509
						return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
1510
1511 View Code Duplication
					if ( ! current_user_can( $post_type_taxonomies[$taxonomy]->cap->assign_terms ) )
1512
						return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
1513
1514
					$term_ids = $post_data['terms'][$taxonomy];
1515
					$terms[ $taxonomy ] = array();
1516
					foreach ( $term_ids as $term_id ) {
1517
						$term = get_term_by( 'id', $term_id, $taxonomy );
1518
1519
						if ( ! $term )
1520
							return new IXR_Error( 403, __( 'Invalid term ID.' ) );
1521
1522
						$terms[$taxonomy][] = (int) $term_id;
1523
					}
1524
				}
1525
			}
1526
1527
			// Now validate terms specified by name.
1528
			if ( isset( $post_data['terms_names'] ) && is_array( $post_data['terms_names'] ) ) {
1529
				$taxonomies = array_keys( $post_data['terms_names'] );
1530
1531
				foreach ( $taxonomies as $taxonomy ) {
1532
					if ( ! array_key_exists( $taxonomy , $post_type_taxonomies ) )
1533
						return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
1534
1535 View Code Duplication
					if ( ! current_user_can( $post_type_taxonomies[$taxonomy]->cap->assign_terms ) )
1536
						return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
1537
1538
					/*
1539
					 * For hierarchical taxonomies, we can't assign a term when multiple terms
1540
					 * in the hierarchy share the same name.
1541
					 */
1542
					$ambiguous_terms = array();
1543
					if ( is_taxonomy_hierarchical( $taxonomy ) ) {
1544
						$tax_term_names = get_terms( $taxonomy, array( 'fields' => 'names', 'hide_empty' => false ) );
1545
1546
						// Count the number of terms with the same name.
1547
						$tax_term_names_count = array_count_values( $tax_term_names );
1548
1549
						// Filter out non-ambiguous term names.
1550
						$ambiguous_tax_term_counts = array_filter( $tax_term_names_count, array( $this, '_is_greater_than_one') );
1551
1552
						$ambiguous_terms = array_keys( $ambiguous_tax_term_counts );
1553
					}
1554
1555
					$term_names = $post_data['terms_names'][$taxonomy];
1556
					foreach ( $term_names as $term_name ) {
1557
						if ( in_array( $term_name, $ambiguous_terms ) )
1558
							return new IXR_Error( 401, __( 'Ambiguous term name used in a hierarchical taxonomy. Please use term ID instead.' ) );
1559
1560
						$term = get_term_by( 'name', $term_name, $taxonomy );
1561
1562
						if ( ! $term ) {
1563
							// Term doesn't exist, so check that the user is allowed to create new terms.
1564 View Code Duplication
							if ( ! current_user_can( $post_type_taxonomies[$taxonomy]->cap->edit_terms ) )
1565
								return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a term to one of the given taxonomies.' ) );
1566
1567
							// Create the new term.
1568
							$term_info = wp_insert_term( $term_name, $taxonomy );
1569
							if ( is_wp_error( $term_info ) )
1570
								return new IXR_Error( 500, $term_info->get_error_message() );
1571
1572
							$terms[$taxonomy][] = (int) $term_info['term_id'];
1573
						} else {
1574
							$terms[$taxonomy][] = (int) $term->term_id;
1575
						}
1576
					}
1577
				}
1578
			}
1579
1580
			$post_data['tax_input'] = $terms;
1581
			unset( $post_data['terms'], $post_data['terms_names'] );
1582
		}
1583
1584
		if ( isset( $post_data['post_format'] ) ) {
1585
			$format = set_post_format( $post_ID, $post_data['post_format'] );
1586
1587
			if ( is_wp_error( $format ) )
1588
				return new IXR_Error( 500, $format->get_error_message() );
1589
1590
			unset( $post_data['post_format'] );
1591
		}
1592
1593
		// Handle enclosures.
1594
		$enclosure = isset( $post_data['enclosure'] ) ? $post_data['enclosure'] : null;
1595
		$this->add_enclosure_if_new( $post_ID, $enclosure );
1596
1597
		$this->attach_uploads( $post_ID, $post_data['post_content'] );
1598
1599
		/**
1600
		 * Filters post data array to be inserted via XML-RPC.
1601
		 *
1602
		 * @since 3.4.0
1603
		 *
1604
		 * @param array $post_data      Parsed array of post data.
1605
		 * @param array $content_struct Post data array.
1606
		 */
1607
		$post_data = apply_filters( 'xmlrpc_wp_insert_post_data', $post_data, $content_struct );
1608
1609
		$post_ID = $update ? wp_update_post( $post_data, true ) : wp_insert_post( $post_data, true );
1610
		if ( is_wp_error( $post_ID ) )
1611
			return new IXR_Error( 500, $post_ID->get_error_message() );
1612
1613
		if ( ! $post_ID )
1614
			return new IXR_Error( 401, __( 'Sorry, your entry could not be posted.' ) );
1615
1616
		return strval( $post_ID );
1617
	}
1618
1619
	/**
1620
	 * Edit a post for any registered post type.
1621
	 *
1622
	 * The $content_struct parameter only needs to contain fields that
1623
	 * should be changed. All other fields will retain their existing values.
1624
	 *
1625
	 * @since 3.4.0
1626
	 *
1627
	 * @param array  $args {
1628
	 *     Method arguments. Note: arguments must be ordered as documented.
1629
	 *
1630
	 *     @type int    $blog_id        Blog ID (unused).
1631
	 *     @type string $username       Username.
1632
	 *     @type string $password       Password.
1633
	 *     @type int    $post_id        Post ID.
1634
	 *     @type array  $content_struct Extra content arguments.
1635
	 * }
1636
	 * @return true|IXR_Error True on success, IXR_Error on failure.
0 ignored issues
show
Should the return type not be IXR_Error|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1637
	 */
1638
	public function wp_editPost( $args ) {
1639
		if ( ! $this->minimum_args( $args, 5 ) )
1640
			return $this->error;
1641
1642
		$this->escape( $args );
1643
1644
		$username       = $args[1];
1645
		$password       = $args[2];
1646
		$post_id        = (int) $args[3];
1647
		$content_struct = $args[4];
1648
1649
		if ( ! $user = $this->login( $username, $password ) )
1650
			return $this->error;
1651
1652
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1653
		do_action( 'xmlrpc_call', 'wp.editPost' );
1654
1655
		$post = get_post( $post_id, ARRAY_A );
1656
1657
		if ( empty( $post['ID'] ) )
1658
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1659
1660
		if ( isset( $content_struct['if_not_modified_since'] ) ) {
1661
			// If the post has been modified since the date provided, return an error.
1662
			if ( mysql2date( 'U', $post['post_modified_gmt'] ) > $content_struct['if_not_modified_since']->getTimestamp() ) {
1663
				return new IXR_Error( 409, __( 'There is a revision of this post that is more recent.' ) );
1664
			}
1665
		}
1666
1667
		// Convert the date field back to IXR form.
1668
		$post['post_date'] = $this->_convert_date( $post['post_date'] );
1669
1670
		/*
1671
		 * Ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct,
1672
		 * since _insert_post() will ignore the non-GMT date if the GMT date is set.
1673
		 */
1674 View Code Duplication
		if ( $post['post_date_gmt'] == '0000-00-00 00:00:00' || isset( $content_struct['post_date'] ) )
1675
			unset( $post['post_date_gmt'] );
1676
		else
1677
			$post['post_date_gmt'] = $this->_convert_date( $post['post_date_gmt'] );
1678
1679
		$this->escape( $post );
1680
		$merged_content_struct = array_merge( $post, $content_struct );
1681
1682
		$retval = $this->_insert_post( $user, $merged_content_struct );
1683
		if ( $retval instanceof IXR_Error )
1684
			return $retval;
1685
1686
		return true;
1687
	}
1688
1689
	/**
1690
	 * Delete a post for any registered post type.
1691
	 *
1692
	 * @since 3.4.0
1693
	 *
1694
	 * @see wp_delete_post()
1695
	 *
1696
	 * @param array  $args {
1697
	 *     Method arguments. Note: arguments must be ordered as documented.
1698
	 *
1699
	 *     @type int    $blog_id  Blog ID (unused).
1700
	 *     @type string $username Username.
1701
	 *     @type string $password Password.
1702
	 *     @type int    $post_id  Post ID.
1703
	 * }
1704
	 * @return true|IXR_Error True on success, IXR_Error instance on failure.
0 ignored issues
show
Should the return type not be IXR_Error|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1705
	 */
1706
	public function wp_deletePost( $args ) {
1707
		if ( ! $this->minimum_args( $args, 4 ) )
1708
			return $this->error;
1709
1710
		$this->escape( $args );
1711
1712
		$username   = $args[1];
1713
		$password   = $args[2];
1714
		$post_id    = (int) $args[3];
1715
1716
		if ( ! $user = $this->login( $username, $password ) )
1717
			return $this->error;
1718
1719
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1720
		do_action( 'xmlrpc_call', 'wp.deletePost' );
1721
1722
		$post = get_post( $post_id, ARRAY_A );
1723
		if ( empty( $post['ID'] ) ) {
1724
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1725
		}
1726
1727
		if ( ! current_user_can( 'delete_post', $post_id ) ) {
1728
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this post.' ) );
1729
		}
1730
1731
		$result = wp_delete_post( $post_id );
1732
1733
		if ( ! $result ) {
1734
			return new IXR_Error( 500, __( 'The post cannot be deleted.' ) );
1735
		}
1736
1737
		return true;
1738
	}
1739
1740
	/**
1741
	 * Retrieve a post.
1742
	 *
1743
	 * @since 3.4.0
1744
	 *
1745
	 * The optional $fields parameter specifies what fields will be included
1746
	 * in the response array. This should be a list of field names. 'post_id' will
1747
	 * always be included in the response regardless of the value of $fields.
1748
	 *
1749
	 * Instead of, or in addition to, individual field names, conceptual group
1750
	 * names can be used to specify multiple fields. The available conceptual
1751
	 * groups are 'post' (all basic fields), 'taxonomies', 'custom_fields',
1752
	 * and 'enclosure'.
1753
	 *
1754
	 * @see get_post()
1755
	 *
1756
	 * @param array $args {
1757
	 *     Method arguments. Note: arguments must be ordered as documented.
1758
	 *
1759
	 *     @type int    $blog_id  Blog ID (unused).
1760
	 *     @type string $username Username.
1761
	 *     @type string $password Password.
1762
	 *     @type int    $post_id  Post ID.
1763
	 *     @type array  $fields   The subset of post type fields to return.
1764
	 * }
1765
	 * @return array|IXR_Error Array contains (based on $fields parameter):
1766
	 *  - 'post_id'
1767
	 *  - 'post_title'
1768
	 *  - 'post_date'
1769
	 *  - 'post_date_gmt'
1770
	 *  - 'post_modified'
1771
	 *  - 'post_modified_gmt'
1772
	 *  - 'post_status'
1773
	 *  - 'post_type'
1774
	 *  - 'post_name'
1775
	 *  - 'post_author'
1776
	 *  - 'post_password'
1777
	 *  - 'post_excerpt'
1778
	 *  - 'post_content'
1779
	 *  - 'link'
1780
	 *  - 'comment_status'
1781
	 *  - 'ping_status'
1782
	 *  - 'sticky'
1783
	 *  - 'custom_fields'
1784
	 *  - 'terms'
1785
	 *  - 'categories'
1786
	 *  - 'tags'
1787
	 *  - 'enclosure'
1788
	 */
1789
	public function wp_getPost( $args ) {
1790
		if ( ! $this->minimum_args( $args, 4 ) )
1791
			return $this->error;
1792
1793
		$this->escape( $args );
1794
1795
		$username = $args[1];
1796
		$password = $args[2];
1797
		$post_id  = (int) $args[3];
1798
1799 View Code Duplication
		if ( isset( $args[4] ) ) {
1800
			$fields = $args[4];
1801
		} else {
1802
			/**
1803
			 * Filters the list of post query fields used by the given XML-RPC method.
1804
			 *
1805
			 * @since 3.4.0
1806
			 *
1807
			 * @param array  $fields Array of post fields. Default array contains 'post', 'terms', and 'custom_fields'.
1808
			 * @param string $method Method name.
1809
			 */
1810
			$fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPost' );
1811
		}
1812
1813
		if ( ! $user = $this->login( $username, $password ) )
1814
			return $this->error;
1815
1816
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1817
		do_action( 'xmlrpc_call', 'wp.getPost' );
1818
1819
		$post = get_post( $post_id, ARRAY_A );
1820
1821
		if ( empty( $post['ID'] ) )
1822
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1823
1824
		if ( ! current_user_can( 'edit_post', $post_id ) )
1825
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
1826
1827
		return $this->_prepare_post( $post, $fields );
1828
	}
1829
1830
	/**
1831
	 * Retrieve posts.
1832
	 *
1833
	 * @since 3.4.0
1834
	 *
1835
	 * @see wp_get_recent_posts()
1836
	 * @see wp_getPost() for more on `$fields`
1837
	 * @see get_posts() for more on `$filter` values
1838
	 *
1839
	 * @param array $args {
1840
	 *     Method arguments. Note: arguments must be ordered as documented.
1841
	 *
1842
	 *     @type int    $blog_id  Blog ID (unused).
1843
	 *     @type string $username Username.
1844
	 *     @type string $password Password.
1845
	 *     @type array  $filter   Optional. Modifies the query used to retrieve posts. Accepts 'post_type',
1846
	 *                            'post_status', 'number', 'offset', 'orderby', 's', and 'order'.
1847
	 *                            Default empty array.
1848
	 *     @type array  $fields   Optional. The subset of post type fields to return in the response array.
1849
	 * }
1850
	 * @return array|IXR_Error Array contains a collection of posts.
1851
	 */
1852
	public function wp_getPosts( $args ) {
1853
		if ( ! $this->minimum_args( $args, 3 ) )
1854
			return $this->error;
1855
1856
		$this->escape( $args );
1857
1858
		$username = $args[1];
1859
		$password = $args[2];
1860
		$filter   = isset( $args[3] ) ? $args[3] : array();
1861
1862 View Code Duplication
		if ( isset( $args[4] ) ) {
1863
			$fields = $args[4];
1864
		} else {
1865
			/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1866
			$fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPosts' );
1867
		}
1868
1869
		if ( ! $user = $this->login( $username, $password ) )
1870
			return $this->error;
1871
1872
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1873
		do_action( 'xmlrpc_call', 'wp.getPosts' );
1874
1875
		$query = array();
1876
1877
		if ( isset( $filter['post_type'] ) ) {
1878
			$post_type = get_post_type_object( $filter['post_type'] );
1879
			if ( ! ( (bool) $post_type ) )
1880
				return new IXR_Error( 403, __( 'Invalid post type.' ) );
1881
		} else {
1882
			$post_type = get_post_type_object( 'post' );
1883
		}
1884
1885
		if ( ! current_user_can( $post_type->cap->edit_posts ) )
1886
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type.' ) );
1887
1888
		$query['post_type'] = $post_type->name;
1889
1890
		if ( isset( $filter['post_status'] ) )
1891
			$query['post_status'] = $filter['post_status'];
1892
1893
		if ( isset( $filter['number'] ) )
1894
			$query['numberposts'] = absint( $filter['number'] );
1895
1896
		if ( isset( $filter['offset'] ) )
1897
			$query['offset'] = absint( $filter['offset'] );
1898
1899 View Code Duplication
		if ( isset( $filter['orderby'] ) ) {
1900
			$query['orderby'] = $filter['orderby'];
1901
1902
			if ( isset( $filter['order'] ) )
1903
				$query['order'] = $filter['order'];
1904
		}
1905
1906
		if ( isset( $filter['s'] ) ) {
1907
			$query['s'] = $filter['s'];
1908
		}
1909
1910
		$posts_list = wp_get_recent_posts( $query );
1911
1912
		if ( ! $posts_list )
1913
			return array();
1914
1915
		// Holds all the posts data.
1916
		$struct = array();
1917
1918
		foreach ( $posts_list as $post ) {
1919
			if ( ! current_user_can( 'edit_post', $post['ID'] ) )
1920
				continue;
1921
1922
			$struct[] = $this->_prepare_post( $post, $fields );
1923
		}
1924
1925
		return $struct;
1926
	}
1927
1928
	/**
1929
	 * Create a new term.
1930
	 *
1931
	 * @since 3.4.0
1932
	 *
1933
	 * @see wp_insert_term()
1934
	 *
1935
	 * @param array $args {
1936
	 *     Method arguments. Note: arguments must be ordered as documented.
1937
	 *
1938
	 *     @type int    $blog_id        Blog ID (unused).
1939
	 *     @type string $username       Username.
1940
	 *     @type string $password       Password.
1941
	 *     @type array  $content_struct Content struct for adding a new term. The struct must contain
1942
	 *                                  the term 'name' and 'taxonomy'. Optional accepted values include
1943
	 *                                  'parent', 'description', and 'slug'.
1944
	 * }
1945
	 * @return int|IXR_Error The term ID on success, or an IXR_Error object on failure.
0 ignored issues
show
Should the return type not be IXR_Error|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1946
	 */
1947
	public function wp_newTerm( $args ) {
1948
		if ( ! $this->minimum_args( $args, 4 ) )
1949
			return $this->error;
1950
1951
		$this->escape( $args );
1952
1953
		$username       = $args[1];
1954
		$password       = $args[2];
1955
		$content_struct = $args[3];
1956
1957
		if ( ! $user = $this->login( $username, $password ) )
1958
			return $this->error;
1959
1960
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1961
		do_action( 'xmlrpc_call', 'wp.newTerm' );
1962
1963
		if ( ! taxonomy_exists( $content_struct['taxonomy'] ) )
1964
			return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
1965
1966
		$taxonomy = get_taxonomy( $content_struct['taxonomy'] );
1967
1968
		if ( ! current_user_can( $taxonomy->cap->edit_terms ) ) {
1969
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to create terms in this taxonomy.' ) );
1970
		}
1971
1972
		$taxonomy = (array) $taxonomy;
1973
1974
		// hold the data of the term
1975
		$term_data = array();
1976
1977
		$term_data['name'] = trim( $content_struct['name'] );
1978
		if ( empty( $term_data['name'] ) )
1979
			return new IXR_Error( 403, __( 'The term name cannot be empty.' ) );
1980
1981 View Code Duplication
		if ( isset( $content_struct['parent'] ) ) {
1982
			if ( ! $taxonomy['hierarchical'] )
1983
				return new IXR_Error( 403, __( 'This taxonomy is not hierarchical.' ) );
1984
1985
			$parent_term_id = (int) $content_struct['parent'];
1986
			$parent_term = get_term( $parent_term_id , $taxonomy['name'] );
1987
1988
			if ( is_wp_error( $parent_term ) )
1989
				return new IXR_Error( 500, $parent_term->get_error_message() );
0 ignored issues
show
The method get_error_message does only exist in WP_Error, but not in WP_Term.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1990
1991
			if ( ! $parent_term )
1992
				return new IXR_Error( 403, __( 'Parent term does not exist.' ) );
1993
1994
			$term_data['parent'] = $content_struct['parent'];
1995
		}
1996
1997
		if ( isset( $content_struct['description'] ) )
1998
			$term_data['description'] = $content_struct['description'];
1999
2000
		if ( isset( $content_struct['slug'] ) )
2001
			$term_data['slug'] = $content_struct['slug'];
2002
2003
		$term = wp_insert_term( $term_data['name'] , $taxonomy['name'] , $term_data );
2004
2005
		if ( is_wp_error( $term ) )
2006
			return new IXR_Error( 500, $term->get_error_message() );
2007
2008
		if ( ! $term )
2009
			return new IXR_Error( 500, __( 'Sorry, your term could not be created.' ) );
2010
2011
		// Add term meta.
2012
		if ( isset( $content_struct['custom_fields'] ) ) {
2013
			$this->set_term_custom_fields( $term['term_id'], $content_struct['custom_fields'] );
2014
		}
2015
2016
		return strval( $term['term_id'] );
2017
	}
2018
2019
	/**
2020
	 * Edit a term.
2021
	 *
2022
	 * @since 3.4.0
2023
	 *
2024
	 * @see wp_update_term()
2025
	 *
2026
	 * @param array $args {
2027
	 *     Method arguments. Note: arguments must be ordered as documented.
2028
	 *
2029
	 *     @type int    $blog_id        Blog ID (unused).
2030
	 *     @type string $username       Username.
2031
	 *     @type string $password       Password.
2032
	 *     @type int    $term_id        Term ID.
2033
	 *     @type array  $content_struct Content struct for editing a term. The struct must contain the
2034
	 *                                  term ''taxonomy'. Optional accepted values include 'name', 'parent',
2035
	 *                                  'description', and 'slug'.
2036
	 * }
2037
	 * @return true|IXR_Error True on success, IXR_Error instance on failure.
0 ignored issues
show
Should the return type not be IXR_Error|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2038
	 */
2039
	public function wp_editTerm( $args ) {
2040
		if ( ! $this->minimum_args( $args, 5 ) )
2041
			return $this->error;
2042
2043
		$this->escape( $args );
2044
2045
		$username       = $args[1];
2046
		$password       = $args[2];
2047
		$term_id        = (int) $args[3];
2048
		$content_struct = $args[4];
2049
2050
		if ( ! $user = $this->login( $username, $password ) )
2051
			return $this->error;
2052
2053
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2054
		do_action( 'xmlrpc_call', 'wp.editTerm' );
2055
2056
		if ( ! taxonomy_exists( $content_struct['taxonomy'] ) )
2057
			return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2058
2059
		$taxonomy = get_taxonomy( $content_struct['taxonomy'] );
2060
2061
		$taxonomy = (array) $taxonomy;
2062
2063
		// hold the data of the term
2064
		$term_data = array();
2065
2066
		$term = get_term( $term_id , $content_struct['taxonomy'] );
2067
2068
		if ( is_wp_error( $term ) )
2069
			return new IXR_Error( 500, $term->get_error_message() );
0 ignored issues
show
The method get_error_message does only exist in WP_Error, but not in WP_Term.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
2070
2071
		if ( ! $term )
2072
			return new IXR_Error( 404, __( 'Invalid term ID.' ) );
2073
2074
		if ( ! current_user_can( 'edit_term', $term_id ) ) {
2075
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this term.' ) );
2076
		}
2077
2078
		if ( isset( $content_struct['name'] ) ) {
2079
			$term_data['name'] = trim( $content_struct['name'] );
2080
2081
			if ( empty( $term_data['name'] ) )
2082
				return new IXR_Error( 403, __( 'The term name cannot be empty.' ) );
2083
		}
2084
2085 View Code Duplication
		if ( ! empty( $content_struct['parent'] ) ) {
2086
			if ( ! $taxonomy['hierarchical'] )
2087
				return new IXR_Error( 403, __( "This taxonomy is not hierarchical so you can't set a parent." ) );
2088
2089
			$parent_term_id = (int) $content_struct['parent'];
2090
			$parent_term = get_term( $parent_term_id , $taxonomy['name'] );
2091
2092
			if ( is_wp_error( $parent_term ) )
2093
				return new IXR_Error( 500, $parent_term->get_error_message() );
2094
2095
			if ( ! $parent_term )
2096
				return new IXR_Error( 403, __( 'Parent term does not exist.' ) );
2097
2098
			$term_data['parent'] = $content_struct['parent'];
2099
		}
2100
2101
		if ( isset( $content_struct['description'] ) )
2102
			$term_data['description'] = $content_struct['description'];
2103
2104
		if ( isset( $content_struct['slug'] ) )
2105
			$term_data['slug'] = $content_struct['slug'];
2106
2107
		$term = wp_update_term( $term_id , $taxonomy['name'] , $term_data );
2108
2109
		if ( is_wp_error( $term ) )
2110
			return new IXR_Error( 500, $term->get_error_message() );
2111
2112
		if ( ! $term )
2113
			return new IXR_Error( 500, __( 'Sorry, editing the term failed.' ) );
2114
2115
		// Update term meta.
2116
		if ( isset( $content_struct['custom_fields'] ) ) {
2117
			$this->set_term_custom_fields( $term_id, $content_struct['custom_fields'] );
2118
		}
2119
2120
		return true;
2121
	}
2122
2123
	/**
2124
	 * Delete a term.
2125
	 *
2126
	 * @since 3.4.0
2127
	 *
2128
	 * @see wp_delete_term()
2129
	 *
2130
	 * @param array  $args {
2131
	 *     Method arguments. Note: arguments must be ordered as documented.
2132
	 *
2133
	 *     @type int    $blog_id      Blog ID (unused).
2134
	 *     @type string $username     Username.
2135
	 *     @type string $password     Password.
2136
	 *     @type string $taxnomy_name Taxonomy name.
2137
	 *     @type int    $term_id      Term ID.
2138
	 * }
2139
	 * @return bool|IXR_Error True on success, IXR_Error instance on failure.
0 ignored issues
show
Should the return type not be IXR_Error|boolean|integer|WP_Error?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2140
	 */
2141
	public function wp_deleteTerm( $args ) {
2142
		if ( ! $this->minimum_args( $args, 5 ) )
2143
			return $this->error;
2144
2145
		$this->escape( $args );
2146
2147
		$username           = $args[1];
2148
		$password           = $args[2];
2149
		$taxonomy           = $args[3];
2150
		$term_id            = (int) $args[4];
2151
2152
		if ( ! $user = $this->login( $username, $password ) )
2153
			return $this->error;
2154
2155
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2156
		do_action( 'xmlrpc_call', 'wp.deleteTerm' );
2157
2158
		if ( ! taxonomy_exists( $taxonomy ) )
2159
			return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2160
2161
		$taxonomy = get_taxonomy( $taxonomy );
2162
		$term = get_term( $term_id, $taxonomy->name );
2163
2164
		if ( is_wp_error( $term ) )
2165
			return new IXR_Error( 500, $term->get_error_message() );
0 ignored issues
show
The method get_error_message does only exist in WP_Error, but not in WP_Term.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
2166
2167
		if ( ! $term )
2168
			return new IXR_Error( 404, __( 'Invalid term ID.' ) );
2169
2170
		if ( ! current_user_can( 'delete_term', $term_id ) ) {
2171
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this term.' ) );
2172
		}
2173
2174
		$result = wp_delete_term( $term_id, $taxonomy->name );
2175
2176
		if ( is_wp_error( $result ) )
2177
			return new IXR_Error( 500, $term->get_error_message() );
2178
2179
		if ( ! $result )
2180
			return new IXR_Error( 500, __( 'Sorry, deleting the term failed.' ) );
2181
2182
		return $result;
2183
	}
2184
2185
	/**
2186
	 * Retrieve a term.
2187
	 *
2188
	 * @since 3.4.0
2189
	 *
2190
	 * @see get_term()
2191
	 *
2192
	 * @param array  $args {
2193
	 *     Method arguments. Note: arguments must be ordered as documented.
2194
	 *
2195
	 *     @type int    $blog_id  Blog ID (unused).
2196
	 *     @type string $username Username.
2197
	 *     @type string $password Password.
2198
	 *     @type string $taxnomy  Taxonomy name.
2199
	 *     @type string $term_id  Term ID.
2200
	 * }
2201
	 * @return array|IXR_Error IXR_Error on failure, array on success, containing:
2202
	 *  - 'term_id'
2203
	 *  - 'name'
2204
	 *  - 'slug'
2205
	 *  - 'term_group'
2206
	 *  - 'term_taxonomy_id'
2207
	 *  - 'taxonomy'
2208
	 *  - 'description'
2209
	 *  - 'parent'
2210
	 *  - 'count'
2211
	 */
2212
	public function wp_getTerm( $args ) {
2213
		if ( ! $this->minimum_args( $args, 5 ) )
2214
			return $this->error;
2215
2216
		$this->escape( $args );
2217
2218
		$username           = $args[1];
2219
		$password           = $args[2];
2220
		$taxonomy           = $args[3];
2221
		$term_id            = (int) $args[4];
2222
2223
		if ( ! $user = $this->login( $username, $password ) )
2224
			return $this->error;
2225
2226
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2227
		do_action( 'xmlrpc_call', 'wp.getTerm' );
2228
2229
		if ( ! taxonomy_exists( $taxonomy ) )
2230
			return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2231
2232
		$taxonomy = get_taxonomy( $taxonomy );
2233
2234
		$term = get_term( $term_id , $taxonomy->name, ARRAY_A );
2235
2236
		if ( is_wp_error( $term ) )
2237
			return new IXR_Error( 500, $term->get_error_message() );
0 ignored issues
show
The method get_error_message does only exist in WP_Error, but not in WP_Term.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
2238
2239
		if ( ! $term )
2240
			return new IXR_Error( 404, __( 'Invalid term ID.' ) );
2241
2242
		if ( ! current_user_can( 'assign_term', $term_id ) ) {
2243
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign this term.' ) );
2244
		}
2245
2246
		return $this->_prepare_term( $term );
2247
	}
2248
2249
	/**
2250
	 * Retrieve all terms for a taxonomy.
2251
	 *
2252
	 * @since 3.4.0
2253
	 *
2254
	 * The optional $filter parameter modifies the query used to retrieve terms.
2255
	 * Accepted keys are 'number', 'offset', 'orderby', 'order', 'hide_empty', and 'search'.
2256
	 *
2257
	 * @see get_terms()
2258
	 *
2259
	 * @param array  $args {
2260
	 *     Method arguments. Note: arguments must be ordered as documented.
2261
	 *
2262
	 *     @type int    $blog_id  Blog ID (unused).
2263
	 *     @type string $username Username.
2264
	 *     @type string $password Password.
2265
	 *     @type string $taxnomy  Taxonomy name.
2266
	 *     @type array  $filter   Optional. Modifies the query used to retrieve posts. Accepts 'number',
2267
	 *                            'offset', 'orderby', 'order', 'hide_empty', and 'search'. Default empty array.
2268
	 * }
2269
	 * @return array|IXR_Error An associative array of terms data on success, IXR_Error instance otherwise.
2270
	 */
2271
	public function wp_getTerms( $args ) {
2272
		if ( ! $this->minimum_args( $args, 4 ) )
2273
			return $this->error;
2274
2275
		$this->escape( $args );
2276
2277
		$username       = $args[1];
2278
		$password       = $args[2];
2279
		$taxonomy       = $args[3];
2280
		$filter         = isset( $args[4] ) ? $args[4] : array();
2281
2282
		if ( ! $user = $this->login( $username, $password ) )
2283
			return $this->error;
2284
2285
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2286
		do_action( 'xmlrpc_call', 'wp.getTerms' );
2287
2288
		if ( ! taxonomy_exists( $taxonomy ) )
2289
			return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2290
2291
		$taxonomy = get_taxonomy( $taxonomy );
2292
2293
		if ( ! current_user_can( $taxonomy->cap->assign_terms ) )
2294
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) );
2295
2296
		$query = array();
2297
2298
		if ( isset( $filter['number'] ) )
2299
			$query['number'] = absint( $filter['number'] );
2300
2301
		if ( isset( $filter['offset'] ) )
2302
			$query['offset'] = absint( $filter['offset'] );
2303
2304 View Code Duplication
		if ( isset( $filter['orderby'] ) ) {
2305
			$query['orderby'] = $filter['orderby'];
2306
2307
			if ( isset( $filter['order'] ) )
2308
				$query['order'] = $filter['order'];
2309
		}
2310
2311
		if ( isset( $filter['hide_empty'] ) )
2312
			$query['hide_empty'] = $filter['hide_empty'];
2313
		else
2314
			$query['get'] = 'all';
2315
2316
		if ( isset( $filter['search'] ) )
2317
			$query['search'] = $filter['search'];
2318
2319
		$terms = get_terms( $taxonomy->name, $query );
2320
2321
		if ( is_wp_error( $terms ) )
2322
			return new IXR_Error( 500, $terms->get_error_message() );
2323
2324
		$struct = array();
2325
2326
		foreach ( $terms as $term ) {
0 ignored issues
show
The expression $terms of type array|integer|object<WP_Error> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
2327
			$struct[] = $this->_prepare_term( $term );
2328
		}
2329
2330
		return $struct;
2331
	}
2332
2333
	/**
2334
	 * Retrieve a taxonomy.
2335
	 *
2336
	 * @since 3.4.0
2337
	 *
2338
	 * @see get_taxonomy()
2339
	 *
2340
	 * @param array  $args {
2341
	 *     Method arguments. Note: arguments must be ordered as documented.
2342
	 *
2343
	 *     @type int    $blog_id  Blog ID (unused).
2344
	 *     @type string $username Username.
2345
	 *     @type string $password Password.
2346
	 *     @type string $taxnomy  Taxonomy name.
2347
	 *     @type array  $fields   Optional. Array of taxonomy fields to limit to in the return.
2348
	 *                            Accepts 'labels', 'cap', 'menu', and 'object_type'.
2349
	 *                            Default empty array.
2350
	 * }
2351
	 * @return array|IXR_Error An array of taxonomy data on success, IXR_Error instance otherwise.
2352
	 */
2353 View Code Duplication
	public function wp_getTaxonomy( $args ) {
2354
		if ( ! $this->minimum_args( $args, 4 ) )
2355
			return $this->error;
2356
2357
		$this->escape( $args );
2358
2359
		$username = $args[1];
2360
		$password = $args[2];
2361
		$taxonomy = $args[3];
2362
2363
		if ( isset( $args[4] ) ) {
2364
			$fields = $args[4];
2365
		} else {
2366
			/**
2367
			 * Filters the taxonomy query fields used by the given XML-RPC method.
2368
			 *
2369
			 * @since 3.4.0
2370
			 *
2371
			 * @param array  $fields An array of taxonomy fields to retrieve.
2372
			 * @param string $method The method name.
2373
			 */
2374
			$fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomy' );
2375
		}
2376
2377
		if ( ! $user = $this->login( $username, $password ) )
2378
			return $this->error;
2379
2380
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2381
		do_action( 'xmlrpc_call', 'wp.getTaxonomy' );
2382
2383
		if ( ! taxonomy_exists( $taxonomy ) )
2384
			return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2385
2386
		$taxonomy = get_taxonomy( $taxonomy );
2387
2388
		if ( ! current_user_can( $taxonomy->cap->assign_terms ) )
2389
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) );
2390
2391
		return $this->_prepare_taxonomy( $taxonomy, $fields );
0 ignored issues
show
It seems like $taxonomy defined by get_taxonomy($taxonomy) on line 2386 can also be of type false; however, wp_xmlrpc_server::_prepare_taxonomy() does only seem to accept object, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
2392
	}
2393
2394
	/**
2395
	 * Retrieve all taxonomies.
2396
	 *
2397
	 * @since 3.4.0
2398
	 *
2399
	 * @see get_taxonomies()
2400
	 *
2401
	 * @param array  $args {
2402
	 *     Method arguments. Note: arguments must be ordered as documented.
2403
	 *
2404
	 *     @type int    $blog_id  Blog ID (unused).
2405
	 *     @type string $username Username.
2406
	 *     @type string $password Password.
2407
	 *     @type array  $filter   Optional. An array of arguments for retrieving taxonomies.
2408
	 *     @type array  $fields   Optional. The subset of taxonomy fields to return.
2409
	 * }
2410
	 * @return array|IXR_Error An associative array of taxonomy data with returned fields determined
2411
	 *                         by `$fields`, or an IXR_Error instance on failure.
2412
	 */
2413 View Code Duplication
	public function wp_getTaxonomies( $args ) {
2414
		if ( ! $this->minimum_args( $args, 3 ) )
2415
			return $this->error;
2416
2417
		$this->escape( $args );
2418
2419
		$username = $args[1];
2420
		$password = $args[2];
2421
		$filter   = isset( $args[3] ) ? $args[3] : array( 'public' => true );
2422
2423
		if ( isset( $args[4] ) ) {
2424
			$fields = $args[4];
2425
		} else {
2426
			/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2427
			$fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomies' );
2428
		}
2429
2430
		if ( ! $user = $this->login( $username, $password ) )
2431
			return $this->error;
2432
2433
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2434
		do_action( 'xmlrpc_call', 'wp.getTaxonomies' );
2435
2436
		$taxonomies = get_taxonomies( $filter, 'objects' );
2437
2438
		// holds all the taxonomy data
2439
		$struct = array();
2440
2441
		foreach ( $taxonomies as $taxonomy ) {
2442
			// capability check for post_types
2443
			if ( ! current_user_can( $taxonomy->cap->assign_terms ) )
2444
				continue;
2445
2446
			$struct[] = $this->_prepare_taxonomy( $taxonomy, $fields );
2447
		}
2448
2449
		return $struct;
2450
	}
2451
2452
	/**
2453
	 * Retrieve a user.
2454
	 *
2455
	 * The optional $fields parameter specifies what fields will be included
2456
	 * in the response array. This should be a list of field names. 'user_id' will
2457
	 * always be included in the response regardless of the value of $fields.
2458
	 *
2459
	 * Instead of, or in addition to, individual field names, conceptual group
2460
	 * names can be used to specify multiple fields. The available conceptual
2461
	 * groups are 'basic' and 'all'.
2462
	 *
2463
	 * @uses get_userdata()
2464
	 *
2465
	 * @param array  $args {
2466
	 *     Method arguments. Note: arguments must be ordered as documented.
2467
	 *
2468
	 *     @type int    $blog_id (unused)
2469
	 *     @type string $username
2470
	 *     @type string $password
2471
	 *     @type int    $user_id
2472
	 *     @type array  $fields (optional)
2473
	 * }
2474
	 * @return array|IXR_Error Array contains (based on $fields parameter):
2475
	 *  - 'user_id'
2476
	 *  - 'username'
2477
	 *  - 'first_name'
2478
	 *  - 'last_name'
2479
	 *  - 'registered'
2480
	 *  - 'bio'
2481
	 *  - 'email'
2482
	 *  - 'nickname'
2483
	 *  - 'nicename'
2484
	 *  - 'url'
2485
	 *  - 'display_name'
2486
	 *  - 'roles'
2487
	 */
2488
	public function wp_getUser( $args ) {
2489
		if ( ! $this->minimum_args( $args, 4 ) )
2490
			return $this->error;
2491
2492
		$this->escape( $args );
2493
2494
		$username = $args[1];
2495
		$password = $args[2];
2496
		$user_id  = (int) $args[3];
2497
2498 View Code Duplication
		if ( isset( $args[4] ) ) {
2499
			$fields = $args[4];
2500
		} else {
2501
			/**
2502
			 * Filters the default user query fields used by the given XML-RPC method.
2503
			 *
2504
			 * @since 3.5.0
2505
			 *
2506
			 * @param array  $fields User query fields for given method. Default 'all'.
2507
			 * @param string $method The method name.
2508
			 */
2509
			$fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUser' );
2510
		}
2511
2512
		if ( ! $user = $this->login( $username, $password ) )
2513
			return $this->error;
2514
2515
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2516
		do_action( 'xmlrpc_call', 'wp.getUser' );
2517
2518
		if ( ! current_user_can( 'edit_user', $user_id ) )
2519
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this user.' ) );
2520
2521
		$user_data = get_userdata( $user_id );
2522
2523
		if ( ! $user_data )
2524
			return new IXR_Error( 404, __( 'Invalid user ID.' ) );
2525
2526
		return $this->_prepare_user( $user_data, $fields );
2527
	}
2528
2529
	/**
2530
	 * Retrieve users.
2531
	 *
2532
	 * The optional $filter parameter modifies the query used to retrieve users.
2533
	 * Accepted keys are 'number' (default: 50), 'offset' (default: 0), 'role',
2534
	 * 'who', 'orderby', and 'order'.
2535
	 *
2536
	 * The optional $fields parameter specifies what fields will be included
2537
	 * in the response array.
2538
	 *
2539
	 * @uses get_users()
2540
	 * @see wp_getUser() for more on $fields and return values
2541
	 *
2542
	 * @param array  $args {
2543
	 *     Method arguments. Note: arguments must be ordered as documented.
2544
	 *
2545
	 *     @type int    $blog_id (unused)
2546
	 *     @type string $username
2547
	 *     @type string $password
2548
	 *     @type array  $filter (optional)
2549
	 *     @type array  $fields (optional)
2550
	 * }
2551
	 * @return array|IXR_Error users data
2552
	 */
2553
	public function wp_getUsers( $args ) {
2554
		if ( ! $this->minimum_args( $args, 3 ) )
2555
			return $this->error;
2556
2557
		$this->escape( $args );
2558
2559
		$username = $args[1];
2560
		$password = $args[2];
2561
		$filter   = isset( $args[3] ) ? $args[3] : array();
2562
2563 View Code Duplication
		if ( isset( $args[4] ) ) {
2564
			$fields = $args[4];
2565
		} else {
2566
			/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2567
			$fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUsers' );
2568
		}
2569
2570
		if ( ! $user = $this->login( $username, $password ) )
2571
			return $this->error;
2572
2573
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2574
		do_action( 'xmlrpc_call', 'wp.getUsers' );
2575
2576
		if ( ! current_user_can( 'list_users' ) )
2577
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to list users.' ) );
2578
2579
		$query = array( 'fields' => 'all_with_meta' );
2580
2581
		$query['number'] = ( isset( $filter['number'] ) ) ? absint( $filter['number'] ) : 50;
2582
		$query['offset'] = ( isset( $filter['offset'] ) ) ? absint( $filter['offset'] ) : 0;
2583
2584 View Code Duplication
		if ( isset( $filter['orderby'] ) ) {
2585
			$query['orderby'] = $filter['orderby'];
2586
2587
			if ( isset( $filter['order'] ) )
2588
				$query['order'] = $filter['order'];
2589
		}
2590
2591
		if ( isset( $filter['role'] ) ) {
2592
			if ( get_role( $filter['role'] ) === null )
2593
				return new IXR_Error( 403, __( 'Invalid role.' ) );
2594
2595
			$query['role'] = $filter['role'];
2596
		}
2597
2598
		if ( isset( $filter['who'] ) ) {
2599
			$query['who'] = $filter['who'];
2600
		}
2601
2602
		$users = get_users( $query );
2603
2604
		$_users = array();
2605
		foreach ( $users as $user_data ) {
2606
			if ( current_user_can( 'edit_user', $user_data->ID ) )
2607
				$_users[] = $this->_prepare_user( $user_data, $fields );
2608
		}
2609
		return $_users;
2610
	}
2611
2612
	/**
2613
	 * Retrieve information about the requesting user.
2614
	 *
2615
	 * @uses get_userdata()
2616
	 *
2617
	 * @param array  $args {
2618
	 *     Method arguments. Note: arguments must be ordered as documented.
2619
	 *
2620
	 *     @type int    $blog_id (unused)
2621
	 *     @type string $username
2622
	 *     @type string $password
2623
	 *     @type array  $fields (optional)
2624
	 * }
2625
	 * @return array|IXR_Error (@see wp_getUser)
2626
	 */
2627
	public function wp_getProfile( $args ) {
2628
		if ( ! $this->minimum_args( $args, 3 ) )
2629
			return $this->error;
2630
2631
		$this->escape( $args );
2632
2633
		$username = $args[1];
2634
		$password = $args[2];
2635
2636 View Code Duplication
		if ( isset( $args[3] ) ) {
2637
			$fields = $args[3];
2638
		} else {
2639
			/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2640
			$fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getProfile' );
2641
		}
2642
2643
		if ( ! $user = $this->login( $username, $password ) )
2644
			return $this->error;
2645
2646
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2647
		do_action( 'xmlrpc_call', 'wp.getProfile' );
2648
2649
		if ( ! current_user_can( 'edit_user', $user->ID ) )
2650
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit your profile.' ) );
2651
2652
		$user_data = get_userdata( $user->ID );
2653
2654
		return $this->_prepare_user( $user_data, $fields );
0 ignored issues
show
It seems like $user_data defined by get_userdata($user->ID) on line 2652 can also be of type false; however, wp_xmlrpc_server::_prepare_user() does only seem to accept object<WP_User>, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
2655
	}
2656
2657
	/**
2658
	 * Edit user's profile.
2659
	 *
2660
	 * @uses wp_update_user()
2661
	 *
2662
	 * @param array  $args {
2663
	 *     Method arguments. Note: arguments must be ordered as documented.
2664
	 *
2665
	 *     @type int    $blog_id (unused)
2666
	 *     @type string $username
2667
	 *     @type string $password
2668
	 *     @type array  $content_struct It can optionally contain:
2669
	 *      - 'first_name'
2670
	 *      - 'last_name'
2671
	 *      - 'website'
2672
	 *      - 'display_name'
2673
	 *      - 'nickname'
2674
	 *      - 'nicename'
2675
	 *      - 'bio'
2676
	 * }
2677
	 * @return true|IXR_Error True, on success.
0 ignored issues
show
Should the return type not be IXR_Error|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2678
	 */
2679
	public function wp_editProfile( $args ) {
2680
		if ( ! $this->minimum_args( $args, 4 ) )
2681
			return $this->error;
2682
2683
		$this->escape( $args );
2684
2685
		$username       = $args[1];
2686
		$password       = $args[2];
2687
		$content_struct = $args[3];
2688
2689
		if ( ! $user = $this->login( $username, $password ) )
2690
			return $this->error;
2691
2692
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2693
		do_action( 'xmlrpc_call', 'wp.editProfile' );
2694
2695
		if ( ! current_user_can( 'edit_user', $user->ID ) )
2696
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit your profile.' ) );
2697
2698
		// holds data of the user
2699
		$user_data = array();
2700
		$user_data['ID'] = $user->ID;
2701
2702
		// only set the user details if it was given
2703
		if ( isset( $content_struct['first_name'] ) )
2704
			$user_data['first_name'] = $content_struct['first_name'];
2705
2706
		if ( isset( $content_struct['last_name'] ) )
2707
			$user_data['last_name'] = $content_struct['last_name'];
2708
2709
		if ( isset( $content_struct['url'] ) )
2710
			$user_data['user_url'] = $content_struct['url'];
2711
2712
		if ( isset( $content_struct['display_name'] ) )
2713
			$user_data['display_name'] = $content_struct['display_name'];
2714
2715
		if ( isset( $content_struct['nickname'] ) )
2716
			$user_data['nickname'] = $content_struct['nickname'];
2717
2718
		if ( isset( $content_struct['nicename'] ) )
2719
			$user_data['user_nicename'] = $content_struct['nicename'];
2720
2721
		if ( isset( $content_struct['bio'] ) )
2722
			$user_data['description'] = $content_struct['bio'];
2723
2724
		$result = wp_update_user( $user_data );
2725
2726
		if ( is_wp_error( $result ) )
2727
			return new IXR_Error( 500, $result->get_error_message() );
2728
2729
		if ( ! $result )
2730
			return new IXR_Error( 500, __( 'Sorry, the user cannot be updated.' ) );
2731
2732
		return true;
2733
	}
2734
2735
	/**
2736
	 * Retrieve page.
2737
	 *
2738
	 * @since 2.2.0
2739
	 *
2740
	 * @param array  $args {
2741
	 *     Method arguments. Note: arguments must be ordered as documented.
2742
	 *
2743
	 *     @type int    $blog_id (unused)
2744
	 *     @type int    $page_id
2745
	 *     @type string $username
2746
	 *     @type string $password
2747
	 * }
2748
	 * @return array|IXR_Error
2749
	 */
2750
	public function wp_getPage( $args ) {
2751
		$this->escape( $args );
2752
2753
		$page_id  = (int) $args[1];
2754
		$username = $args[2];
2755
		$password = $args[3];
2756
2757
		if ( !$user = $this->login($username, $password) ) {
2758
			return $this->error;
2759
		}
2760
2761
		$page = get_post($page_id);
2762
		if ( ! $page )
2763
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
2764
2765
		if ( !current_user_can( 'edit_page', $page_id ) )
2766
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this page.' ) );
2767
2768
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2769
		do_action( 'xmlrpc_call', 'wp.getPage' );
2770
2771
		// If we found the page then format the data.
2772
		if ( $page->ID && ($page->post_type == 'page') ) {
2773
			return $this->_prepare_page( $page );
2774
		}
2775
		// If the page doesn't exist indicate that.
2776
		else {
2777
			return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
2778
		}
2779
	}
2780
2781
	/**
2782
	 * Retrieve Pages.
2783
	 *
2784
	 * @since 2.2.0
2785
	 *
2786
	 * @param array  $args {
2787
	 *     Method arguments. Note: arguments must be ordered as documented.
2788
	 *
2789
	 *     @type int    $blog_id (unused)
2790
	 *     @type string $username
2791
	 *     @type string $password
2792
	 *     @type int    $num_pages
2793
	 * }
2794
	 * @return array|IXR_Error
2795
	 */
2796
	public function wp_getPages( $args ) {
2797
		$this->escape( $args );
2798
2799
		$username  = $args[1];
2800
		$password  = $args[2];
2801
		$num_pages = isset($args[3]) ? (int) $args[3] : 10;
2802
2803
		if ( !$user = $this->login($username, $password) )
2804
			return $this->error;
2805
2806
		if ( !current_user_can( 'edit_pages' ) )
2807
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit pages.' ) );
2808
2809
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2810
		do_action( 'xmlrpc_call', 'wp.getPages' );
2811
2812
		$pages = get_posts( array('post_type' => 'page', 'post_status' => 'any', 'numberposts' => $num_pages) );
2813
		$num_pages = count($pages);
2814
2815
		// If we have pages, put together their info.
2816
		if ( $num_pages >= 1 ) {
2817
			$pages_struct = array();
2818
2819
			foreach ($pages as $page) {
2820
				if ( current_user_can( 'edit_page', $page->ID ) )
2821
					$pages_struct[] = $this->_prepare_page( $page );
2822
			}
2823
2824
			return $pages_struct;
2825
		}
2826
2827
		return array();
2828
	}
2829
2830
	/**
2831
	 * Create new page.
2832
	 *
2833
	 * @since 2.2.0
2834
	 *
2835
	 * @see wp_xmlrpc_server::mw_newPost()
2836
	 *
2837
	 * @param array  $args {
2838
	 *     Method arguments. Note: arguments must be ordered as documented.
2839
	 *
2840
	 *     @type int    $blog_id (unused)
2841
	 *     @type string $username
2842
	 *     @type string $password
2843
	 *     @type array  $content_struct
2844
	 * }
2845
	 * @return int|IXR_Error
0 ignored issues
show
Should the return type not be IXR_Error|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2846
	 */
2847
	public function wp_newPage( $args ) {
2848
		// Items not escaped here will be escaped in newPost.
2849
		$username = $this->escape( $args[1] );
2850
		$password = $this->escape( $args[2] );
2851
2852
		if ( !$user = $this->login($username, $password) )
2853
			return $this->error;
2854
2855
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2856
		do_action( 'xmlrpc_call', 'wp.newPage' );
2857
2858
		// Mark this as content for a page.
2859
		$args[3]["post_type"] = 'page';
2860
2861
		// Let mw_newPost do all of the heavy lifting.
2862
		return $this->mw_newPost( $args );
2863
	}
2864
2865
	/**
2866
	 * Delete page.
2867
	 *
2868
	 * @since 2.2.0
2869
	 *
2870
	 * @param array  $args {
2871
	 *     Method arguments. Note: arguments must be ordered as documented.
2872
	 *
2873
	 *     @type int    $blog_id (unused)
2874
	 *     @type string $username
2875
	 *     @type string $password
2876
	 *     @type int    $page_id
2877
	 * }
2878
	 * @return true|IXR_Error True, if success.
0 ignored issues
show
Should the return type not be IXR_Error|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2879
	 */
2880 View Code Duplication
	public function wp_deletePage( $args ) {
2881
		$this->escape( $args );
2882
2883
		$username = $args[1];
2884
		$password = $args[2];
2885
		$page_id  = (int) $args[3];
2886
2887
		if ( !$user = $this->login($username, $password) )
2888
			return $this->error;
2889
2890
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2891
		do_action( 'xmlrpc_call', 'wp.deletePage' );
2892
2893
		// Get the current page based on the page_id and
2894
		// make sure it is a page and not a post.
2895
		$actual_page = get_post($page_id, ARRAY_A);
2896
		if ( !$actual_page || ($actual_page['post_type'] != 'page') )
2897
			return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
2898
2899
		// Make sure the user can delete pages.
2900
		if ( !current_user_can('delete_page', $page_id) )
2901
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this page.' ) );
2902
2903
		// Attempt to delete the page.
2904
		$result = wp_delete_post($page_id);
2905
		if ( !$result )
2906
			return new IXR_Error( 500, __( 'Failed to delete the page.' ) );
2907
2908
		/**
2909
		 * Fires after a page has been successfully deleted via XML-RPC.
2910
		 *
2911
		 * @since 3.4.0
2912
		 *
2913
		 * @param int   $page_id ID of the deleted page.
2914
		 * @param array $args    An array of arguments to delete the page.
2915
		 */
2916
		do_action( 'xmlrpc_call_success_wp_deletePage', $page_id, $args );
2917
2918
		return true;
2919
	}
2920
2921
	/**
2922
	 * Edit page.
2923
	 *
2924
	 * @since 2.2.0
2925
	 *
2926
	 * @param array  $args {
2927
	 *     Method arguments. Note: arguments must be ordered as documented.
2928
	 *
2929
	 *     @type int    $blog_id (unused)
2930
	 *     @type int    $page_id
2931
	 *     @type string $username
2932
	 *     @type string $password
2933
	 *     @type string $content
2934
	 *     @type string $publish
2935
	 * }
2936
	 * @return array|IXR_Error
0 ignored issues
show
Should the return type not be IXR_Error|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2937
	 */
2938
	public function wp_editPage( $args ) {
2939
		// Items will be escaped in mw_editPost.
2940
		$page_id  = (int) $args[1];
2941
		$username = $args[2];
2942
		$password = $args[3];
2943
		$content  = $args[4];
2944
		$publish  = $args[5];
2945
2946
		$escaped_username = $this->escape( $username );
2947
		$escaped_password = $this->escape( $password );
2948
2949
		if ( !$user = $this->login( $escaped_username, $escaped_password ) ) {
2950
			return $this->error;
2951
		}
2952
2953
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2954
		do_action( 'xmlrpc_call', 'wp.editPage' );
2955
2956
		// Get the page data and make sure it is a page.
2957
		$actual_page = get_post($page_id, ARRAY_A);
2958
		if ( !$actual_page || ($actual_page['post_type'] != 'page') )
2959
			return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
2960
2961
		// Make sure the user is allowed to edit pages.
2962
		if ( !current_user_can('edit_page', $page_id) )
2963
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this page.' ) );
2964
2965
		// Mark this as content for a page.
2966
		$content['post_type'] = 'page';
2967
2968
		// Arrange args in the way mw_editPost understands.
2969
		$args = array(
2970
			$page_id,
2971
			$username,
2972
			$password,
2973
			$content,
2974
			$publish
2975
		);
2976
2977
		// Let mw_editPost do all of the heavy lifting.
2978
		return $this->mw_editPost( $args );
2979
	}
2980
2981
	/**
2982
	 * Retrieve page list.
2983
	 *
2984
	 * @since 2.2.0
2985
	 *
2986
	 * @global wpdb $wpdb WordPress database abstraction object.
2987
	 *
2988
	 * @param array  $args {
2989
	 *     Method arguments. Note: arguments must be ordered as documented.
2990
	 *
2991
	 *     @type int    $blog_id (unused)
2992
	 *     @type string $username
2993
	 *     @type string $password
2994
	 * }
2995
	 * @return array|IXR_Error
2996
	 */
2997
	public function wp_getPageList( $args ) {
2998
		global $wpdb;
2999
3000
		$this->escape( $args );
3001
3002
		$username = $args[1];
3003
		$password = $args[2];
3004
3005
		if ( !$user = $this->login($username, $password) )
3006
			return $this->error;
3007
3008
		if ( !current_user_can( 'edit_pages' ) )
3009
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit pages.' ) );
3010
3011
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3012
		do_action( 'xmlrpc_call', 'wp.getPageList' );
3013
3014
		// Get list of pages ids and titles
3015
		$page_list = $wpdb->get_results("
3016
			SELECT ID page_id,
3017
				post_title page_title,
3018
				post_parent page_parent_id,
3019
				post_date_gmt,
3020
				post_date,
3021
				post_status
3022
			FROM {$wpdb->posts}
3023
			WHERE post_type = 'page'
3024
			ORDER BY ID
3025
		");
3026
3027
		// The date needs to be formatted properly.
3028
		$num_pages = count($page_list);
3029
		for ( $i = 0; $i < $num_pages; $i++ ) {
3030
			$page_list[$i]->dateCreated = $this->_convert_date(  $page_list[$i]->post_date );
3031
			$page_list[$i]->date_created_gmt = $this->_convert_date_gmt( $page_list[$i]->post_date_gmt, $page_list[$i]->post_date );
3032
3033
			unset($page_list[$i]->post_date_gmt);
3034
			unset($page_list[$i]->post_date);
3035
			unset($page_list[$i]->post_status);
3036
		}
3037
3038
		return $page_list;
3039
	}
3040
3041
	/**
3042
	 * Retrieve authors list.
3043
	 *
3044
	 * @since 2.2.0
3045
	 *
3046
	 * @param array  $args {
3047
	 *     Method arguments. Note: arguments must be ordered as documented.
3048
	 *
3049
	 *     @type int    $blog_id (unused)
3050
	 *     @type string $username
3051
	 *     @type string $password
3052
	 * }
3053
	 * @return array|IXR_Error
3054
	 */
3055
	public function wp_getAuthors( $args ) {
3056
		$this->escape( $args );
3057
3058
		$username = $args[1];
3059
		$password = $args[2];
3060
3061
		if ( !$user = $this->login($username, $password) )
3062
			return $this->error;
3063
3064
		if ( !current_user_can('edit_posts') )
3065
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
3066
3067
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3068
		do_action( 'xmlrpc_call', 'wp.getAuthors' );
3069
3070
		$authors = array();
3071
		foreach ( get_users( array( 'fields' => array('ID','user_login','display_name') ) ) as $user ) {
3072
			$authors[] = array(
3073
				'user_id'       => $user->ID,
3074
				'user_login'    => $user->user_login,
3075
				'display_name'  => $user->display_name
3076
			);
3077
		}
3078
3079
		return $authors;
3080
	}
3081
3082
	/**
3083
	 * Get list of all tags
3084
	 *
3085
	 * @since 2.7.0
3086
	 *
3087
	 * @param array  $args {
3088
	 *     Method arguments. Note: arguments must be ordered as documented.
3089
	 *
3090
	 *     @type int    $blog_id (unused)
3091
	 *     @type string $username
3092
	 *     @type string $password
3093
	 * }
3094
	 * @return array|IXR_Error
3095
	 */
3096
	public function wp_getTags( $args ) {
3097
		$this->escape( $args );
3098
3099
		$username = $args[1];
3100
		$password = $args[2];
3101
3102
		if ( !$user = $this->login($username, $password) )
3103
			return $this->error;
3104
3105
		if ( !current_user_can( 'edit_posts' ) )
3106
			return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view tags.' ) );
3107
3108
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3109
		do_action( 'xmlrpc_call', 'wp.getKeywords' );
3110
3111
		$tags = array();
3112
3113
		if ( $all_tags = get_tags() ) {
3114
			foreach ( (array) $all_tags as $tag ) {
3115
				$struct = array();
3116
				$struct['tag_id']			= $tag->term_id;
3117
				$struct['name']				= $tag->name;
3118
				$struct['count']			= $tag->count;
3119
				$struct['slug']				= $tag->slug;
3120
				$struct['html_url']			= esc_html( get_tag_link( $tag->term_id ) );
0 ignored issues
show
It seems like get_tag_link($tag->term_id) targeting get_tag_link() can also be of type object<WP_Error>; however, esc_html() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
3121
				$struct['rss_url']			= esc_html( get_tag_feed_link( $tag->term_id ) );
0 ignored issues
show
It seems like get_tag_feed_link($tag->term_id) targeting get_tag_feed_link() can also be of type false; however, esc_html() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
3122
3123
				$tags[] = $struct;
3124
			}
3125
		}
3126
3127
		return $tags;
3128
	}
3129
3130
	/**
3131
	 * Create new category.
3132
	 *
3133
	 * @since 2.2.0
3134
	 *
3135
	 * @param array  $args {
3136
	 *     Method arguments. Note: arguments must be ordered as documented.
3137
	 *
3138
	 *     @type int    $blog_id (unused)
3139
	 *     @type string $username
3140
	 *     @type string $password
3141
	 *     @type array  $category
3142
	 * }
3143
	 * @return int|IXR_Error Category ID.
0 ignored issues
show
Should the return type not be integer|object?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
3144
	 */
3145
	public function wp_newCategory( $args ) {
3146
		$this->escape( $args );
3147
3148
		$username = $args[1];
3149
		$password = $args[2];
3150
		$category = $args[3];
3151
3152
		if ( !$user = $this->login($username, $password) )
3153
			return $this->error;
3154
3155
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3156
		do_action( 'xmlrpc_call', 'wp.newCategory' );
3157
3158
		// Make sure the user is allowed to add a category.
3159
		if ( !current_user_can('manage_categories') )
3160
			return new IXR_Error(401, __('Sorry, you are not allowed to add a category.'));
3161
3162
		// If no slug was provided make it empty so that
3163
		// WordPress will generate one.
3164
		if ( empty($category['slug']) )
3165
			$category['slug'] = '';
3166
3167
		// If no parent_id was provided make it empty
3168
		// so that it will be a top level page (no parent).
3169
		if ( !isset($category['parent_id']) )
3170
			$category['parent_id'] = '';
3171
3172
		// If no description was provided make it empty.
3173
		if ( empty($category["description"]) )
3174
			$category["description"] = "";
3175
3176
		$new_category = array(
3177
			'cat_name'				=> $category['name'],
3178
			'category_nicename'		=> $category['slug'],
3179
			'category_parent'		=> $category['parent_id'],
3180
			'category_description'	=> $category['description']
3181
		);
3182
3183
		$cat_id = wp_insert_category($new_category, true);
3184
		if ( is_wp_error( $cat_id ) ) {
3185
			if ( 'term_exists' == $cat_id->get_error_code() )
3186
				return (int) $cat_id->get_error_data();
3187
			else
3188
				return new IXR_Error(500, __('Sorry, the new category failed.'));
3189
		} elseif ( ! $cat_id ) {
3190
			return new IXR_Error(500, __('Sorry, the new category failed.'));
3191
		}
3192
3193
		/**
3194
		 * Fires after a new category has been successfully created via XML-RPC.
3195
		 *
3196
		 * @since 3.4.0
3197
		 *
3198
		 * @param int   $cat_id ID of the new category.
3199
		 * @param array $args   An array of new category arguments.
3200
		 */
3201
		do_action( 'xmlrpc_call_success_wp_newCategory', $cat_id, $args );
3202
3203
		return $cat_id;
3204
	}
3205
3206
	/**
3207
	 * Remove category.
3208
	 *
3209
	 * @since 2.5.0
3210
	 *
3211
	 * @param array  $args {
3212
	 *     Method arguments. Note: arguments must be ordered as documented.
3213
	 *
3214
	 *     @type int    $blog_id (unused)
3215
	 *     @type string $username
3216
	 *     @type string $password
3217
	 *     @type int    $category_id
3218
	 * }
3219
	 * @return bool|IXR_Error See wp_delete_term() for return info.
0 ignored issues
show
Should the return type not be IXR_Error|boolean|integer|WP_Error?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
3220
	 */
3221
	public function wp_deleteCategory( $args ) {
3222
		$this->escape( $args );
3223
3224
		$username    = $args[1];
3225
		$password    = $args[2];
3226
		$category_id = (int) $args[3];
3227
3228
		if ( !$user = $this->login($username, $password) )
3229
			return $this->error;
3230
3231
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3232
		do_action( 'xmlrpc_call', 'wp.deleteCategory' );
3233
3234
		if ( !current_user_can('manage_categories') )
3235
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete a category.' ) );
3236
3237
		$status = wp_delete_term( $category_id, 'category' );
3238
3239
		if ( true == $status ) {
3240
			/**
3241
			 * Fires after a category has been successfully deleted via XML-RPC.
3242
			 *
3243
			 * @since 3.4.0
3244
			 *
3245
			 * @param int   $category_id ID of the deleted category.
3246
			 * @param array $args        An array of arguments to delete the category.
3247
			 */
3248
			do_action( 'xmlrpc_call_success_wp_deleteCategory', $category_id, $args );
3249
		}
3250
3251
		return $status;
3252
	}
3253
3254
	/**
3255
	 * Retrieve category list.
3256
	 *
3257
	 * @since 2.2.0
3258
	 *
3259
	 * @param array  $args {
3260
	 *     Method arguments. Note: arguments must be ordered as documented.
3261
	 *
3262
	 *     @type int    $blog_id (unused)
3263
	 *     @type string $username
3264
	 *     @type string $password
3265
	 *     @type array  $category
3266
	 *     @type int    $max_results
3267
	 * }
3268
	 * @return array|IXR_Error
3269
	 */
3270
	public function wp_suggestCategories( $args ) {
3271
		$this->escape( $args );
3272
3273
		$username    = $args[1];
3274
		$password    = $args[2];
3275
		$category    = $args[3];
3276
		$max_results = (int) $args[4];
3277
3278
		if ( !$user = $this->login($username, $password) )
3279
			return $this->error;
3280
3281
		if ( !current_user_can( 'edit_posts' ) )
3282
			return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
3283
3284
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3285
		do_action( 'xmlrpc_call', 'wp.suggestCategories' );
3286
3287
		$category_suggestions = array();
3288
		$args = array('get' => 'all', 'number' => $max_results, 'name__like' => $category);
3289
		foreach ( (array) get_categories($args) as $cat ) {
3290
			$category_suggestions[] = array(
3291
				'category_id'	=> $cat->term_id,
3292
				'category_name'	=> $cat->name
3293
			);
3294
		}
3295
3296
		return $category_suggestions;
3297
	}
3298
3299
	/**
3300
	 * Retrieve comment.
3301
	 *
3302
	 * @since 2.7.0
3303
	 *
3304
	 * @param array  $args {
3305
	 *     Method arguments. Note: arguments must be ordered as documented.
3306
	 *
3307
	 *     @type int    $blog_id (unused)
3308
	 *     @type string $username
3309
	 *     @type string $password
3310
	 *     @type int    $comment_id
3311
	 * }
3312
	 * @return array|IXR_Error
3313
	 */
3314 View Code Duplication
	public function wp_getComment($args) {
3315
		$this->escape($args);
3316
3317
		$username	= $args[1];
3318
		$password	= $args[2];
3319
		$comment_id	= (int) $args[3];
3320
3321
		if ( ! $user = $this->login( $username, $password ) ) {
3322
			return $this->error;
3323
		}
3324
3325
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3326
		do_action( 'xmlrpc_call', 'wp.getComment' );
3327
3328
		if ( ! $comment = get_comment( $comment_id ) ) {
3329
			return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
3330
		}
3331
3332
		if ( ! current_user_can( 'edit_comment', $comment_id ) ) {
3333
			return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) );
3334
		}
3335
3336
		return $this->_prepare_comment( $comment );
3337
	}
3338
3339
	/**
3340
	 * Retrieve comments.
3341
	 *
3342
	 * Besides the common blog_id (unused), username, and password arguments, it takes a filter
3343
	 * array as last argument.
3344
	 *
3345
	 * Accepted 'filter' keys are 'status', 'post_id', 'offset', and 'number'.
3346
	 *
3347
	 * The defaults are as follows:
3348
	 * - 'status' - Default is ''. Filter by status (e.g., 'approve', 'hold')
3349
	 * - 'post_id' - Default is ''. The post where the comment is posted. Empty string shows all comments.
3350
	 * - 'number' - Default is 10. Total number of media items to retrieve.
3351
	 * - 'offset' - Default is 0. See WP_Query::query() for more.
3352
	 *
3353
	 * @since 2.7.0
3354
	 *
3355
	 * @param array  $args {
3356
	 *     Method arguments. Note: arguments must be ordered as documented.
3357
	 *
3358
	 *     @type int    $blog_id (unused)
3359
	 *     @type string $username
3360
	 *     @type string $password
3361
	 *     @type array  $struct
3362
	 * }
3363
	 * @return array|IXR_Error Contains a collection of comments. See wp_xmlrpc_server::wp_getComment() for a description of each item contents
3364
	 */
3365
	public function wp_getComments( $args ) {
3366
		$this->escape( $args );
3367
3368
		$username = $args[1];
3369
		$password = $args[2];
3370
		$struct	  = isset( $args[3] ) ? $args[3] : array();
3371
3372
		if ( ! $user = $this->login( $username, $password ) ) {
3373
			return $this->error;
3374
		}
3375
3376
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3377
		do_action( 'xmlrpc_call', 'wp.getComments' );
3378
3379
		if ( isset( $struct['status'] ) ) {
3380
			$status = $struct['status'];
3381
		} else {
3382
			$status = '';
3383
		}
3384
3385
		if ( ! current_user_can( 'moderate_comments' ) && 'approve' !== $status ) {
3386
			return new IXR_Error( 401, __( 'Invalid comment status.' ) );
3387
		}
3388
3389
		$post_id = '';
3390
		if ( isset( $struct['post_id'] ) ) {
3391
			$post_id = absint( $struct['post_id'] );
3392
		}
3393
3394
		$post_type = '';
3395 View Code Duplication
		if ( isset( $struct['post_type'] ) ) {
3396
			$post_type_object = get_post_type_object( $struct['post_type'] );
3397
			if ( ! $post_type_object || ! post_type_supports( $post_type_object->name, 'comments' ) ) {
3398
				return new IXR_Error( 404, __( 'Invalid post type.' ) );
3399
			}
3400
			$post_type = $struct['post_type'];
3401
		}
3402
3403
		$offset = 0;
3404
		if ( isset( $struct['offset'] ) ) {
3405
			$offset = absint( $struct['offset'] );
3406
		}
3407
3408
		$number = 10;
3409
		if ( isset( $struct['number'] ) ) {
3410
			$number = absint( $struct['number'] );
3411
		}
3412
3413
		$comments = get_comments( array(
3414
			'status' => $status,
3415
			'post_id' => $post_id,
3416
			'offset' => $offset,
3417
			'number' => $number,
3418
			'post_type' => $post_type,
3419
		) );
3420
3421
		$comments_struct = array();
3422
		if ( is_array( $comments ) ) {
3423
			foreach ( $comments as $comment ) {
3424
				$comments_struct[] = $this->_prepare_comment( $comment );
3425
			}
3426
		}
3427
3428
		return $comments_struct;
3429
	}
3430
3431
	/**
3432
	 * Delete a comment.
3433
	 *
3434
	 * By default, the comment will be moved to the trash instead of deleted.
3435
	 * See wp_delete_comment() for more information on this behavior.
3436
	 *
3437
	 * @since 2.7.0
3438
	 *
3439
	 * @param array  $args {
3440
	 *     Method arguments. Note: arguments must be ordered as documented.
3441
	 *
3442
	 *     @type int    $blog_id (unused)
3443
	 *     @type string $username
3444
	 *     @type string $password
3445
	 *     @type int    $comment_ID
3446
	 * }
3447
	 * @return bool|IXR_Error See wp_delete_comment().
3448
	 */
3449
	public function wp_deleteComment( $args ) {
3450
		$this->escape($args);
3451
3452
		$username	= $args[1];
3453
		$password	= $args[2];
3454
		$comment_ID	= (int) $args[3];
3455
3456
		if ( ! $user = $this->login( $username, $password ) ) {
3457
			return $this->error;
3458
		}
3459
3460
		if ( ! get_comment( $comment_ID ) ) {
3461
			return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
3462
		}
3463
3464
		if ( !current_user_can( 'edit_comment', $comment_ID ) ) {
3465
			return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) );
3466
		}
3467
3468
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3469
		do_action( 'xmlrpc_call', 'wp.deleteComment' );
3470
3471
		$status = wp_delete_comment( $comment_ID );
3472
3473
		if ( $status ) {
3474
			/**
3475
			 * Fires after a comment has been successfully deleted via XML-RPC.
3476
			 *
3477
			 * @since 3.4.0
3478
			 *
3479
			 * @param int   $comment_ID ID of the deleted comment.
3480
			 * @param array $args       An array of arguments to delete the comment.
3481
			 */
3482
			do_action( 'xmlrpc_call_success_wp_deleteComment', $comment_ID, $args );
3483
		}
3484
3485
		return $status;
3486
	}
3487
3488
	/**
3489
	 * Edit comment.
3490
	 *
3491
	 * Besides the common blog_id (unused), username, and password arguments, it takes a
3492
	 * comment_id integer and a content_struct array as last argument.
3493
	 *
3494
	 * The allowed keys in the content_struct array are:
3495
	 *  - 'author'
3496
	 *  - 'author_url'
3497
	 *  - 'author_email'
3498
	 *  - 'content'
3499
	 *  - 'date_created_gmt'
3500
	 *  - 'status'. Common statuses are 'approve', 'hold', 'spam'. See get_comment_statuses() for more details
3501
	 *
3502
	 * @since 2.7.0
3503
	 *
3504
	 * @param array  $args {
3505
	 *     Method arguments. Note: arguments must be ordered as documented.
3506
	 *
3507
	 *     @type int    $blog_id (unused)
3508
	 *     @type string $username
3509
	 *     @type string $password
3510
	 *     @type int    $comment_ID
3511
	 *     @type array  $content_struct
3512
	 * }
3513
	 * @return true|IXR_Error True, on success.
0 ignored issues
show
Should the return type not be IXR_Error|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
3514
	 */
3515
	public function wp_editComment( $args ) {
3516
		$this->escape( $args );
3517
3518
		$username	= $args[1];
3519
		$password	= $args[2];
3520
		$comment_ID	= (int) $args[3];
3521
		$content_struct = $args[4];
3522
3523
		if ( !$user = $this->login( $username, $password ) ) {
3524
			return $this->error;
3525
		}
3526
3527
		if ( ! get_comment( $comment_ID ) ) {
3528
			return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
3529
		}
3530
3531
		if ( ! current_user_can( 'edit_comment', $comment_ID ) ) {
3532
			return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) );
3533
		}
3534
3535
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3536
		do_action( 'xmlrpc_call', 'wp.editComment' );
3537
3538 View Code Duplication
		if ( isset($content_struct['status']) ) {
3539
			$statuses = get_comment_statuses();
3540
			$statuses = array_keys($statuses);
3541
3542
			if ( ! in_array($content_struct['status'], $statuses) )
3543
				return new IXR_Error( 401, __( 'Invalid comment status.' ) );
3544
			$comment_approved = $content_struct['status'];
3545
		}
3546
3547
		// Do some timestamp voodoo
3548
		if ( !empty( $content_struct['date_created_gmt'] ) ) {
3549
			// We know this is supposed to be GMT, so we're going to slap that Z on there by force
3550
			$dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
3551
			$comment_date = get_date_from_gmt(iso8601_to_datetime($dateCreated));
3552
			$comment_date_gmt = iso8601_to_datetime($dateCreated, 'GMT');
3553
		}
3554
3555
		if ( isset($content_struct['content']) )
3556
			$comment_content = $content_struct['content'];
3557
3558
		if ( isset($content_struct['author']) )
3559
			$comment_author = $content_struct['author'];
3560
3561
		if ( isset($content_struct['author_url']) )
3562
			$comment_author_url = $content_struct['author_url'];
3563
3564
		if ( isset($content_struct['author_email']) )
3565
			$comment_author_email = $content_struct['author_email'];
3566
3567
		// We've got all the data -- post it:
3568
		$comment = compact('comment_ID', 'comment_content', 'comment_approved', 'comment_date', 'comment_date_gmt', 'comment_author', 'comment_author_email', 'comment_author_url');
3569
3570
		$result = wp_update_comment($comment);
3571
		if ( is_wp_error( $result ) )
3572
			return new IXR_Error(500, $result->get_error_message());
0 ignored issues
show
The method get_error_message cannot be called on $result (of type integer).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
3573
3574
		if ( !$result )
3575
			return new IXR_Error(500, __('Sorry, the comment could not be edited.'));
3576
3577
		/**
3578
		 * Fires after a comment has been successfully updated via XML-RPC.
3579
		 *
3580
		 * @since 3.4.0
3581
		 *
3582
		 * @param int   $comment_ID ID of the updated comment.
3583
		 * @param array $args       An array of arguments to update the comment.
3584
		 */
3585
		do_action( 'xmlrpc_call_success_wp_editComment', $comment_ID, $args );
3586
3587
		return true;
3588
	}
3589
3590
	/**
3591
	 * Create new comment.
3592
	 *
3593
	 * @since 2.7.0
3594
	 *
3595
	 * @param array  $args {
3596
	 *     Method arguments. Note: arguments must be ordered as documented.
3597
	 *
3598
	 *     @type int        $blog_id (unused)
3599
	 *     @type string     $username
3600
	 *     @type string     $password
3601
	 *     @type string|int $post
3602
	 *     @type array      $content_struct
3603
	 * }
3604
	 * @return int|IXR_Error See wp_new_comment().
0 ignored issues
show
Should the return type not be IXR_Error|integer|string|WP_Error|WP_Comment?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
3605
	 */
3606
	public function wp_newComment($args) {
3607
		$this->escape($args);
3608
3609
		$username       = $args[1];
3610
		$password       = $args[2];
3611
		$post           = $args[3];
3612
		$content_struct = $args[4];
3613
3614
		/**
3615
		 * Filters whether to allow anonymous comments over XML-RPC.
3616
		 *
3617
		 * @since 2.7.0
3618
		 *
3619
		 * @param bool $allow Whether to allow anonymous commenting via XML-RPC.
3620
		 *                    Default false.
3621
		 */
3622
		$allow_anon = apply_filters( 'xmlrpc_allow_anonymous_comments', false );
3623
3624
		$user = $this->login($username, $password);
3625
3626
		if ( !$user ) {
3627
			$logged_in = false;
3628
			if ( $allow_anon && get_option('comment_registration') ) {
3629
				return new IXR_Error( 403, __( 'You must be registered to comment.' ) );
3630
			} elseif ( ! $allow_anon ) {
3631
				return $this->error;
3632
			}
3633
		} else {
3634
			$logged_in = true;
3635
		}
3636
3637
		if ( is_numeric($post) )
3638
			$post_id = absint($post);
3639
		else
3640
			$post_id = url_to_postid($post);
3641
3642
		if ( ! $post_id ) {
3643
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
3644
		}
3645
3646
		if ( ! get_post( $post_id ) ) {
3647
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
3648
		}
3649
3650
		if ( ! comments_open( $post_id ) ) {
3651
			return new IXR_Error( 403, __( 'Sorry, comments are closed for this item.' ) );
3652
		}
3653
3654
		if ( empty( $content_struct['content'] ) ) {
3655
			return new IXR_Error( 403, __( 'Comment is required.' ) );
3656
		}
3657
3658
		$comment = array(
3659
			'comment_post_ID' => $post_id,
3660
			'comment_content' => $content_struct['content'],
3661
		);
3662
3663
		if ( $logged_in ) {
3664
			$display_name = $user->display_name;
3665
			$user_email = $user->user_email;
3666
			$user_url = $user->user_url;
3667
3668
			$comment['comment_author'] = $this->escape( $display_name );
3669
			$comment['comment_author_email'] = $this->escape( $user_email );
3670
			$comment['comment_author_url'] = $this->escape( $user_url );
3671
			$comment['user_ID'] = $user->ID;
3672
		} else {
3673
			$comment['comment_author'] = '';
3674
			if ( isset($content_struct['author']) )
3675
				$comment['comment_author'] = $content_struct['author'];
3676
3677
			$comment['comment_author_email'] = '';
3678
			if ( isset($content_struct['author_email']) )
3679
				$comment['comment_author_email'] = $content_struct['author_email'];
3680
3681
			$comment['comment_author_url'] = '';
3682
			if ( isset($content_struct['author_url']) )
3683
				$comment['comment_author_url'] = $content_struct['author_url'];
3684
3685
			$comment['user_ID'] = 0;
3686
3687
			if ( get_option('require_name_email') ) {
3688
				if ( 6 > strlen($comment['comment_author_email']) || '' == $comment['comment_author'] )
3689
					return new IXR_Error( 403, __( 'Comment author name and email are required.' ) );
3690
				elseif ( !is_email($comment['comment_author_email']) )
3691
					return new IXR_Error( 403, __( 'A valid email address is required.' ) );
3692
			}
3693
		}
3694
3695
		$comment['comment_parent'] = isset($content_struct['comment_parent']) ? absint($content_struct['comment_parent']) : 0;
3696
3697
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3698
		do_action( 'xmlrpc_call', 'wp.newComment' );
3699
3700
		$comment_ID = wp_new_comment( $comment, true );
3701
		if ( is_wp_error( $comment_ID ) ) {
3702
			return new IXR_Error( 403, $comment_ID->get_error_message() );
0 ignored issues
show
The method get_error_message does only exist in WP_Error, but not in WP_Comment.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
3703
		}
3704
3705
		if ( ! $comment_ID ) {
3706
			return new IXR_Error( 403, __( 'An unknown error occurred' ) );
3707
		}
3708
3709
		/**
3710
		 * Fires after a new comment has been successfully created via XML-RPC.
3711
		 *
3712
		 * @since 3.4.0
3713
		 *
3714
		 * @param int   $comment_ID ID of the new comment.
3715
		 * @param array $args       An array of new comment arguments.
3716
		 */
3717
		do_action( 'xmlrpc_call_success_wp_newComment', $comment_ID, $args );
3718
3719
		return $comment_ID;
3720
	}
3721
3722
	/**
3723
	 * Retrieve all of the comment status.
3724
	 *
3725
	 * @since 2.7.0
3726
	 *
3727
	 * @param array  $args {
3728
	 *     Method arguments. Note: arguments must be ordered as documented.
3729
	 *
3730
	 *     @type int    $blog_id (unused)
3731
	 *     @type string $username
3732
	 *     @type string $password
3733
	 * }
3734
	 * @return array|IXR_Error
0 ignored issues
show
Consider making the return type a bit more specific; maybe use IXR_Error|array<string,string|null>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
3735
	 */
3736 View Code Duplication
	public function wp_getCommentStatusList( $args ) {
3737
		$this->escape( $args );
3738
3739
		$username = $args[1];
3740
		$password = $args[2];
3741
3742
		if ( ! $user = $this->login( $username, $password ) ) {
3743
			return $this->error;
3744
		}
3745
3746
		if ( ! current_user_can( 'publish_posts' ) ) {
3747
			return new IXR_Error( 403, __( 'Sorry, you are not allowed access to details about this site.' ) );
3748
		}
3749
3750
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3751
		do_action( 'xmlrpc_call', 'wp.getCommentStatusList' );
3752
3753
		return get_comment_statuses();
3754
	}
3755
3756
	/**
3757
	 * Retrieve comment count.
3758
	 *
3759
	 * @since 2.5.0
3760
	 *
3761
	 * @param array  $args {
3762
	 *     Method arguments. Note: arguments must be ordered as documented.
3763
	 *
3764
	 *     @type int    $blog_id (unused)
3765
	 *     @type string $username
3766
	 *     @type string $password
3767
	 *     @type int    $post_id
3768
	 * }
3769
	 * @return array|IXR_Error
0 ignored issues
show
Consider making the return type a bit more specific; maybe use IXR_Error|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
3770
	 */
3771
	public function wp_getCommentCount( $args ) {
3772
		$this->escape( $args );
3773
3774
		$username	= $args[1];
3775
		$password	= $args[2];
3776
		$post_id	= (int) $args[3];
3777
3778
		if ( ! $user = $this->login( $username, $password ) ) {
3779
			return $this->error;
3780
		}
3781
3782
		$post = get_post( $post_id, ARRAY_A );
3783
		if ( empty( $post['ID'] ) ) {
3784
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
3785
		}
3786
3787
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
3788
			return new IXR_Error( 403, __( 'Sorry, you are not allowed access to details of this post.' ) );
3789
		}
3790
3791
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3792
		do_action( 'xmlrpc_call', 'wp.getCommentCount' );
3793
3794
		$count = wp_count_comments( $post_id );
3795
3796
		return array(
3797
			'approved' => $count->approved,
3798
			'awaiting_moderation' => $count->moderated,
3799
			'spam' => $count->spam,
3800
			'total_comments' => $count->total_comments
3801
		);
3802
	}
3803
3804
	/**
3805
	 * Retrieve post statuses.
3806
	 *
3807
	 * @since 2.5.0
3808
	 *
3809
	 * @param array  $args {
3810
	 *     Method arguments. Note: arguments must be ordered as documented.
3811
	 *
3812
	 *     @type int    $blog_id (unused)
3813
	 *     @type string $username
3814
	 *     @type string $password
3815
	 * }
3816
	 * @return array|IXR_Error
0 ignored issues
show
Consider making the return type a bit more specific; maybe use IXR_Error|array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
3817
	 */
3818 View Code Duplication
	public function wp_getPostStatusList( $args ) {
3819
		$this->escape( $args );
3820
3821
		$username = $args[1];
3822
		$password = $args[2];
3823
3824
		if ( !$user = $this->login($username, $password) )
3825
			return $this->error;
3826
3827
		if ( !current_user_can( 'edit_posts' ) )
3828
			return new IXR_Error( 403, __( 'Sorry, you are not allowed access to details about this site.' ) );
3829
3830
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3831
		do_action( 'xmlrpc_call', 'wp.getPostStatusList' );
3832
3833
		return get_post_statuses();
3834
	}
3835
3836
	/**
3837
	 * Retrieve page statuses.
3838
	 *
3839
	 * @since 2.5.0
3840
	 *
3841
	 * @param array  $args {
3842
	 *     Method arguments. Note: arguments must be ordered as documented.
3843
	 *
3844
	 *     @type int    $blog_id (unused)
3845
	 *     @type string $username
3846
	 *     @type string $password
3847
	 * }
3848
	 * @return array|IXR_Error
0 ignored issues
show
Consider making the return type a bit more specific; maybe use IXR_Error|array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
3849
	 */
3850 View Code Duplication
	public function wp_getPageStatusList( $args ) {
3851
		$this->escape( $args );
3852
3853
		$username = $args[1];
3854
		$password = $args[2];
3855
3856
		if ( !$user = $this->login($username, $password) )
3857
			return $this->error;
3858
3859
		if ( !current_user_can( 'edit_pages' ) )
3860
			return new IXR_Error( 403, __( 'Sorry, you are not allowed access to details about this site.' ) );
3861
3862
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3863
		do_action( 'xmlrpc_call', 'wp.getPageStatusList' );
3864
3865
		return get_page_statuses();
3866
	}
3867
3868
	/**
3869
	 * Retrieve page templates.
3870
	 *
3871
	 * @since 2.6.0
3872
	 *
3873
	 * @param array  $args {
3874
	 *     Method arguments. Note: arguments must be ordered as documented.
3875
	 *
3876
	 *     @type int    $blog_id (unused)
3877
	 *     @type string $username
3878
	 *     @type string $password
3879
	 * }
3880
	 * @return array|IXR_Error
0 ignored issues
show
Consider making the return type a bit more specific; maybe use IXR_Error|array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
3881
	 */
3882
	public function wp_getPageTemplates( $args ) {
3883
		$this->escape( $args );
3884
3885
		$username = $args[1];
3886
		$password = $args[2];
3887
3888
		if ( !$user = $this->login($username, $password) )
3889
			return $this->error;
3890
3891
		if ( !current_user_can( 'edit_pages' ) )
3892
			return new IXR_Error( 403, __( 'Sorry, you are not allowed access to details about this site.' ) );
3893
3894
		$templates = get_page_templates();
3895
		$templates['Default'] = 'default';
3896
3897
		return $templates;
3898
	}
3899
3900
	/**
3901
	 * Retrieve blog options.
3902
	 *
3903
	 * @since 2.6.0
3904
	 *
3905
	 * @param array  $args {
3906
	 *     Method arguments. Note: arguments must be ordered as documented.
3907
	 *
3908
	 *     @type int    $blog_id (unused)
3909
	 *     @type string $username
3910
	 *     @type string $password
3911
	 *     @type array  $options
3912
	 * }
3913
	 * @return array|IXR_Error
3914
	 */
3915
	public function wp_getOptions( $args ) {
3916
		$this->escape( $args );
3917
3918
		$username	= $args[1];
3919
		$password	= $args[2];
3920
		$options	= isset( $args[3] ) ? (array) $args[3] : array();
3921
3922
		if ( !$user = $this->login($username, $password) )
3923
			return $this->error;
3924
3925
		// If no specific options where asked for, return all of them
3926
		if ( count( $options ) == 0 )
3927
			$options = array_keys($this->blog_options);
3928
3929
		return $this->_getOptions($options);
3930
	}
3931
3932
	/**
3933
	 * Retrieve blog options value from list.
3934
	 *
3935
	 * @since 2.6.0
3936
	 *
3937
	 * @param array $options Options to retrieve.
3938
	 * @return array
3939
	 */
3940
	public function _getOptions($options) {
3941
		$data = array();
3942
		$can_manage = current_user_can( 'manage_options' );
3943
		foreach ( $options as $option ) {
3944
			if ( array_key_exists( $option, $this->blog_options ) ) {
3945
				$data[$option] = $this->blog_options[$option];
3946
				//Is the value static or dynamic?
3947
				if ( isset( $data[$option]['option'] ) ) {
3948
					$data[$option]['value'] = get_option( $data[$option]['option'] );
3949
					unset($data[$option]['option']);
3950
				}
3951
3952
				if ( ! $can_manage )
3953
					$data[$option]['readonly'] = true;
3954
			}
3955
		}
3956
3957
		return $data;
3958
	}
3959
3960
	/**
3961
	 * Update blog options.
3962
	 *
3963
	 * @since 2.6.0
3964
	 *
3965
	 * @param array  $args {
3966
	 *     Method arguments. Note: arguments must be ordered as documented.
3967
	 *
3968
	 *     @type int    $blog_id (unused)
3969
	 *     @type string $username
3970
	 *     @type string $password
3971
	 *     @type array  $options
3972
	 * }
3973
	 * @return array|IXR_Error
3974
	 */
3975
	public function wp_setOptions( $args ) {
3976
		$this->escape( $args );
3977
3978
		$username	= $args[1];
3979
		$password	= $args[2];
3980
		$options	= (array) $args[3];
3981
3982
		if ( !$user = $this->login($username, $password) )
3983
			return $this->error;
3984
3985
		if ( !current_user_can( 'manage_options' ) )
3986
			return new IXR_Error( 403, __( 'Sorry, you are not allowed to update options.' ) );
3987
3988
		$option_names = array();
3989
		foreach ( $options as $o_name => $o_value ) {
3990
			$option_names[] = $o_name;
3991
			if ( !array_key_exists( $o_name, $this->blog_options ) )
3992
				continue;
3993
3994
			if ( $this->blog_options[$o_name]['readonly'] == true )
3995
				continue;
3996
3997
			update_option( $this->blog_options[$o_name]['option'], wp_unslash( $o_value ) );
3998
		}
3999
4000
		//Now return the updated values
4001
		return $this->_getOptions($option_names);
4002
	}
4003
4004
	/**
4005
	 * Retrieve a media item by ID
4006
	 *
4007
	 * @since 3.1.0
4008
	 *
4009
	 * @param array  $args {
4010
	 *     Method arguments. Note: arguments must be ordered as documented.
4011
	 *
4012
	 *     @type int    $blog_id (unused)
4013
	 *     @type string $username
4014
	 *     @type string $password
4015
	 *     @type int    $attachment_id
4016
	 * }
4017
	 * @return array|IXR_Error Associative array contains:
4018
	 *  - 'date_created_gmt'
4019
	 *  - 'parent'
4020
	 *  - 'link'
4021
	 *  - 'thumbnail'
4022
	 *  - 'title'
4023
	 *  - 'caption'
4024
	 *  - 'description'
4025
	 *  - 'metadata'
4026
	 */
4027 View Code Duplication
	public function wp_getMediaItem( $args ) {
4028
		$this->escape( $args );
4029
4030
		$username		= $args[1];
4031
		$password		= $args[2];
4032
		$attachment_id	= (int) $args[3];
4033
4034
		if ( !$user = $this->login($username, $password) )
4035
			return $this->error;
4036
4037
		if ( !current_user_can( 'upload_files' ) )
4038
			return new IXR_Error( 403, __( 'Sorry, you are not allowed to upload files.' ) );
4039
4040
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4041
		do_action( 'xmlrpc_call', 'wp.getMediaItem' );
4042
4043
		if ( ! $attachment = get_post($attachment_id) )
4044
			return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
4045
4046
		return $this->_prepare_media_item( $attachment );
4047
	}
4048
4049
	/**
4050
	 * Retrieves a collection of media library items (or attachments)
4051
	 *
4052
	 * Besides the common blog_id (unused), username, and password arguments, it takes a filter
4053
	 * array as last argument.
4054
	 *
4055
	 * Accepted 'filter' keys are 'parent_id', 'mime_type', 'offset', and 'number'.
4056
	 *
4057
	 * The defaults are as follows:
4058
	 * - 'number' - Default is 5. Total number of media items to retrieve.
4059
	 * - 'offset' - Default is 0. See WP_Query::query() for more.
4060
	 * - 'parent_id' - Default is ''. The post where the media item is attached. Empty string shows all media items. 0 shows unattached media items.
4061
	 * - 'mime_type' - Default is ''. Filter by mime type (e.g., 'image/jpeg', 'application/pdf')
4062
	 *
4063
	 * @since 3.1.0
4064
	 *
4065
	 * @param array  $args {
4066
	 *     Method arguments. Note: arguments must be ordered as documented.
4067
	 *
4068
	 *     @type int    $blog_id (unused)
4069
	 *     @type string $username
4070
	 *     @type string $password
4071
	 *     @type array  $struct
4072
	 * }
4073
	 * @return array|IXR_Error Contains a collection of media items. See wp_xmlrpc_server::wp_getMediaItem() for a description of each item contents
4074
	 */
4075
	public function wp_getMediaLibrary($args) {
4076
		$this->escape($args);
4077
4078
		$username	= $args[1];
4079
		$password	= $args[2];
4080
		$struct		= isset( $args[3] ) ? $args[3] : array() ;
4081
4082
		if ( !$user = $this->login($username, $password) )
4083
			return $this->error;
4084
4085
		if ( !current_user_can( 'upload_files' ) )
4086
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to upload files.' ) );
4087
4088
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4089
		do_action( 'xmlrpc_call', 'wp.getMediaLibrary' );
4090
4091
		$parent_id = ( isset($struct['parent_id']) ) ? absint($struct['parent_id']) : '' ;
4092
		$mime_type = ( isset($struct['mime_type']) ) ? $struct['mime_type'] : '' ;
4093
		$offset = ( isset($struct['offset']) ) ? absint($struct['offset']) : 0 ;
4094
		$number = ( isset($struct['number']) ) ? absint($struct['number']) : -1 ;
4095
4096
		$attachments = get_posts( array('post_type' => 'attachment', 'post_parent' => $parent_id, 'offset' => $offset, 'numberposts' => $number, 'post_mime_type' => $mime_type ) );
4097
4098
		$attachments_struct = array();
4099
4100
		foreach ($attachments as $attachment )
4101
			$attachments_struct[] = $this->_prepare_media_item( $attachment );
4102
4103
		return $attachments_struct;
4104
	}
4105
4106
	/**
4107
	 * Retrieves a list of post formats used by the site.
4108
	 *
4109
	 * @since 3.1.0
4110
	 *
4111
	 * @param array  $args {
4112
	 *     Method arguments. Note: arguments must be ordered as documented.
4113
	 *
4114
	 *     @type int    $blog_id (unused)
4115
	 *     @type string $username
4116
	 *     @type string $password
4117
	 * }
4118
	 * @return array|IXR_Error List of post formats, otherwise IXR_Error object.
0 ignored issues
show
Consider making the return type a bit more specific; maybe use IXR_Error|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
4119
	 */
4120
	public function wp_getPostFormats( $args ) {
4121
		$this->escape( $args );
4122
4123
		$username = $args[1];
4124
		$password = $args[2];
4125
4126
		if ( !$user = $this->login( $username, $password ) )
4127
			return $this->error;
4128
4129
		if ( !current_user_can( 'edit_posts' ) )
4130
			return new IXR_Error( 403, __( 'Sorry, you are not allowed access to details about this site.' ) );
4131
4132
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4133
		do_action( 'xmlrpc_call', 'wp.getPostFormats' );
4134
4135
		$formats = get_post_format_strings();
4136
4137
		// find out if they want a list of currently supports formats
4138
		if ( isset( $args[3] ) && is_array( $args[3] ) ) {
4139
			if ( $args[3]['show-supported'] ) {
4140
				if ( current_theme_supports( 'post-formats' ) ) {
4141
					$supported = get_theme_support( 'post-formats' );
4142
4143
					$data = array();
4144
					$data['all'] = $formats;
4145
					$data['supported'] = $supported[0];
4146
4147
					$formats = $data;
4148
				}
4149
			}
4150
		}
4151
4152
		return $formats;
4153
	}
4154
4155
	/**
4156
	 * Retrieves a post type
4157
	 *
4158
	 * @since 3.4.0
4159
	 *
4160
	 * @see get_post_type_object()
4161
	 *
4162
	 * @param array  $args {
4163
	 *     Method arguments. Note: arguments must be ordered as documented.
4164
	 *
4165
	 *     @type int    $blog_id (unused)
4166
	 *     @type string $username
4167
	 *     @type string $password
4168
	 *     @type string $post_type_name
4169
	 *     @type array  $fields (optional)
4170
	 * }
4171
	 * @return array|IXR_Error Array contains:
4172
	 *  - 'labels'
4173
	 *  - 'description'
4174
	 *  - 'capability_type'
4175
	 *  - 'cap'
4176
	 *  - 'map_meta_cap'
4177
	 *  - 'hierarchical'
4178
	 *  - 'menu_position'
4179
	 *  - 'taxonomies'
4180
	 *  - 'supports'
4181
	 */
4182 View Code Duplication
	public function wp_getPostType( $args ) {
4183
		if ( ! $this->minimum_args( $args, 4 ) )
4184
			return $this->error;
4185
4186
		$this->escape( $args );
4187
4188
		$username       = $args[1];
4189
		$password       = $args[2];
4190
		$post_type_name = $args[3];
4191
4192
		if ( isset( $args[4] ) ) {
4193
			$fields = $args[4];
4194
		} else {
4195
			/**
4196
			 * Filters the default query fields used by the given XML-RPC method.
4197
			 *
4198
			 * @since 3.4.0
4199
			 *
4200
			 * @param array  $fields An array of post type query fields for the given method.
4201
			 * @param string $method The method name.
4202
			 */
4203
			$fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostType' );
4204
		}
4205
4206
		if ( !$user = $this->login( $username, $password ) )
4207
			return $this->error;
4208
4209
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4210
		do_action( 'xmlrpc_call', 'wp.getPostType' );
4211
4212
		if ( ! post_type_exists( $post_type_name ) )
4213
			return new IXR_Error( 403, __( 'Invalid post type.' ) );
4214
4215
		$post_type = get_post_type_object( $post_type_name );
4216
4217
		if ( ! current_user_can( $post_type->cap->edit_posts ) )
4218
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type.' ) );
4219
4220
		return $this->_prepare_post_type( $post_type, $fields );
4221
	}
4222
4223
	/**
4224
	 * Retrieves a post types
4225
	 *
4226
	 * @since 3.4.0
4227
	 *
4228
	 * @see get_post_types()
4229
	 *
4230
	 * @param array  $args {
4231
	 *     Method arguments. Note: arguments must be ordered as documented.
4232
	 *
4233
	 *     @type int    $blog_id (unused)
4234
	 *     @type string $username
4235
	 *     @type string $password
4236
	 *     @type array  $filter (optional)
4237
	 *     @type array  $fields (optional)
4238
	 * }
4239
	 * @return array|IXR_Error
4240
	 */
4241 View Code Duplication
	public function wp_getPostTypes( $args ) {
4242
		if ( ! $this->minimum_args( $args, 3 ) )
4243
			return $this->error;
4244
4245
		$this->escape( $args );
4246
4247
		$username = $args[1];
4248
		$password = $args[2];
4249
		$filter   = isset( $args[3] ) ? $args[3] : array( 'public' => true );
4250
4251
		if ( isset( $args[4] ) ) {
4252
			$fields = $args[4];
4253
		} else {
4254
			/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4255
			$fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostTypes' );
4256
		}
4257
4258
		if ( ! $user = $this->login( $username, $password ) )
4259
			return $this->error;
4260
4261
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4262
		do_action( 'xmlrpc_call', 'wp.getPostTypes' );
4263
4264
		$post_types = get_post_types( $filter, 'objects' );
4265
4266
		$struct = array();
4267
4268
		foreach ( $post_types as $post_type ) {
4269
			if ( ! current_user_can( $post_type->cap->edit_posts ) )
4270
				continue;
4271
4272
			$struct[$post_type->name] = $this->_prepare_post_type( $post_type, $fields );
4273
		}
4274
4275
		return $struct;
4276
	}
4277
4278
	/**
4279
	 * Retrieve revisions for a specific post.
4280
	 *
4281
	 * @since 3.5.0
4282
	 *
4283
	 * The optional $fields parameter specifies what fields will be included
4284
	 * in the response array.
4285
	 *
4286
	 * @uses wp_get_post_revisions()
4287
	 * @see wp_getPost() for more on $fields
4288
	 *
4289
	 * @param array  $args {
4290
	 *     Method arguments. Note: arguments must be ordered as documented.
4291
	 *
4292
	 *     @type int    $blog_id (unused)
4293
	 *     @type string $username
4294
	 *     @type string $password
4295
	 *     @type int    $post_id
4296
	 *     @type array  $fields (optional)
4297
	 * }
4298
	 * @return array|IXR_Error contains a collection of posts.
4299
	 */
4300
	public function wp_getRevisions( $args ) {
4301
		if ( ! $this->minimum_args( $args, 4 ) )
4302
			return $this->error;
4303
4304
		$this->escape( $args );
4305
4306
		$username = $args[1];
4307
		$password = $args[2];
4308
		$post_id  = (int) $args[3];
4309
4310
		if ( isset( $args[4] ) ) {
4311
			$fields = $args[4];
4312
		} else {
4313
			/**
4314
			 * Filters the default revision query fields used by the given XML-RPC method.
4315
			 *
4316
			 * @since 3.5.0
4317
			 *
4318
			 * @param array  $field  An array of revision query fields.
4319
			 * @param string $method The method name.
4320
			 */
4321
			$fields = apply_filters( 'xmlrpc_default_revision_fields', array( 'post_date', 'post_date_gmt' ), 'wp.getRevisions' );
4322
		}
4323
4324
		if ( ! $user = $this->login( $username, $password ) )
4325
			return $this->error;
4326
4327
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4328
		do_action( 'xmlrpc_call', 'wp.getRevisions' );
4329
4330
		if ( ! $post = get_post( $post_id ) )
4331
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4332
4333
		if ( ! current_user_can( 'edit_post', $post_id ) )
4334
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
4335
4336
		// Check if revisions are enabled.
4337
		if ( ! wp_revisions_enabled( $post ) )
4338
			return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) );
4339
4340
		$revisions = wp_get_post_revisions( $post_id );
4341
4342
		if ( ! $revisions )
0 ignored issues
show
Bug Best Practice introduced by
The expression $revisions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
4343
			return array();
4344
4345
		$struct = array();
4346
4347
		foreach ( $revisions as $revision ) {
4348
			if ( ! current_user_can( 'read_post', $revision->ID ) )
4349
				continue;
4350
4351
			// Skip autosaves
4352
			if ( wp_is_post_autosave( $revision ) )
4353
				continue;
4354
4355
			$struct[] = $this->_prepare_post( get_object_vars( $revision ), $fields );
4356
		}
4357
4358
		return $struct;
4359
	}
4360
4361
	/**
4362
	 * Restore a post revision
4363
	 *
4364
	 * @since 3.5.0
4365
	 *
4366
	 * @uses wp_restore_post_revision()
4367
	 *
4368
	 * @param array  $args {
4369
	 *     Method arguments. Note: arguments must be ordered as documented.
4370
	 *
4371
	 *     @type int    $blog_id (unused)
4372
	 *     @type string $username
4373
	 *     @type string $password
4374
	 *     @type int    $revision_id
4375
	 * }
4376
	 * @return bool|IXR_Error false if there was an error restoring, true if success.
4377
	 */
4378
	public function wp_restoreRevision( $args ) {
4379
		if ( ! $this->minimum_args( $args, 3 ) )
4380
			return $this->error;
4381
4382
		$this->escape( $args );
4383
4384
		$username    = $args[1];
4385
		$password    = $args[2];
4386
		$revision_id = (int) $args[3];
4387
4388
		if ( ! $user = $this->login( $username, $password ) )
4389
			return $this->error;
4390
4391
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4392
		do_action( 'xmlrpc_call', 'wp.restoreRevision' );
4393
4394
		if ( ! $revision = wp_get_post_revision( $revision_id ) )
4395
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4396
4397
		if ( wp_is_post_autosave( $revision ) )
4398
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4399
4400
		if ( ! $post = get_post( $revision->post_parent ) )
4401
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4402
4403
		if ( ! current_user_can( 'edit_post', $revision->post_parent ) )
4404
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
4405
4406
		// Check if revisions are disabled.
4407
		if ( ! wp_revisions_enabled( $post ) )
4408
			return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) );
4409
4410
		$post = wp_restore_post_revision( $revision_id );
4411
4412
		return (bool) $post;
4413
	}
4414
4415
	/* Blogger API functions.
4416
	 * specs on http://plant.blogger.com/api and https://groups.yahoo.com/group/bloggerDev/
4417
	 */
4418
4419
	/**
4420
	 * Retrieve blogs that user owns.
4421
	 *
4422
	 * Will make more sense once we support multiple blogs.
4423
	 *
4424
	 * @since 1.5.0
4425
	 *
4426
	 * @param array  $args {
4427
	 *     Method arguments. Note: arguments must be ordered as documented.
4428
	 *
4429
	 *     @type int    $blog_id (unused)
4430
	 *     @type string $username
4431
	 *     @type string $password
4432
	 * }
4433
	 * @return array|IXR_Error
4434
	 */
4435
	public function blogger_getUsersBlogs($args) {
4436
		if ( ! $this->minimum_args( $args, 3 ) ) {
4437
			return $this->error;
4438
		}
4439
4440
		if ( is_multisite() ) {
4441
			return $this->_multisite_getUsersBlogs($args);
4442
		}
4443
4444
		$this->escape($args);
4445
4446
		$username = $args[1];
4447
		$password = $args[2];
4448
4449
		if ( !$user = $this->login($username, $password) )
4450
			return $this->error;
4451
4452
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4453
		do_action( 'xmlrpc_call', 'blogger.getUsersBlogs' );
4454
4455
		$is_admin = current_user_can('manage_options');
4456
4457
		$struct = array(
4458
			'isAdmin'  => $is_admin,
4459
			'url'      => get_option('home') . '/',
4460
			'blogid'   => '1',
4461
			'blogName' => get_option('blogname'),
4462
			'xmlrpc'   => site_url( 'xmlrpc.php', 'rpc' ),
4463
		);
4464
4465
		return array($struct);
4466
	}
4467
4468
	/**
4469
	 * Private function for retrieving a users blogs for multisite setups
4470
	 *
4471
	 * @since 3.0.0
4472
	 * @access protected
4473
	 *
4474
	 * @param array $args {
4475
	 *     Method arguments. Note: arguments must be ordered as documented.
4476
	 *
4477
	 *     @type string $username Username.
4478
	 *     @type string $password Password.
4479
	 * }
4480
	 * @return array|IXR_Error
4481
	 */
4482
	protected function _multisite_getUsersBlogs( $args ) {
4483
		$current_blog = get_site();
4484
4485
		$domain = $current_blog->domain;
4486
		$path = $current_blog->path . 'xmlrpc.php';
4487
4488
		$rpc = new IXR_Client( set_url_scheme( "http://{$domain}{$path}" ) );
4489
		$rpc->query('wp.getUsersBlogs', $args[1], $args[2]);
4490
		$blogs = $rpc->getResponse();
4491
4492
		if ( isset($blogs['faultCode']) )
4493
			return new IXR_Error($blogs['faultCode'], $blogs['faultString']);
4494
4495
		if ( $_SERVER['HTTP_HOST'] == $domain && $_SERVER['REQUEST_URI'] == $path ) {
4496
			return $blogs;
4497
		} else {
4498
			foreach ( (array) $blogs as $blog ) {
4499
				if ( strpos($blog['url'], $_SERVER['HTTP_HOST']) )
4500
					return array($blog);
4501
			}
4502
			return array();
4503
		}
4504
	}
4505
4506
	/**
4507
	 * Retrieve user's data.
4508
	 *
4509
	 * Gives your client some info about you, so you don't have to.
4510
	 *
4511
	 * @since 1.5.0
4512
	 *
4513
	 * @param array  $args {
4514
	 *     Method arguments. Note: arguments must be ordered as documented.
4515
	 *
4516
	 *     @type int    $blog_id (unused)
4517
	 *     @type string $username
4518
	 *     @type string $password
4519
	 * }
4520
	 * @return array|IXR_Error
0 ignored issues
show
Consider making the return type a bit more specific; maybe use IXR_Error|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
4521
	 */
4522
	public function blogger_getUserInfo( $args ) {
4523
		$this->escape( $args );
4524
4525
		$username = $args[1];
4526
		$password = $args[2];
4527
4528
		if ( !$user = $this->login($username, $password) )
4529
			return $this->error;
4530
4531
		if ( !current_user_can( 'edit_posts' ) )
4532
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to access user data on this site.' ) );
4533
4534
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4535
		do_action( 'xmlrpc_call', 'blogger.getUserInfo' );
4536
4537
		$struct = array(
4538
			'nickname'  => $user->nickname,
4539
			'userid'    => $user->ID,
4540
			'url'       => $user->user_url,
4541
			'lastname'  => $user->last_name,
4542
			'firstname' => $user->first_name
4543
		);
4544
4545
		return $struct;
4546
	}
4547
4548
	/**
4549
	 * Retrieve post.
4550
	 *
4551
	 * @since 1.5.0
4552
	 *
4553
	 * @param array  $args {
4554
	 *     Method arguments. Note: arguments must be ordered as documented.
4555
	 *
4556
	 *     @type int    $blog_id (unused)
4557
	 *     @type int    $post_ID
4558
	 *     @type string $username
4559
	 *     @type string $password
4560
	 * }
4561
	 * @return array|IXR_Error
0 ignored issues
show
Consider making the return type a bit more specific; maybe use IXR_Error|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
4562
	 */
4563
	public function blogger_getPost( $args ) {
4564
		$this->escape( $args );
4565
4566
		$post_ID  = (int) $args[1];
4567
		$username = $args[2];
4568
		$password = $args[3];
4569
4570
		if ( !$user = $this->login($username, $password) )
4571
			return $this->error;
4572
4573
		$post_data = get_post($post_ID, ARRAY_A);
4574
		if ( ! $post_data )
4575
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4576
4577
		if ( !current_user_can( 'edit_post', $post_ID ) )
4578
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
4579
4580
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4581
		do_action( 'xmlrpc_call', 'blogger.getPost' );
4582
4583
		$categories = implode(',', wp_get_post_categories($post_ID));
4584
4585
		$content  = '<title>'.wp_unslash($post_data['post_title']).'</title>';
4586
		$content .= '<category>'.$categories.'</category>';
4587
		$content .= wp_unslash($post_data['post_content']);
4588
4589
		$struct = array(
4590
			'userid'    => $post_data['post_author'],
4591
			'dateCreated' => $this->_convert_date( $post_data['post_date'] ),
4592
			'content'     => $content,
4593
			'postid'  => (string) $post_data['ID']
4594
		);
4595
4596
		return $struct;
4597
	}
4598
4599
	/**
4600
	 * Retrieve list of recent posts.
4601
	 *
4602
	 * @since 1.5.0
4603
	 *
4604
	 * @param array  $args {
4605
	 *     Method arguments. Note: arguments must be ordered as documented.
4606
	 *
4607
	 *     @type string $appkey (unused)
4608
	 *     @type int    $blog_id (unused)
4609
	 *     @type string $username
4610
	 *     @type string $password
4611
	 *     @type int    $numberposts (optional)
4612
	 * }
4613
	 * @return array|IXR_Error
4614
	 */
4615
	public function blogger_getRecentPosts( $args ) {
4616
4617
		$this->escape($args);
4618
4619
		// $args[0] = appkey - ignored
4620
		$username = $args[2];
4621
		$password = $args[3];
4622 View Code Duplication
		if ( isset( $args[4] ) )
4623
			$query = array( 'numberposts' => absint( $args[4] ) );
4624
		else
4625
			$query = array();
4626
4627
		if ( !$user = $this->login($username, $password) )
4628
			return $this->error;
4629
4630
		if ( ! current_user_can( 'edit_posts' ) )
4631
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
4632
4633
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4634
		do_action( 'xmlrpc_call', 'blogger.getRecentPosts' );
4635
4636
		$posts_list = wp_get_recent_posts( $query );
4637
4638
		if ( !$posts_list ) {
4639
			$this->error = new IXR_Error(500, __('Either there are no posts, or something went wrong.'));
4640
			return $this->error;
4641
		}
4642
4643
		$recent_posts = array();
4644
		foreach ($posts_list as $entry) {
4645
			if ( !current_user_can( 'edit_post', $entry['ID'] ) )
4646
				continue;
4647
4648
			$post_date  = $this->_convert_date( $entry['post_date'] );
4649
			$categories = implode(',', wp_get_post_categories($entry['ID']));
4650
4651
			$content  = '<title>'.wp_unslash($entry['post_title']).'</title>';
4652
			$content .= '<category>'.$categories.'</category>';
4653
			$content .= wp_unslash($entry['post_content']);
4654
4655
			$recent_posts[] = array(
4656
				'userid' => $entry['post_author'],
4657
				'dateCreated' => $post_date,
4658
				'content' => $content,
4659
				'postid' => (string) $entry['ID'],
4660
			);
4661
		}
4662
4663
		return $recent_posts;
4664
	}
4665
4666
	/**
4667
	 * Deprecated.
4668
	 *
4669
	 * @since 1.5.0
4670
	 * @deprecated 3.5.0
4671
	 *
4672
	 * @param array $args Unused.
4673
	 * @return IXR_Error Error object.
4674
	 */
4675
	public function blogger_getTemplate($args) {
0 ignored issues
show
The parameter $args is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
4676
		return new IXR_Error( 403, __('Sorry, that file cannot be edited.' ) );
4677
	}
4678
4679
	/**
4680
	 * Deprecated.
4681
	 *
4682
	 * @since 1.5.0
4683
	 * @deprecated 3.5.0
4684
	 *
4685
	 * @param array $args Unused.
4686
	 * @return IXR_Error Error object.
4687
	 */
4688
	public function blogger_setTemplate($args) {
0 ignored issues
show
The parameter $args is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
4689
		return new IXR_Error( 403, __('Sorry, that file cannot be edited.' ) );
4690
	}
4691
4692
	/**
4693
	 * Creates new post.
4694
	 *
4695
	 * @since 1.5.0
4696
	 *
4697
	 * @param array $args {
4698
	 *     Method arguments. Note: arguments must be ordered as documented.
4699
	 *
4700
	 *     @type string $appkey (unused)
4701
	 *     @type int    $blog_id (unused)
4702
	 *     @type string $username
4703
	 *     @type string $password
4704
	 *     @type string $content
4705
	 *     @type string $publish
4706
	 * }
4707
	 * @return int|IXR_Error
0 ignored issues
show
Should the return type not be IXR_Error|integer|WP_Error?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
4708
	 */
4709
	public function blogger_newPost( $args ) {
4710
		$this->escape( $args );
4711
4712
		$username = $args[2];
4713
		$password = $args[3];
4714
		$content  = $args[4];
4715
		$publish  = $args[5];
4716
4717
		if ( !$user = $this->login($username, $password) )
4718
			return $this->error;
4719
4720
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4721
		do_action( 'xmlrpc_call', 'blogger.newPost' );
4722
4723
		$cap = ($publish) ? 'publish_posts' : 'edit_posts';
4724 View Code Duplication
		if ( ! current_user_can( get_post_type_object( 'post' )->cap->create_posts ) || !current_user_can($cap) )
4725
			return new IXR_Error(401, __('Sorry, you are not allowed to post on this site.'));
4726
4727
		$post_status = ($publish) ? 'publish' : 'draft';
4728
4729
		$post_author = $user->ID;
4730
4731
		$post_title = xmlrpc_getposttitle($content);
4732
		$post_category = xmlrpc_getpostcategory($content);
4733
		$post_content = xmlrpc_removepostdata($content);
4734
4735
		$post_date = current_time('mysql');
4736
		$post_date_gmt = current_time('mysql', 1);
4737
4738
		$post_data = compact('post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status');
4739
4740
		$post_ID = wp_insert_post($post_data);
4741
		if ( is_wp_error( $post_ID ) )
4742
			return new IXR_Error(500, $post_ID->get_error_message());
4743
4744
		if ( !$post_ID )
4745
			return new IXR_Error(500, __('Sorry, your entry could not be posted.'));
4746
4747
		$this->attach_uploads( $post_ID, $post_content );
4748
4749
		/**
4750
		 * Fires after a new post has been successfully created via the XML-RPC Blogger API.
4751
		 *
4752
		 * @since 3.4.0
4753
		 *
4754
		 * @param int   $post_ID ID of the new post.
4755
		 * @param array $args    An array of new post arguments.
4756
		 */
4757
		do_action( 'xmlrpc_call_success_blogger_newPost', $post_ID, $args );
4758
4759
		return $post_ID;
4760
	}
4761
4762
	/**
4763
	 * Edit a post.
4764
	 *
4765
	 * @since 1.5.0
4766
	 *
4767
	 * @param array  $args {
4768
	 *     Method arguments. Note: arguments must be ordered as documented.
4769
	 *
4770
	 *     @type int    $blog_id (unused)
4771
	 *     @type int    $post_ID
4772
	 *     @type string $username
4773
	 *     @type string $password
4774
	 *     @type string $content
4775
	 *     @type bool   $publish
4776
	 * }
4777
	 * @return true|IXR_Error true when done.
0 ignored issues
show
Should the return type not be IXR_Error|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
4778
	 */
4779
	public function blogger_editPost( $args ) {
4780
4781
		$this->escape($args);
4782
4783
		$post_ID  = (int) $args[1];
4784
		$username = $args[2];
4785
		$password = $args[3];
4786
		$content  = $args[4];
4787
		$publish  = $args[5];
4788
4789
		if ( ! $user = $this->login( $username, $password ) ) {
4790
			return $this->error;
4791
		}
4792
4793
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4794
		do_action( 'xmlrpc_call', 'blogger.editPost' );
4795
4796
		$actual_post = get_post( $post_ID, ARRAY_A );
4797
4798
		if ( ! $actual_post || $actual_post['post_type'] != 'post' ) {
4799
			return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
4800
		}
4801
4802
		$this->escape($actual_post);
4803
4804
		if ( ! current_user_can( 'edit_post', $post_ID ) ) {
4805
			return new IXR_Error(401, __('Sorry, you are not allowed to edit this post.'));
4806
		}
4807
		if ( 'publish' == $actual_post['post_status'] && ! current_user_can( 'publish_posts' ) ) {
4808
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) );
4809
		}
4810
4811
		$postdata = array();
4812
		$postdata['ID'] = $actual_post['ID'];
4813
		$postdata['post_content'] = xmlrpc_removepostdata( $content );
4814
		$postdata['post_title'] = xmlrpc_getposttitle( $content );
4815
		$postdata['post_category'] = xmlrpc_getpostcategory( $content );
4816
		$postdata['post_status'] = $actual_post['post_status'];
4817
		$postdata['post_excerpt'] = $actual_post['post_excerpt'];
4818
		$postdata['post_status'] = $publish ? 'publish' : 'draft';
4819
4820
		$result = wp_update_post( $postdata );
4821
4822
		if ( ! $result ) {
4823
			return new IXR_Error(500, __('For some strange yet very annoying reason, this post could not be edited.'));
4824
		}
4825
		$this->attach_uploads( $actual_post['ID'], $postdata['post_content'] );
4826
4827
		/**
4828
		 * Fires after a post has been successfully updated via the XML-RPC Blogger API.
4829
		 *
4830
		 * @since 3.4.0
4831
		 *
4832
		 * @param int   $post_ID ID of the updated post.
4833
		 * @param array $args    An array of arguments for the post to edit.
4834
		 */
4835
		do_action( 'xmlrpc_call_success_blogger_editPost', $post_ID, $args );
4836
4837
		return true;
4838
	}
4839
4840
	/**
4841
	 * Remove a post.
4842
	 *
4843
	 * @since 1.5.0
4844
	 *
4845
	 * @param array  $args {
4846
	 *     Method arguments. Note: arguments must be ordered as documented.
4847
	 *
4848
	 *     @type int    $blog_id (unused)
4849
	 *     @type int    $post_ID
4850
	 *     @type string $username
4851
	 *     @type string $password
4852
	 * }
4853
	 * @return true|IXR_Error True when post is deleted.
0 ignored issues
show
Should the return type not be IXR_Error|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
4854
	 */
4855 View Code Duplication
	public function blogger_deletePost( $args ) {
4856
		$this->escape( $args );
4857
4858
		$post_ID  = (int) $args[1];
4859
		$username = $args[2];
4860
		$password = $args[3];
4861
4862
		if ( !$user = $this->login($username, $password) )
4863
			return $this->error;
4864
4865
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4866
		do_action( 'xmlrpc_call', 'blogger.deletePost' );
4867
4868
		$actual_post = get_post( $post_ID, ARRAY_A );
4869
4870
		if ( ! $actual_post || $actual_post['post_type'] != 'post' ) {
4871
			return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
4872
		}
4873
4874
		if ( ! current_user_can( 'delete_post', $post_ID ) ) {
4875
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this post.' ) );
4876
		}
4877
4878
		$result = wp_delete_post( $post_ID );
4879
4880
		if ( ! $result ) {
4881
			return new IXR_Error( 500, __( 'The post cannot be deleted.' ) );
4882
		}
4883
4884
		/**
4885
		 * Fires after a post has been successfully deleted via the XML-RPC Blogger API.
4886
		 *
4887
		 * @since 3.4.0
4888
		 *
4889
		 * @param int   $post_ID ID of the deleted post.
4890
		 * @param array $args    An array of arguments to delete the post.
4891
		 */
4892
		do_action( 'xmlrpc_call_success_blogger_deletePost', $post_ID, $args );
4893
4894
		return true;
4895
	}
4896
4897
	/* MetaWeblog API functions
4898
	 * specs on wherever Dave Winer wants them to be
4899
	 */
4900
4901
	/**
4902
	 * Create a new post.
4903
	 *
4904
	 * The 'content_struct' argument must contain:
4905
	 *  - title
4906
	 *  - description
4907
	 *  - mt_excerpt
4908
	 *  - mt_text_more
4909
	 *  - mt_keywords
4910
	 *  - mt_tb_ping_urls
4911
	 *  - categories
4912
	 *
4913
	 * Also, it can optionally contain:
4914
	 *  - wp_slug
4915
	 *  - wp_password
4916
	 *  - wp_page_parent_id
4917
	 *  - wp_page_order
4918
	 *  - wp_author_id
4919
	 *  - post_status | page_status - can be 'draft', 'private', 'publish', or 'pending'
4920
	 *  - mt_allow_comments - can be 'open' or 'closed'
4921
	 *  - mt_allow_pings - can be 'open' or 'closed'
4922
	 *  - date_created_gmt
4923
	 *  - dateCreated
4924
	 *  - wp_post_thumbnail
4925
	 *
4926
	 * @since 1.5.0
4927
	 *
4928
	 * @param array  $args {
4929
	 *     Method arguments. Note: arguments must be ordered as documented.
4930
	 *
4931
	 *     @type int    $blog_id (unused)
4932
	 *     @type string $username
4933
	 *     @type string $password
4934
	 *     @type array  $content_struct
4935
	 *     @type int    $publish
4936
	 * }
4937
	 * @return int|IXR_Error
0 ignored issues
show
Should the return type not be IXR_Error|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
4938
	 */
4939
	public function mw_newPost($args) {
4940
		$this->escape($args);
4941
4942
		$username       = $args[1];
4943
		$password       = $args[2];
4944
		$content_struct = $args[3];
4945
		$publish        = isset( $args[4] ) ? $args[4] : 0;
4946
4947
		if ( !$user = $this->login($username, $password) )
4948
			return $this->error;
4949
4950
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4951
		do_action( 'xmlrpc_call', 'metaWeblog.newPost' );
4952
4953
		$page_template = '';
4954
		if ( !empty( $content_struct['post_type'] ) ) {
4955
			if ( $content_struct['post_type'] == 'page' ) {
4956
				if ( $publish )
4957
					$cap  = 'publish_pages';
4958
				elseif ( isset( $content_struct['page_status'] ) && 'publish' == $content_struct['page_status'] )
4959
					$cap  = 'publish_pages';
4960
				else
4961
					$cap = 'edit_pages';
4962
				$error_message = __( 'Sorry, you are not allowed to publish pages on this site.' );
4963
				$post_type = 'page';
4964
				if ( !empty( $content_struct['wp_page_template'] ) )
4965
					$page_template = $content_struct['wp_page_template'];
4966 View Code Duplication
			} elseif ( $content_struct['post_type'] == 'post' ) {
4967
				if ( $publish )
4968
					$cap  = 'publish_posts';
4969
				elseif ( isset( $content_struct['post_status'] ) && 'publish' == $content_struct['post_status'] )
4970
					$cap  = 'publish_posts';
4971
				else
4972
					$cap = 'edit_posts';
4973
				$error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
4974
				$post_type = 'post';
4975
			} else {
4976
				// No other post_type values are allowed here
4977
				return new IXR_Error( 401, __( 'Invalid post type.' ) );
4978
			}
4979 View Code Duplication
		} else {
4980
			if ( $publish )
4981
				$cap  = 'publish_posts';
4982
			elseif ( isset( $content_struct['post_status'] ) && 'publish' == $content_struct['post_status'])
4983
				$cap  = 'publish_posts';
4984
			else
4985
				$cap = 'edit_posts';
4986
			$error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
4987
			$post_type = 'post';
4988
		}
4989
4990 View Code Duplication
		if ( ! current_user_can( get_post_type_object( $post_type )->cap->create_posts ) )
4991
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts on this site.' ) );
4992
		if ( !current_user_can( $cap ) )
4993
			return new IXR_Error( 401, $error_message );
4994
4995
		// Check for a valid post format if one was given
4996 View Code Duplication
		if ( isset( $content_struct['wp_post_format'] ) ) {
4997
			$content_struct['wp_post_format'] = sanitize_key( $content_struct['wp_post_format'] );
4998
			if ( !array_key_exists( $content_struct['wp_post_format'], get_post_format_strings() ) ) {
4999
				return new IXR_Error( 404, __( 'Invalid post format.' ) );
5000
			}
5001
		}
5002
5003
		// Let WordPress generate the post_name (slug) unless
5004
		// one has been provided.
5005
		$post_name = "";
5006
		if ( isset($content_struct['wp_slug']) )
5007
			$post_name = $content_struct['wp_slug'];
5008
5009
		// Only use a password if one was given.
5010
		if ( isset($content_struct['wp_password']) )
5011
			$post_password = $content_struct['wp_password'];
5012
5013
		// Only set a post parent if one was provided.
5014
		if ( isset($content_struct['wp_page_parent_id']) )
5015
			$post_parent = $content_struct['wp_page_parent_id'];
5016
5017
		// Only set the menu_order if it was provided.
5018
		if ( isset($content_struct['wp_page_order']) )
5019
			$menu_order = $content_struct['wp_page_order'];
5020
5021
		$post_author = $user->ID;
5022
5023
		// If an author id was provided then use it instead.
5024
		if ( isset( $content_struct['wp_author_id'] ) && ( $user->ID != $content_struct['wp_author_id'] ) ) {
5025 View Code Duplication
			switch ( $post_type ) {
5026
				case "post":
5027
					if ( !current_user_can( 'edit_others_posts' ) )
5028
						return new IXR_Error( 401, __( 'Sorry, you are not allowed to create posts as this user.' ) );
5029
					break;
5030
				case "page":
5031
					if ( !current_user_can( 'edit_others_pages' ) )
5032
						return new IXR_Error( 401, __( 'Sorry, you are not allowed to create pages as this user.' ) );
5033
					break;
5034
				default:
5035
					return new IXR_Error( 401, __( 'Invalid post type.' ) );
5036
			}
5037
			$author = get_userdata( $content_struct['wp_author_id'] );
5038
			if ( ! $author )
5039
				return new IXR_Error( 404, __( 'Invalid author ID.' ) );
5040
			$post_author = $content_struct['wp_author_id'];
5041
		}
5042
5043
		$post_title = isset( $content_struct['title'] ) ? $content_struct['title'] : null;
5044
		$post_content = isset( $content_struct['description'] ) ? $content_struct['description'] : null;
5045
5046
		$post_status = $publish ? 'publish' : 'draft';
5047
5048 View Code Duplication
		if ( isset( $content_struct["{$post_type}_status"] ) ) {
5049
			switch ( $content_struct["{$post_type}_status"] ) {
5050
				case 'draft':
5051
				case 'pending':
5052
				case 'private':
5053
				case 'publish':
5054
					$post_status = $content_struct["{$post_type}_status"];
5055
					break;
5056
				default:
5057
					$post_status = $publish ? 'publish' : 'draft';
5058
					break;
5059
			}
5060
		}
5061
5062
		$post_excerpt = isset($content_struct['mt_excerpt']) ? $content_struct['mt_excerpt'] : null;
5063
		$post_more = isset($content_struct['mt_text_more']) ? $content_struct['mt_text_more'] : null;
5064
5065
		$tags_input = isset($content_struct['mt_keywords']) ? $content_struct['mt_keywords'] : null;
5066
5067 View Code Duplication
		if ( isset($content_struct['mt_allow_comments']) ) {
5068
			if ( !is_numeric($content_struct['mt_allow_comments']) ) {
5069
				switch ( $content_struct['mt_allow_comments'] ) {
5070
					case 'closed':
5071
						$comment_status = 'closed';
5072
						break;
5073
					case 'open':
5074
						$comment_status = 'open';
5075
						break;
5076
					default:
5077
						$comment_status = get_default_comment_status( $post_type );
5078
						break;
5079
				}
5080
			} else {
5081
				switch ( (int) $content_struct['mt_allow_comments'] ) {
5082
					case 0:
5083
					case 2:
5084
						$comment_status = 'closed';
5085
						break;
5086
					case 1:
5087
						$comment_status = 'open';
5088
						break;
5089
					default:
5090
						$comment_status = get_default_comment_status( $post_type );
5091
						break;
5092
				}
5093
			}
5094
		} else {
5095
			$comment_status = get_default_comment_status( $post_type );
5096
		}
5097
5098 View Code Duplication
		if ( isset($content_struct['mt_allow_pings']) ) {
5099
			if ( !is_numeric($content_struct['mt_allow_pings']) ) {
5100
				switch ( $content_struct['mt_allow_pings'] ) {
5101
					case 'closed':
5102
						$ping_status = 'closed';
5103
						break;
5104
					case 'open':
5105
						$ping_status = 'open';
5106
						break;
5107
					default:
5108
						$ping_status = get_default_comment_status( $post_type, 'pingback' );
5109
						break;
5110
				}
5111
			} else {
5112
				switch ( (int) $content_struct['mt_allow_pings'] ) {
5113
					case 0:
5114
						$ping_status = 'closed';
5115
						break;
5116
					case 1:
5117
						$ping_status = 'open';
5118
						break;
5119
					default:
5120
						$ping_status = get_default_comment_status( $post_type, 'pingback' );
5121
						break;
5122
				}
5123
			}
5124
		} else {
5125
			$ping_status = get_default_comment_status( $post_type, 'pingback' );
5126
		}
5127
5128
		if ( $post_more )
5129
			$post_content = $post_content . '<!--more-->' . $post_more;
5130
5131
		$to_ping = null;
5132 View Code Duplication
		if ( isset( $content_struct['mt_tb_ping_urls'] ) ) {
5133
			$to_ping = $content_struct['mt_tb_ping_urls'];
5134
			if ( is_array($to_ping) )
5135
				$to_ping = implode(' ', $to_ping);
5136
		}
5137
5138
		// Do some timestamp voodoo
5139 View Code Duplication
		if ( !empty( $content_struct['date_created_gmt'] ) )
5140
			// We know this is supposed to be GMT, so we're going to slap that Z on there by force
5141
			$dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
5142
		elseif ( !empty( $content_struct['dateCreated']) )
5143
			$dateCreated = $content_struct['dateCreated']->getIso();
5144
5145
		if ( !empty( $dateCreated ) ) {
5146
			$post_date = get_date_from_gmt(iso8601_to_datetime($dateCreated));
5147
			$post_date_gmt = iso8601_to_datetime($dateCreated, 'GMT');
5148
		} else {
5149
			$post_date = '';
5150
			$post_date_gmt = '';
5151
		}
5152
5153
		$post_category = array();
5154 View Code Duplication
		if ( isset( $content_struct['categories'] ) ) {
5155
			$catnames = $content_struct['categories'];
5156
5157
			if ( is_array($catnames) ) {
5158
				foreach ($catnames as $cat) {
5159
					$post_category[] = get_cat_ID($cat);
5160
				}
5161
			}
5162
		}
5163
5164
		$postdata = compact('post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'comment_status', 'ping_status', 'to_ping', 'post_type', 'post_name', 'post_password', 'post_parent', 'menu_order', 'tags_input', 'page_template');
5165
5166
		$post_ID = $postdata['ID'] = get_default_post_to_edit( $post_type, true )->ID;
5167
5168
		// Only posts can be sticky
5169 View Code Duplication
		if ( $post_type == 'post' && isset( $content_struct['sticky'] ) ) {
5170
			$data = $postdata;
5171
			$data['sticky'] = $content_struct['sticky'];
5172
			$error = $this->_toggle_sticky( $data );
5173
			if ( $error ) {
5174
				return $error;
5175
			}
5176
		}
5177
5178
		if ( isset($content_struct['custom_fields']) )
5179
			$this->set_custom_fields($post_ID, $content_struct['custom_fields']);
5180
5181 View Code Duplication
		if ( isset ( $content_struct['wp_post_thumbnail'] ) ) {
5182
			if ( set_post_thumbnail( $post_ID, $content_struct['wp_post_thumbnail'] ) === false )
5183
				return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
5184
5185
			unset( $content_struct['wp_post_thumbnail'] );
5186
		}
5187
5188
		// Handle enclosures
5189
		$thisEnclosure = isset($content_struct['enclosure']) ? $content_struct['enclosure'] : null;
5190
		$this->add_enclosure_if_new($post_ID, $thisEnclosure);
5191
5192
		$this->attach_uploads( $post_ID, $post_content );
5193
5194
		// Handle post formats if assigned, value is validated earlier
5195
		// in this function
5196
		if ( isset( $content_struct['wp_post_format'] ) )
5197
			set_post_format( $post_ID, $content_struct['wp_post_format'] );
5198
5199
		$post_ID = wp_insert_post( $postdata, true );
5200
		if ( is_wp_error( $post_ID ) )
5201
			return new IXR_Error(500, $post_ID->get_error_message());
5202
5203
		if ( !$post_ID )
5204
			return new IXR_Error(500, __('Sorry, your entry could not be posted.'));
5205
5206
		/**
5207
		 * Fires after a new post has been successfully created via the XML-RPC MovableType API.
5208
		 *
5209
		 * @since 3.4.0
5210
		 *
5211
		 * @param int   $post_ID ID of the new post.
5212
		 * @param array $args    An array of arguments to create the new post.
5213
		 */
5214
		do_action( 'xmlrpc_call_success_mw_newPost', $post_ID, $args );
5215
5216
		return strval($post_ID);
5217
	}
5218
5219
	/**
5220
	 * Adds an enclosure to a post if it's new.
5221
	 *
5222
	 * @since 2.8.0
5223
	 *
5224
	 * @param integer $post_ID   Post ID.
5225
	 * @param array   $enclosure Enclosure data.
5226
	 */
5227
	public function add_enclosure_if_new( $post_ID, $enclosure ) {
5228
		if ( is_array( $enclosure ) && isset( $enclosure['url'] ) && isset( $enclosure['length'] ) && isset( $enclosure['type'] ) ) {
5229
			$encstring = $enclosure['url'] . "\n" . $enclosure['length'] . "\n" . $enclosure['type'] . "\n";
5230
			$found = false;
5231
			if ( $enclosures = get_post_meta( $post_ID, 'enclosure' ) ) {
5232
				foreach ( $enclosures as $enc ) {
5233
					// This method used to omit the trailing new line. #23219
5234
					if ( rtrim( $enc, "\n" ) == rtrim( $encstring, "\n" ) ) {
5235
						$found = true;
5236
						break;
5237
					}
5238
				}
5239
			}
5240
			if ( ! $found )
5241
				add_post_meta( $post_ID, 'enclosure', $encstring );
5242
		}
5243
	}
5244
5245
	/**
5246
	 * Attach upload to a post.
5247
	 *
5248
	 * @since 2.1.0
5249
	 *
5250
	 * @global wpdb $wpdb WordPress database abstraction object.
5251
	 *
5252
	 * @param int $post_ID Post ID.
5253
	 * @param string $post_content Post Content for attachment.
5254
	 */
5255
	public function attach_uploads( $post_ID, $post_content ) {
5256
		global $wpdb;
5257
5258
		// find any unattached files
5259
		$attachments = $wpdb->get_results( "SELECT ID, guid FROM {$wpdb->posts} WHERE post_parent = '0' AND post_type = 'attachment'" );
5260
		if ( is_array( $attachments ) ) {
5261
			foreach ( $attachments as $file ) {
5262
				if ( ! empty( $file->guid ) && strpos( $post_content, $file->guid ) !== false )
5263
					$wpdb->update($wpdb->posts, array('post_parent' => $post_ID), array('ID' => $file->ID) );
5264
			}
5265
		}
5266
	}
5267
5268
	/**
5269
	 * Edit a post.
5270
	 *
5271
	 * @since 1.5.0
5272
	 *
5273
	 * @param array  $args {
5274
	 *     Method arguments. Note: arguments must be ordered as documented.
5275
	 *
5276
	 *     @type int    $blog_id (unused)
5277
	 *     @type string $username
5278
	 *     @type string $password
5279
	 *     @type array  $content_struct
5280
	 *     @type int    $publish
5281
	 * }
5282
	 * @return bool|IXR_Error True on success.
5283
	 */
5284
	public function mw_editPost( $args ) {
5285
		$this->escape( $args );
5286
5287
		$post_ID        = (int) $args[0];
5288
		$username       = $args[1];
5289
		$password       = $args[2];
5290
		$content_struct = $args[3];
5291
		$publish        = isset( $args[4] ) ? $args[4] : 0;
5292
5293
		if ( ! $user = $this->login($username, $password) )
5294
			return $this->error;
5295
5296
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5297
		do_action( 'xmlrpc_call', 'metaWeblog.editPost' );
5298
5299
		$postdata = get_post( $post_ID, ARRAY_A );
5300
5301
		/*
5302
		 * If there is no post data for the give post id, stop now and return an error.
5303
		 * Otherwise a new post will be created (which was the old behavior).
5304
		 */
5305
		if ( ! $postdata || empty( $postdata[ 'ID' ] ) )
5306
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
5307
5308
		if ( ! current_user_can( 'edit_post', $post_ID ) )
5309
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
5310
5311
		// Use wp.editPost to edit post types other than post and page.
5312
		if ( ! in_array( $postdata[ 'post_type' ], array( 'post', 'page' ) ) )
5313
			return new IXR_Error( 401, __( 'Invalid post type.' ) );
5314
5315
		// Thwart attempt to change the post type.
5316
		if ( ! empty( $content_struct[ 'post_type' ] ) && ( $content_struct['post_type'] != $postdata[ 'post_type' ] ) )
5317
			return new IXR_Error( 401, __( 'The post type may not be changed.' ) );
5318
5319
		// Check for a valid post format if one was given
5320 View Code Duplication
		if ( isset( $content_struct['wp_post_format'] ) ) {
5321
			$content_struct['wp_post_format'] = sanitize_key( $content_struct['wp_post_format'] );
5322
			if ( !array_key_exists( $content_struct['wp_post_format'], get_post_format_strings() ) ) {
5323
				return new IXR_Error( 404, __( 'Invalid post format.' ) );
5324
			}
5325
		}
5326
5327
		$this->escape($postdata);
5328
5329
		$ID = $postdata['ID'];
5330
		$post_content = $postdata['post_content'];
5331
		$post_title = $postdata['post_title'];
5332
		$post_excerpt = $postdata['post_excerpt'];
5333
		$post_password = $postdata['post_password'];
5334
		$post_parent = $postdata['post_parent'];
5335
		$post_type = $postdata['post_type'];
5336
		$menu_order = $postdata['menu_order'];
5337
5338
		// Let WordPress manage slug if none was provided.
5339
		$post_name = $postdata['post_name'];
5340
		if ( isset($content_struct['wp_slug']) )
5341
			$post_name = $content_struct['wp_slug'];
5342
5343
		// Only use a password if one was given.
5344
		if ( isset($content_struct['wp_password']) )
5345
			$post_password = $content_struct['wp_password'];
5346
5347
		// Only set a post parent if one was given.
5348
		if ( isset($content_struct['wp_page_parent_id']) )
5349
			$post_parent = $content_struct['wp_page_parent_id'];
5350
5351
		// Only set the menu_order if it was given.
5352
		if ( isset($content_struct['wp_page_order']) )
5353
			$menu_order = $content_struct['wp_page_order'];
5354
5355
		$page_template = null;
5356
		if ( ! empty( $content_struct['wp_page_template'] ) && 'page' == $post_type )
5357
			$page_template = $content_struct['wp_page_template'];
5358
5359
		$post_author = $postdata['post_author'];
5360
5361
		// Only set the post_author if one is set.
5362
		if ( isset( $content_struct['wp_author_id'] ) ) {
5363
			// Check permissions if attempting to switch author to or from another user.
5364
			if ( $user->ID != $content_struct['wp_author_id'] || $user->ID != $post_author ) {
5365 View Code Duplication
				switch ( $post_type ) {
5366
					case 'post':
5367
						if ( ! current_user_can( 'edit_others_posts' ) ) {
5368
							return new IXR_Error( 401, __( 'Sorry, you are not allowed to change the post author as this user.' ) );
5369
						}
5370
						break;
5371
					case 'page':
5372
						if ( ! current_user_can( 'edit_others_pages' ) ) {
5373
							return new IXR_Error( 401, __( 'Sorry, you are not allowed to change the page author as this user.' ) );
5374
						}
5375
						break;
5376
					default:
5377
						return new IXR_Error( 401, __( 'Invalid post type.' ) );
5378
				}
5379
				$post_author = $content_struct['wp_author_id'];
5380
			}
5381
		}
5382
5383
		if ( isset($content_struct['mt_allow_comments']) ) {
5384
			if ( !is_numeric($content_struct['mt_allow_comments']) ) {
5385
				switch ( $content_struct['mt_allow_comments'] ) {
5386
					case 'closed':
5387
						$comment_status = 'closed';
5388
						break;
5389
					case 'open':
5390
						$comment_status = 'open';
5391
						break;
5392
					default:
5393
						$comment_status = get_default_comment_status( $post_type );
5394
						break;
5395
				}
5396
			} else {
5397
				switch ( (int) $content_struct['mt_allow_comments'] ) {
5398
					case 0:
5399
					case 2:
5400
						$comment_status = 'closed';
5401
						break;
5402
					case 1:
5403
						$comment_status = 'open';
5404
						break;
5405
					default:
5406
						$comment_status = get_default_comment_status( $post_type );
5407
						break;
5408
				}
5409
			}
5410
		}
5411
5412
		if ( isset($content_struct['mt_allow_pings']) ) {
5413
			if ( !is_numeric($content_struct['mt_allow_pings']) ) {
5414
				switch ( $content_struct['mt_allow_pings'] ) {
5415
					case 'closed':
5416
						$ping_status = 'closed';
5417
						break;
5418
					case 'open':
5419
						$ping_status = 'open';
5420
						break;
5421
					default:
5422
						$ping_status = get_default_comment_status( $post_type, 'pingback' );
5423
						break;
5424
				}
5425
			} else {
5426
				switch ( (int) $content_struct["mt_allow_pings"] ) {
5427
					case 0:
5428
						$ping_status = 'closed';
5429
						break;
5430
					case 1:
5431
						$ping_status = 'open';
5432
						break;
5433
					default:
5434
						$ping_status = get_default_comment_status( $post_type, 'pingback' );
5435
						break;
5436
				}
5437
			}
5438
		}
5439
5440
		if ( isset( $content_struct['title'] ) )
5441
			$post_title =  $content_struct['title'];
5442
5443
		if ( isset( $content_struct['description'] ) )
5444
			$post_content = $content_struct['description'];
5445
5446
		$post_category = array();
5447 View Code Duplication
		if ( isset( $content_struct['categories'] ) ) {
5448
			$catnames = $content_struct['categories'];
5449
			if ( is_array($catnames) ) {
5450
				foreach ($catnames as $cat) {
5451
					$post_category[] = get_cat_ID($cat);
5452
				}
5453
			}
5454
		}
5455
5456
		if ( isset( $content_struct['mt_excerpt'] ) )
5457
			$post_excerpt =  $content_struct['mt_excerpt'];
5458
5459
		$post_more = isset( $content_struct['mt_text_more'] ) ? $content_struct['mt_text_more'] : null;
5460
5461
		$post_status = $publish ? 'publish' : 'draft';
5462 View Code Duplication
		if ( isset( $content_struct["{$post_type}_status"] ) ) {
5463
			switch( $content_struct["{$post_type}_status"] ) {
5464
				case 'draft':
5465
				case 'pending':
5466
				case 'private':
5467
				case 'publish':
5468
					$post_status = $content_struct["{$post_type}_status"];
5469
					break;
5470
				default:
5471
					$post_status = $publish ? 'publish' : 'draft';
5472
					break;
5473
			}
5474
		}
5475
5476
		$tags_input = isset( $content_struct['mt_keywords'] ) ? $content_struct['mt_keywords'] : null;
5477
5478
		if ( 'publish' == $post_status || 'private' == $post_status ) {
5479
			if ( 'page' == $post_type && ! current_user_can( 'publish_pages' ) ) {
5480
				return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this page.' ) );
5481
			} elseif ( ! current_user_can( 'publish_posts' ) ) {
5482
				return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) );
5483
			}
5484
		}
5485
5486
		if ( $post_more )
5487
			$post_content = $post_content . "<!--more-->" . $post_more;
5488
5489
		$to_ping = null;
5490 View Code Duplication
		if ( isset( $content_struct['mt_tb_ping_urls'] ) ) {
5491
			$to_ping = $content_struct['mt_tb_ping_urls'];
5492
			if ( is_array($to_ping) )
5493
				$to_ping = implode(' ', $to_ping);
5494
		}
5495
5496
		// Do some timestamp voodoo.
5497 View Code Duplication
		if ( !empty( $content_struct['date_created_gmt'] ) )
5498
			// We know this is supposed to be GMT, so we're going to slap that Z on there by force.
5499
			$dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
5500
		elseif ( !empty( $content_struct['dateCreated']) )
5501
			$dateCreated = $content_struct['dateCreated']->getIso();
5502
5503
		// Default to not flagging the post date to be edited unless it's intentional.
5504
		$edit_date = false;
5505
5506
		if ( !empty( $dateCreated ) ) {
5507
			$post_date = get_date_from_gmt(iso8601_to_datetime($dateCreated));
5508
			$post_date_gmt = iso8601_to_datetime($dateCreated, 'GMT');
5509
5510
			// Flag the post date to be edited.
5511
			$edit_date = true;
5512
		} else {
5513
			$post_date     = $postdata['post_date'];
5514
			$post_date_gmt = $postdata['post_date_gmt'];
5515
		}
5516
5517
		// We've got all the data -- post it.
5518
		$newpost = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'comment_status', 'ping_status', 'edit_date', 'post_date', 'post_date_gmt', 'to_ping', 'post_name', 'post_password', 'post_parent', 'menu_order', 'post_author', 'tags_input', 'page_template');
5519
5520
		$result = wp_update_post($newpost, true);
5521
		if ( is_wp_error( $result ) )
5522
			return new IXR_Error(500, $result->get_error_message());
5523
5524
		if ( !$result )
5525
			return new IXR_Error(500, __('Sorry, your entry could not be edited.'));
5526
5527
		// Only posts can be sticky
5528 View Code Duplication
		if ( $post_type == 'post' && isset( $content_struct['sticky'] ) ) {
5529
			$data = $newpost;
5530
			$data['sticky'] = $content_struct['sticky'];
5531
			$data['post_type'] = 'post';
5532
			$error = $this->_toggle_sticky( $data, true );
5533
			if ( $error ) {
5534
				return $error;
5535
			}
5536
		}
5537
5538
		if ( isset($content_struct['custom_fields']) )
5539
			$this->set_custom_fields($post_ID, $content_struct['custom_fields']);
5540
5541
		if ( isset ( $content_struct['wp_post_thumbnail'] ) ) {
5542
5543
			// Empty value deletes, non-empty value adds/updates.
5544 View Code Duplication
			if ( empty( $content_struct['wp_post_thumbnail'] ) ) {
5545
				delete_post_thumbnail( $post_ID );
5546
			} else {
5547
				if ( set_post_thumbnail( $post_ID, $content_struct['wp_post_thumbnail'] ) === false )
5548
					return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
5549
			}
5550
			unset( $content_struct['wp_post_thumbnail'] );
5551
		}
5552
5553
		// Handle enclosures.
5554
		$thisEnclosure = isset($content_struct['enclosure']) ? $content_struct['enclosure'] : null;
5555
		$this->add_enclosure_if_new($post_ID, $thisEnclosure);
5556
5557
		$this->attach_uploads( $ID, $post_content );
5558
5559
		// Handle post formats if assigned, validation is handled earlier in this function.
5560
		if ( isset( $content_struct['wp_post_format'] ) )
5561
			set_post_format( $post_ID, $content_struct['wp_post_format'] );
5562
5563
		/**
5564
		 * Fires after a post has been successfully updated via the XML-RPC MovableType API.
5565
		 *
5566
		 * @since 3.4.0
5567
		 *
5568
		 * @param int   $post_ID ID of the updated post.
5569
		 * @param array $args    An array of arguments to update the post.
5570
		 */
5571
		do_action( 'xmlrpc_call_success_mw_editPost', $post_ID, $args );
5572
5573
		return true;
5574
	}
5575
5576
	/**
5577
	 * Retrieve post.
5578
	 *
5579
	 * @since 1.5.0
5580
	 *
5581
	 * @param array  $args {
5582
	 *     Method arguments. Note: arguments must be ordered as documented.
5583
	 *
5584
	 *     @type int    $blog_id (unused)
5585
	 *     @type int    $post_ID
5586
	 *     @type string $username
5587
	 *     @type string $password
5588
	 * }
5589
	 * @return array|IXR_Error
0 ignored issues
show
Consider making the return type a bit more specific; maybe use IXR_Error|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
5590
	 */
5591
	public function mw_getPost( $args ) {
5592
		$this->escape( $args );
5593
5594
		$post_ID  = (int) $args[0];
5595
		$username = $args[1];
5596
		$password = $args[2];
5597
5598
		if ( !$user = $this->login($username, $password) )
5599
			return $this->error;
5600
5601
		$postdata = get_post($post_ID, ARRAY_A);
5602
		if ( ! $postdata )
5603
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
5604
5605
		if ( !current_user_can( 'edit_post', $post_ID ) )
5606
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
5607
5608
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5609
		do_action( 'xmlrpc_call', 'metaWeblog.getPost' );
5610
5611
		if ($postdata['post_date'] != '') {
5612
			$post_date = $this->_convert_date( $postdata['post_date'] );
5613
			$post_date_gmt = $this->_convert_date_gmt( $postdata['post_date_gmt'],  $postdata['post_date'] );
5614
			$post_modified = $this->_convert_date( $postdata['post_modified'] );
5615
			$post_modified_gmt = $this->_convert_date_gmt( $postdata['post_modified_gmt'], $postdata['post_modified'] );
5616
5617
			$categories = array();
5618
			$catids = wp_get_post_categories($post_ID);
5619
			foreach ($catids as $catid)
0 ignored issues
show
The expression $catids of type array|object<WP_Error> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
5620
				$categories[] = get_cat_name($catid);
5621
5622
			$tagnames = array();
5623
			$tags = wp_get_post_tags( $post_ID );
5624 View Code Duplication
			if ( !empty( $tags ) ) {
5625
				foreach ( $tags as $tag )
0 ignored issues
show
The expression $tags of type array|object<WP_Error> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
5626
					$tagnames[] = $tag->name;
5627
				$tagnames = implode( ', ', $tagnames );
5628
			} else {
5629
				$tagnames = '';
5630
			}
5631
5632
			$post = get_extended($postdata['post_content']);
5633
			$link = get_permalink($postdata['ID']);
5634
5635
			// Get the author info.
5636
			$author = get_userdata($postdata['post_author']);
5637
5638
			$allow_comments = ('open' == $postdata['comment_status']) ? 1 : 0;
5639
			$allow_pings = ('open' == $postdata['ping_status']) ? 1 : 0;
5640
5641
			// Consider future posts as published
5642
			if ( $postdata['post_status'] === 'future' )
5643
				$postdata['post_status'] = 'publish';
5644
5645
			// Get post format
5646
			$post_format = get_post_format( $post_ID );
5647
			if ( empty( $post_format ) )
5648
				$post_format = 'standard';
5649
5650
			$sticky = false;
5651
			if ( is_sticky( $post_ID ) )
5652
				$sticky = true;
5653
5654
			$enclosure = array();
5655
			foreach ( (array) get_post_custom($post_ID) as $key => $val) {
5656
				if ($key == 'enclosure') {
5657
					foreach ( (array) $val as $enc ) {
5658
						$encdata = explode("\n", $enc);
5659
						$enclosure['url'] = trim(htmlspecialchars($encdata[0]));
5660
						$enclosure['length'] = (int) trim($encdata[1]);
5661
						$enclosure['type'] = trim($encdata[2]);
5662
						break 2;
5663
					}
5664
				}
5665
			}
5666
5667
			$resp = array(
5668
				'dateCreated' => $post_date,
5669
				'userid' => $postdata['post_author'],
5670
				'postid' => $postdata['ID'],
5671
				'description' => $post['main'],
5672
				'title' => $postdata['post_title'],
5673
				'link' => $link,
5674
				'permaLink' => $link,
5675
				// commented out because no other tool seems to use this
5676
				//	      'content' => $entry['post_content'],
5677
				'categories' => $categories,
5678
				'mt_excerpt' => $postdata['post_excerpt'],
5679
				'mt_text_more' => $post['extended'],
5680
				'wp_more_text' => $post['more_text'],
5681
				'mt_allow_comments' => $allow_comments,
5682
				'mt_allow_pings' => $allow_pings,
5683
				'mt_keywords' => $tagnames,
5684
				'wp_slug' => $postdata['post_name'],
5685
				'wp_password' => $postdata['post_password'],
5686
				'wp_author_id' => (string) $author->ID,
5687
				'wp_author_display_name' => $author->display_name,
5688
				'date_created_gmt' => $post_date_gmt,
5689
				'post_status' => $postdata['post_status'],
5690
				'custom_fields' => $this->get_custom_fields($post_ID),
5691
				'wp_post_format' => $post_format,
5692
				'sticky' => $sticky,
5693
				'date_modified' => $post_modified,
5694
				'date_modified_gmt' => $post_modified_gmt
5695
			);
5696
5697
			if ( !empty($enclosure) ) $resp['enclosure'] = $enclosure;
5698
5699
			$resp['wp_post_thumbnail'] = get_post_thumbnail_id( $postdata['ID'] );
5700
5701
			return $resp;
5702
		} else {
5703
			return new IXR_Error(404, __('Sorry, no such post.'));
5704
		}
5705
	}
5706
5707
	/**
5708
	 * Retrieve list of recent posts.
5709
	 *
5710
	 * @since 1.5.0
5711
	 *
5712
	 * @param array  $args {
5713
	 *     Method arguments. Note: arguments must be ordered as documented.
5714
	 *
5715
	 *     @type int    $blog_id (unused)
5716
	 *     @type string $username
5717
	 *     @type string $password
5718
	 *     @type int    $numberposts
5719
	 * }
5720
	 * @return array|IXR_Error
5721
	 */
5722
	public function mw_getRecentPosts( $args ) {
5723
		$this->escape( $args );
5724
5725
		$username = $args[1];
5726
		$password = $args[2];
5727 View Code Duplication
		if ( isset( $args[3] ) )
5728
			$query = array( 'numberposts' => absint( $args[3] ) );
5729
		else
5730
			$query = array();
5731
5732
		if ( !$user = $this->login($username, $password) )
5733
			return $this->error;
5734
5735
		if ( ! current_user_can( 'edit_posts' ) )
5736
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
5737
5738
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5739
		do_action( 'xmlrpc_call', 'metaWeblog.getRecentPosts' );
5740
5741
		$posts_list = wp_get_recent_posts( $query );
5742
5743
		if ( !$posts_list )
5744
			return array();
5745
5746
		$recent_posts = array();
5747
		foreach ($posts_list as $entry) {
5748
			if ( !current_user_can( 'edit_post', $entry['ID'] ) )
5749
				continue;
5750
5751
			$post_date = $this->_convert_date( $entry['post_date'] );
5752
			$post_date_gmt = $this->_convert_date_gmt( $entry['post_date_gmt'], $entry['post_date'] );
5753
			$post_modified = $this->_convert_date( $entry['post_modified'] );
5754
			$post_modified_gmt = $this->_convert_date_gmt( $entry['post_modified_gmt'], $entry['post_modified'] );
5755
5756
			$categories = array();
5757
			$catids = wp_get_post_categories($entry['ID']);
5758
			foreach ( $catids as $catid )
0 ignored issues
show
The expression $catids of type array|object<WP_Error> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
5759
				$categories[] = get_cat_name($catid);
5760
5761
			$tagnames = array();
5762
			$tags = wp_get_post_tags( $entry['ID'] );
5763 View Code Duplication
			if ( !empty( $tags ) ) {
5764
				foreach ( $tags as $tag ) {
0 ignored issues
show
The expression $tags of type array|object<WP_Error> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
5765
					$tagnames[] = $tag->name;
5766
				}
5767
				$tagnames = implode( ', ', $tagnames );
5768
			} else {
5769
				$tagnames = '';
5770
			}
5771
5772
			$post = get_extended($entry['post_content']);
5773
			$link = get_permalink($entry['ID']);
5774
5775
			// Get the post author info.
5776
			$author = get_userdata($entry['post_author']);
5777
5778
			$allow_comments = ('open' == $entry['comment_status']) ? 1 : 0;
5779
			$allow_pings = ('open' == $entry['ping_status']) ? 1 : 0;
5780
5781
			// Consider future posts as published
5782
			if ( $entry['post_status'] === 'future' )
5783
				$entry['post_status'] = 'publish';
5784
5785
			// Get post format
5786
			$post_format = get_post_format( $entry['ID'] );
5787
			if ( empty( $post_format ) )
5788
				$post_format = 'standard';
5789
5790
			$recent_posts[] = array(
5791
				'dateCreated' => $post_date,
5792
				'userid' => $entry['post_author'],
5793
				'postid' => (string) $entry['ID'],
5794
				'description' => $post['main'],
5795
				'title' => $entry['post_title'],
5796
				'link' => $link,
5797
				'permaLink' => $link,
5798
				// commented out because no other tool seems to use this
5799
				// 'content' => $entry['post_content'],
5800
				'categories' => $categories,
5801
				'mt_excerpt' => $entry['post_excerpt'],
5802
				'mt_text_more' => $post['extended'],
5803
				'wp_more_text' => $post['more_text'],
5804
				'mt_allow_comments' => $allow_comments,
5805
				'mt_allow_pings' => $allow_pings,
5806
				'mt_keywords' => $tagnames,
5807
				'wp_slug' => $entry['post_name'],
5808
				'wp_password' => $entry['post_password'],
5809
				'wp_author_id' => (string) $author->ID,
5810
				'wp_author_display_name' => $author->display_name,
5811
				'date_created_gmt' => $post_date_gmt,
5812
				'post_status' => $entry['post_status'],
5813
				'custom_fields' => $this->get_custom_fields($entry['ID']),
5814
				'wp_post_format' => $post_format,
5815
				'date_modified' => $post_modified,
5816
				'date_modified_gmt' => $post_modified_gmt,
5817
				'sticky' => ( $entry['post_type'] === 'post' && is_sticky( $entry['ID'] ) ),
5818
				'wp_post_thumbnail' => get_post_thumbnail_id( $entry['ID'] )
5819
			);
5820
		}
5821
5822
		return $recent_posts;
5823
	}
5824
5825
	/**
5826
	 * Retrieve the list of categories on a given blog.
5827
	 *
5828
	 * @since 1.5.0
5829
	 *
5830
	 * @param array  $args {
5831
	 *     Method arguments. Note: arguments must be ordered as documented.
5832
	 *
5833
	 *     @type int    $blog_id (unused)
5834
	 *     @type string $username
5835
	 *     @type string $password
5836
	 * }
5837
	 * @return array|IXR_Error
5838
	 */
5839
	public function mw_getCategories( $args ) {
5840
		$this->escape( $args );
5841
5842
		$username = $args[1];
5843
		$password = $args[2];
5844
5845
		if ( !$user = $this->login($username, $password) )
5846
			return $this->error;
5847
5848
		if ( !current_user_can( 'edit_posts' ) )
5849
			return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
5850
5851
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5852
		do_action( 'xmlrpc_call', 'metaWeblog.getCategories' );
5853
5854
		$categories_struct = array();
5855
5856
		if ( $cats = get_categories(array('get' => 'all')) ) {
5857
			foreach ( $cats as $cat ) {
5858
				$struct = array();
5859
				$struct['categoryId'] = $cat->term_id;
5860
				$struct['parentId'] = $cat->parent;
5861
				$struct['description'] = $cat->name;
5862
				$struct['categoryDescription'] = $cat->description;
5863
				$struct['categoryName'] = $cat->name;
5864
				$struct['htmlUrl'] = esc_html(get_category_link($cat->term_id));
0 ignored issues
show
It seems like get_category_link($cat->term_id) targeting get_category_link() can also be of type object<WP_Error>; however, esc_html() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
5865
				$struct['rssUrl'] = esc_html(get_category_feed_link($cat->term_id, 'rss2'));
0 ignored issues
show
It seems like get_category_feed_link($cat->term_id, 'rss2') targeting get_category_feed_link() can also be of type false; however, esc_html() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
5866
5867
				$categories_struct[] = $struct;
5868
			}
5869
		}
5870
5871
		return $categories_struct;
5872
	}
5873
5874
	/**
5875
	 * Uploads a file, following your settings.
5876
	 *
5877
	 * Adapted from a patch by Johann Richard.
5878
	 *
5879
	 * @link http://mycvs.org/archives/2004/06/30/file-upload-to-wordpress-in-ecto/
5880
	 *
5881
	 * @since 1.5.0
5882
	 *
5883
	 * @global wpdb $wpdb WordPress database abstraction object.
5884
	 *
5885
	 * @param array  $args {
5886
	 *     Method arguments. Note: arguments must be ordered as documented.
5887
	 *
5888
	 *     @type int    $blog_id (unused)
5889
	 *     @type string $username
5890
	 *     @type string $password
5891
	 *     @type array  $data
5892
	 * }
5893
	 * @return array|IXR_Error
0 ignored issues
show
Consider making the return type a bit more specific; maybe use IXR_Error|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
5894
	 */
5895
	public function mw_newMediaObject( $args ) {
5896
		global $wpdb;
5897
5898
		$username = $this->escape( $args[1] );
5899
		$password = $this->escape( $args[2] );
5900
		$data     = $args[3];
5901
5902
		$name = sanitize_file_name( $data['name'] );
5903
		$type = $data['type'];
5904
		$bits = $data['bits'];
5905
5906
		if ( !$user = $this->login($username, $password) )
5907
			return $this->error;
5908
5909
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5910
		do_action( 'xmlrpc_call', 'metaWeblog.newMediaObject' );
5911
5912
		if ( !current_user_can('upload_files') ) {
5913
			$this->error = new IXR_Error( 401, __( 'Sorry, you are not allowed to upload files.' ) );
5914
			return $this->error;
5915
		}
5916
5917
		if ( is_multisite() && upload_is_user_over_quota( false ) ) {
5918
			$this->error = new IXR_Error( 401, __( 'Sorry, you have used your space allocation.' ) );
5919
			return $this->error;
5920
		}
5921
5922
		/**
5923
		 * Filters whether to preempt the XML-RPC media upload.
5924
		 *
5925
		 * Passing a truthy value will effectively short-circuit the media upload,
5926
		 * returning that value as a 500 error instead.
5927
		 *
5928
		 * @since 2.1.0
5929
		 *
5930
		 * @param bool $error Whether to pre-empt the media upload. Default false.
5931
		 */
5932
		if ( $upload_err = apply_filters( 'pre_upload_error', false ) ) {
5933
			return new IXR_Error( 500, $upload_err );
5934
		}
5935
5936
		$upload = wp_upload_bits($name, null, $bits);
5937
		if ( ! empty($upload['error']) ) {
5938
			/* translators: 1: file name, 2: error message */
5939
			$errorString = sprintf( __( 'Could not write file %1$s (%2$s).' ), $name, $upload['error'] );
5940
			return new IXR_Error( 500, $errorString );
5941
		}
5942
		// Construct the attachment array
5943
		$post_id = 0;
5944
		if ( ! empty( $data['post_id'] ) ) {
5945
			$post_id = (int) $data['post_id'];
5946
5947
			if ( ! current_user_can( 'edit_post', $post_id ) )
5948
				return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
5949
		}
5950
		$attachment = array(
5951
			'post_title' => $name,
5952
			'post_content' => '',
5953
			'post_type' => 'attachment',
5954
			'post_parent' => $post_id,
5955
			'post_mime_type' => $type,
5956
			'guid' => $upload[ 'url' ]
5957
		);
5958
5959
		// Save the data
5960
		$id = wp_insert_attachment( $attachment, $upload[ 'file' ], $post_id );
5961
		wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $upload['file'] ) );
5962
5963
		/**
5964
		 * Fires after a new attachment has been added via the XML-RPC MovableType API.
5965
		 *
5966
		 * @since 3.4.0
5967
		 *
5968
		 * @param int   $id   ID of the new attachment.
5969
		 * @param array $args An array of arguments to add the attachment.
5970
		 */
5971
		do_action( 'xmlrpc_call_success_mw_newMediaObject', $id, $args );
5972
5973
		$struct = $this->_prepare_media_item( get_post( $id ) );
0 ignored issues
show
It seems like get_post($id) targeting get_post() can also be of type array or null; however, wp_xmlrpc_server::_prepare_media_item() does only seem to accept object, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
5974
5975
		// Deprecated values
5976
		$struct['id']   = $struct['attachment_id'];
5977
		$struct['file'] = $struct['title'];
5978
		$struct['url']  = $struct['link'];
5979
5980
		return $struct;
5981
	}
5982
5983
	/* MovableType API functions
5984
	 * specs on http://www.movabletype.org/docs/mtmanual_programmatic.html
5985
	 */
5986
5987
	/**
5988
	 * Retrieve the post titles of recent posts.
5989
	 *
5990
	 * @since 1.5.0
5991
	 *
5992
	 * @param array  $args {
5993
	 *     Method arguments. Note: arguments must be ordered as documented.
5994
	 *
5995
	 *     @type int    $blog_id (unused)
5996
	 *     @type string $username
5997
	 *     @type string $password
5998
	 *     @type int    $numberposts
5999
	 * }
6000
	 * @return array|IXR_Error
6001
	 */
6002
	public function mt_getRecentPostTitles( $args ) {
6003
		$this->escape( $args );
6004
6005
		$username = $args[1];
6006
		$password = $args[2];
6007 View Code Duplication
		if ( isset( $args[3] ) )
6008
			$query = array( 'numberposts' => absint( $args[3] ) );
6009
		else
6010
			$query = array();
6011
6012
		if ( !$user = $this->login($username, $password) )
6013
			return $this->error;
6014
6015
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6016
		do_action( 'xmlrpc_call', 'mt.getRecentPostTitles' );
6017
6018
		$posts_list = wp_get_recent_posts( $query );
6019
6020
		if ( !$posts_list ) {
6021
			$this->error = new IXR_Error(500, __('Either there are no posts, or something went wrong.'));
6022
			return $this->error;
6023
		}
6024
6025
		$recent_posts = array();
6026
6027
		foreach ($posts_list as $entry) {
6028
			if ( !current_user_can( 'edit_post', $entry['ID'] ) )
6029
				continue;
6030
6031
			$post_date = $this->_convert_date( $entry['post_date'] );
6032
			$post_date_gmt = $this->_convert_date_gmt( $entry['post_date_gmt'], $entry['post_date'] );
6033
6034
			$recent_posts[] = array(
6035
				'dateCreated' => $post_date,
6036
				'userid' => $entry['post_author'],
6037
				'postid' => (string) $entry['ID'],
6038
				'title' => $entry['post_title'],
6039
				'post_status' => $entry['post_status'],
6040
				'date_created_gmt' => $post_date_gmt
6041
			);
6042
		}
6043
6044
		return $recent_posts;
6045
	}
6046
6047
	/**
6048
	 * Retrieve list of all categories on blog.
6049
	 *
6050
	 * @since 1.5.0
6051
	 *
6052
	 * @param array  $args {
6053
	 *     Method arguments. Note: arguments must be ordered as documented.
6054
	 *
6055
	 *     @type int    $blog_id (unused)
6056
	 *     @type string $username
6057
	 *     @type string $password
6058
	 * }
6059
	 * @return array|IXR_Error
6060
	 */
6061
	public function mt_getCategoryList( $args ) {
6062
		$this->escape( $args );
6063
6064
		$username = $args[1];
6065
		$password = $args[2];
6066
6067
		if ( !$user = $this->login($username, $password) )
6068
			return $this->error;
6069
6070
		if ( !current_user_can( 'edit_posts' ) )
6071
			return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
6072
6073
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6074
		do_action( 'xmlrpc_call', 'mt.getCategoryList' );
6075
6076
		$categories_struct = array();
6077
6078
		if ( $cats = get_categories(array('hide_empty' => 0, 'hierarchical' => 0)) ) {
6079
			foreach ( $cats as $cat ) {
6080
				$struct = array();
6081
				$struct['categoryId'] = $cat->term_id;
6082
				$struct['categoryName'] = $cat->name;
6083
6084
				$categories_struct[] = $struct;
6085
			}
6086
		}
6087
6088
		return $categories_struct;
6089
	}
6090
6091
	/**
6092
	 * Retrieve post categories.
6093
	 *
6094
	 * @since 1.5.0
6095
	 *
6096
	 * @param array  $args {
6097
	 *     Method arguments. Note: arguments must be ordered as documented.
6098
	 *
6099
	 *     @type int    $post_ID
6100
	 *     @type string $username
6101
	 *     @type string $password
6102
	 * }
6103
	 * @return array|IXR_Error
6104
	 */
6105
	public function mt_getPostCategories( $args ) {
6106
		$this->escape( $args );
6107
6108
		$post_ID  = (int) $args[0];
6109
		$username = $args[1];
6110
		$password = $args[2];
6111
6112
		if ( !$user = $this->login($username, $password) )
6113
			return $this->error;
6114
6115
		if ( ! get_post( $post_ID ) )
6116
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
6117
6118
		if ( !current_user_can( 'edit_post', $post_ID ) )
6119
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
6120
6121
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6122
		do_action( 'xmlrpc_call', 'mt.getPostCategories' );
6123
6124
		$categories = array();
6125
		$catids = wp_get_post_categories(intval($post_ID));
6126
		// first listed category will be the primary category
6127
		$isPrimary = true;
6128
		foreach ( $catids as $catid ) {
0 ignored issues
show
The expression $catids of type array|object<WP_Error> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
6129
			$categories[] = array(
6130
				'categoryName' => get_cat_name($catid),
6131
				'categoryId' => (string) $catid,
6132
				'isPrimary' => $isPrimary
6133
			);
6134
			$isPrimary = false;
6135
		}
6136
6137
		return $categories;
6138
	}
6139
6140
	/**
6141
	 * Sets categories for a post.
6142
	 *
6143
	 * @since 1.5.0
6144
	 *
6145
	 * @param array  $args {
6146
	 *     Method arguments. Note: arguments must be ordered as documented.
6147
	 *
6148
	 *     @type int    $post_ID
6149
	 *     @type string $username
6150
	 *     @type string $password
6151
	 *     @type array  $categories
6152
	 * }
6153
	 * @return true|IXR_Error True on success.
0 ignored issues
show
Should the return type not be IXR_Error|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
6154
	 */
6155
	public function mt_setPostCategories( $args ) {
6156
		$this->escape( $args );
6157
6158
		$post_ID    = (int) $args[0];
6159
		$username   = $args[1];
6160
		$password   = $args[2];
6161
		$categories = $args[3];
6162
6163
		if ( !$user = $this->login($username, $password) )
6164
			return $this->error;
6165
6166
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6167
		do_action( 'xmlrpc_call', 'mt.setPostCategories' );
6168
6169
		if ( ! get_post( $post_ID ) )
6170
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
6171
6172
		if ( !current_user_can('edit_post', $post_ID) )
6173
			return new IXR_Error(401, __('Sorry, you are not allowed to edit this post.'));
6174
6175
		$catids = array();
6176
		foreach ( $categories as $cat ) {
6177
			$catids[] = $cat['categoryId'];
6178
		}
6179
6180
		wp_set_post_categories($post_ID, $catids);
6181
6182
		return true;
6183
	}
6184
6185
	/**
6186
	 * Retrieve an array of methods supported by this server.
6187
	 *
6188
	 * @since 1.5.0
6189
	 *
6190
	 * @return array
0 ignored issues
show
Consider making the return type a bit more specific; maybe use array<integer|string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
6191
	 */
6192
	public function mt_supportedMethods() {
6193
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6194
		do_action( 'xmlrpc_call', 'mt.supportedMethods' );
6195
6196
		return array_keys( $this->methods );
6197
	}
6198
6199
	/**
6200
	 * Retrieve an empty array because we don't support per-post text filters.
6201
	 *
6202
	 * @since 1.5.0
6203
	 */
6204
	public function mt_supportedTextFilters() {
6205
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6206
		do_action( 'xmlrpc_call', 'mt.supportedTextFilters' );
6207
6208
		/**
6209
		 * Filters the MoveableType text filters list for XML-RPC.
6210
		 *
6211
		 * @since 2.2.0
6212
		 *
6213
		 * @param array $filters An array of text filters.
6214
		 */
6215
		return apply_filters( 'xmlrpc_text_filters', array() );
6216
	}
6217
6218
	/**
6219
	 * Retrieve trackbacks sent to a given post.
6220
	 *
6221
	 * @since 1.5.0
6222
	 *
6223
	 * @global wpdb $wpdb WordPress database abstraction object.
6224
	 *
6225
	 * @param int $post_ID
6226
	 * @return array|IXR_Error
6227
	 */
6228
	public function mt_getTrackbackPings( $post_ID ) {
6229
		global $wpdb;
6230
6231
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6232
		do_action( 'xmlrpc_call', 'mt.getTrackbackPings' );
6233
6234
		$actual_post = get_post($post_ID, ARRAY_A);
6235
6236
		if ( !$actual_post )
6237
			return new IXR_Error(404, __('Sorry, no such post.'));
6238
6239
		$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) );
6240
6241
		if ( !$comments )
6242
			return array();
6243
6244
		$trackback_pings = array();
6245
		foreach ( $comments as $comment ) {
6246
			if ( 'trackback' == $comment->comment_type ) {
6247
				$content = $comment->comment_content;
6248
				$title = substr($content, 8, (strpos($content, '</strong>') - 8));
6249
				$trackback_pings[] = array(
6250
					'pingTitle' => $title,
6251
					'pingURL'   => $comment->comment_author_url,
6252
					'pingIP'    => $comment->comment_author_IP
6253
				);
6254
			}
6255
		}
6256
6257
		return $trackback_pings;
6258
	}
6259
6260
	/**
6261
	 * Sets a post's publish status to 'publish'.
6262
	 *
6263
	 * @since 1.5.0
6264
	 *
6265
	 * @param array  $args {
6266
	 *     Method arguments. Note: arguments must be ordered as documented.
6267
	 *
6268
	 *     @type int    $post_ID
6269
	 *     @type string $username
6270
	 *     @type string $password
6271
	 * }
6272
	 * @return int|IXR_Error
0 ignored issues
show
Should the return type not be IXR_Error|WP_Error|integer?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
6273
	 */
6274
	public function mt_publishPost( $args ) {
6275
		$this->escape( $args );
6276
6277
		$post_ID  = (int) $args[0];
6278
		$username = $args[1];
6279
		$password = $args[2];
6280
6281
		if ( !$user = $this->login($username, $password) )
6282
			return $this->error;
6283
6284
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6285
		do_action( 'xmlrpc_call', 'mt.publishPost' );
6286
6287
		$postdata = get_post($post_ID, ARRAY_A);
6288
		if ( ! $postdata )
6289
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
6290
6291
		if ( !current_user_can('publish_posts') || !current_user_can('edit_post', $post_ID) )
6292
			return new IXR_Error(401, __('Sorry, you are not allowed to publish this post.'));
6293
6294
		$postdata['post_status'] = 'publish';
6295
6296
		// retain old cats
6297
		$cats = wp_get_post_categories($post_ID);
6298
		$postdata['post_category'] = $cats;
6299
		$this->escape($postdata);
6300
6301
		return wp_update_post( $postdata );
6302
	}
6303
6304
	/* PingBack functions
6305
	 * specs on www.hixie.ch/specs/pingback/pingback
6306
	 */
6307
6308
	/**
6309
	 * Retrieves a pingback and registers it.
6310
	 *
6311
	 * @since 1.5.0
6312
	 *
6313
	 * @param array  $args {
6314
	 *     Method arguments. Note: arguments must be ordered as documented.
6315
	 *
6316
	 *     @type string $pagelinkedfrom
6317
	 *     @type string $pagelinkedto
6318
	 * }
6319
	 * @return string|IXR_Error
6320
	 */
6321
	public function pingback_ping( $args ) {
6322
		global $wpdb;
6323
6324
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6325
		do_action( 'xmlrpc_call', 'pingback.ping' );
6326
6327
		$this->escape( $args );
6328
6329
		$pagelinkedfrom = str_replace( '&amp;', '&', $args[0] );
6330
		$pagelinkedto = str_replace( '&amp;', '&', $args[1] );
6331
		$pagelinkedto = str_replace( '&', '&amp;', $pagelinkedto );
6332
6333
		/**
6334
		 * Filters the pingback source URI.
6335
		 *
6336
		 * @since 3.6.0
6337
		 *
6338
		 * @param string $pagelinkedfrom URI of the page linked from.
6339
		 * @param string $pagelinkedto   URI of the page linked to.
6340
		 */
6341
		$pagelinkedfrom = apply_filters( 'pingback_ping_source_uri', $pagelinkedfrom, $pagelinkedto );
6342
6343
		if ( ! $pagelinkedfrom )
6344
			return $this->pingback_error( 0, __( 'A valid URL was not provided.' ) );
6345
6346
		// Check if the page linked to is in our site
6347
		$pos1 = strpos($pagelinkedto, str_replace(array('http://www.','http://','https://www.','https://'), '', get_option('home')));
6348
		if ( !$pos1 )
6349
			return $this->pingback_error( 0, __( 'Is there no link to us?' ) );
6350
6351
		// let's find which post is linked to
6352
		// FIXME: does url_to_postid() cover all these cases already?
6353
		//        if so, then let's use it and drop the old code.
6354
		$urltest = parse_url($pagelinkedto);
6355
		if ( $post_ID = url_to_postid($pagelinkedto) ) {
6356
			// $way
6357
		} elseif ( isset( $urltest['path'] ) && preg_match('#p/[0-9]{1,}#', $urltest['path'], $match) ) {
6358
			// the path defines the post_ID (archives/p/XXXX)
6359
			$blah = explode('/', $match[0]);
6360
			$post_ID = (int) $blah[1];
6361
		} elseif ( isset( $urltest['query'] ) && preg_match('#p=[0-9]{1,}#', $urltest['query'], $match) ) {
6362
			// the querystring defines the post_ID (?p=XXXX)
6363
			$blah = explode('=', $match[0]);
6364
			$post_ID = (int) $blah[1];
6365
		} elseif ( isset($urltest['fragment']) ) {
6366
			// an #anchor is there, it's either...
6367
			if ( intval($urltest['fragment']) ) {
6368
				// ...an integer #XXXX (simplest case)
6369
				$post_ID = (int) $urltest['fragment'];
6370
			} elseif ( preg_match('/post-[0-9]+/',$urltest['fragment']) ) {
6371
				// ...a post id in the form 'post-###'
6372
				$post_ID = preg_replace('/[^0-9]+/', '', $urltest['fragment']);
6373
			} elseif ( is_string($urltest['fragment']) ) {
6374
				// ...or a string #title, a little more complicated
6375
				$title = preg_replace('/[^a-z0-9]/i', '.', $urltest['fragment']);
6376
				$sql = $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE post_title RLIKE %s", $title );
6377
				if (! ($post_ID = $wpdb->get_var($sql)) ) {
6378
					// returning unknown error '0' is better than die()ing
6379
			  		return $this->pingback_error( 0, '' );
6380
				}
6381
			}
6382
		} else {
6383
			// TODO: Attempt to extract a post ID from the given URL
6384
	  		return $this->pingback_error( 33, __('The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.' ) );
6385
		}
6386
		$post_ID = (int) $post_ID;
6387
6388
		$post = get_post($post_ID);
6389
6390
		if ( !$post ) // Post_ID not found
6391
	  		return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.' ) );
6392
6393
		if ( $post_ID == url_to_postid($pagelinkedfrom) )
6394
			return $this->pingback_error( 0, __( 'The source URL and the target URL cannot both point to the same resource.' ) );
6395
6396
		// Check if pings are on
6397
		if ( !pings_open($post) )
6398
	  		return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.' ) );
6399
6400
		// Let's check that the remote site didn't already pingback this entry
6401
		if ( $wpdb->get_results( $wpdb->prepare("SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_author_url = %s", $post_ID, $pagelinkedfrom) ) )
6402
			return $this->pingback_error( 48, __( 'The pingback has already been registered.' ) );
6403
6404
		// very stupid, but gives time to the 'from' server to publish !
6405
		sleep(1);
6406
6407
		$remote_ip = preg_replace( '/[^0-9a-fA-F:., ]/', '', $_SERVER['REMOTE_ADDR'] );
6408
6409
		/** This filter is documented in wp-includes/class-http.php */
6410
		$user_agent = apply_filters( 'http_headers_useragent', 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ) );
6411
6412
		// Let's check the remote site
6413
		$http_api_args = array(
6414
			'timeout' => 10,
6415
			'redirection' => 0,
6416
			'limit_response_size' => 153600, // 150 KB
6417
			'user-agent' => "$user_agent; verifying pingback from $remote_ip",
6418
			'headers' => array(
6419
				'X-Pingback-Forwarded-For' => $remote_ip,
6420
			),
6421
		);
6422
6423
		$request = wp_safe_remote_get( $pagelinkedfrom, $http_api_args );
6424
		$remote_source = $remote_source_original = wp_remote_retrieve_body( $request );
6425
6426
		if ( ! $remote_source ) {
6427
			return $this->pingback_error( 16, __( 'The source URL does not exist.' ) );
6428
		}
6429
6430
		/**
6431
		 * Filters the pingback remote source.
6432
		 *
6433
		 * @since 2.5.0
6434
		 *
6435
		 * @param string $remote_source Response source for the page linked from.
6436
		 * @param string $pagelinkedto  URL of the page linked to.
6437
		 */
6438
		$remote_source = apply_filters( 'pre_remote_source', $remote_source, $pagelinkedto );
6439
6440
		// Work around bug in strip_tags():
6441
		$remote_source = str_replace( '<!DOC', '<DOC', $remote_source );
6442
		$remote_source = preg_replace( '/[\r\n\t ]+/', ' ', $remote_source ); // normalize spaces
6443
		$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 );
6444
6445
		preg_match( '|<title>([^<]*?)</title>|is', $remote_source, $matchtitle );
6446
		$title = isset( $matchtitle[1] ) ? $matchtitle[1] : '';
6447
		if ( empty( $title ) ) {
6448
			return $this->pingback_error( 32, __( 'We cannot find a title on that page.' ) );
6449
		}
6450
6451
		$remote_source = strip_tags( $remote_source, '<a>' ); // just keep the tag we need
6452
6453
		$p = explode( "\n\n", $remote_source );
6454
6455
		$preg_target = preg_quote($pagelinkedto, '|');
6456
6457
		foreach ( $p as $para ) {
6458
			if ( strpos($para, $pagelinkedto) !== false ) { // it exists, but is it a link?
6459
				preg_match("|<a[^>]+?".$preg_target."[^>]*>([^>]+?)</a>|", $para, $context);
6460
6461
				// If the URL isn't in a link context, keep looking
6462
				if ( empty($context) )
6463
					continue;
6464
6465
				// We're going to use this fake tag to mark the context in a bit
6466
				// the marker is needed in case the link text appears more than once in the paragraph
6467
				$excerpt = preg_replace('|\</?wpcontext\>|', '', $para);
6468
6469
				// prevent really long link text
6470
				if ( strlen($context[1]) > 100 )
6471
					$context[1] = substr($context[1], 0, 100) . '&#8230;';
6472
6473
				$marker = '<wpcontext>'.$context[1].'</wpcontext>';    // set up our marker
6474
				$excerpt= str_replace($context[0], $marker, $excerpt); // swap out the link for our marker
6475
				$excerpt = strip_tags($excerpt, '<wpcontext>');        // strip all tags but our context marker
6476
				$excerpt = trim($excerpt);
6477
				$preg_marker = preg_quote($marker, '|');
6478
				$excerpt = preg_replace("|.*?\s(.{0,100}$preg_marker.{0,100})\s.*|s", '$1', $excerpt);
6479
				$excerpt = strip_tags($excerpt); // YES, again, to remove the marker wrapper
6480
				break;
6481
			}
6482
		}
6483
6484
		if ( empty($context) ) // Link to target not found
6485
			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.' ) );
6486
6487
		$pagelinkedfrom = str_replace('&', '&amp;', $pagelinkedfrom);
6488
6489
		$context = '[&#8230;] ' . esc_html( $excerpt ) . ' [&#8230;]';
0 ignored issues
show
The variable $excerpt does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
6490
		$pagelinkedfrom = $this->escape( $pagelinkedfrom );
6491
6492
		$comment_post_ID = (int) $post_ID;
6493
		$comment_author = $title;
6494
		$comment_author_email = '';
6495
		$this->escape($comment_author);
6496
		$comment_author_url = $pagelinkedfrom;
6497
		$comment_content = $context;
6498
		$this->escape($comment_content);
6499
		$comment_type = 'pingback';
6500
6501
		$commentdata = compact(
6502
			'comment_post_ID', 'comment_author', 'comment_author_url', 'comment_author_email',
6503
			'comment_content', 'comment_type', 'remote_source', 'remote_source_original'
6504
		);
6505
6506
		$comment_ID = wp_new_comment($commentdata);
6507
6508
		/**
6509
		 * Fires after a post pingback has been sent.
6510
		 *
6511
		 * @since 0.71
6512
		 *
6513
		 * @param int $comment_ID Comment ID.
6514
		 */
6515
		do_action( 'pingback_post', $comment_ID );
6516
6517
		/* translators: 1: URL of the page linked from, 2: URL of the page linked to */
6518
		return sprintf( __( 'Pingback from %1$s to %2$s registered. Keep the web talking! :-)' ), $pagelinkedfrom, $pagelinkedto );
6519
	}
6520
6521
	/**
6522
	 * Retrieve array of URLs that pingbacked the given URL.
6523
	 *
6524
	 * Specs on http://www.aquarionics.com/misc/archives/blogite/0198.html
6525
	 *
6526
	 * @since 1.5.0
6527
	 *
6528
	 * @global wpdb $wpdb WordPress database abstraction object.
6529
	 *
6530
	 * @param string $url
6531
	 * @return array|IXR_Error
6532
	 */
6533
	public function pingback_extensions_getPingbacks( $url ) {
6534
		global $wpdb;
6535
6536
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6537
		do_action( 'xmlrpc_call', 'pingback.extensions.getPingbacks' );
6538
6539
		$url = $this->escape( $url );
6540
6541
		$post_ID = url_to_postid($url);
6542
		if ( !$post_ID ) {
6543
			// We aren't sure that the resource is available and/or pingback enabled
6544
	  		return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.' ) );
6545
		}
6546
6547
		$actual_post = get_post($post_ID, ARRAY_A);
6548
6549
		if ( !$actual_post ) {
6550
			// No such post = resource not found
6551
	  		return $this->pingback_error( 32, __('The specified target URL does not exist.' ) );
6552
		}
6553
6554
		$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) );
6555
6556
		if ( !$comments )
6557
			return array();
6558
6559
		$pingbacks = array();
6560
		foreach ( $comments as $comment ) {
6561
			if ( 'pingback' == $comment->comment_type )
6562
				$pingbacks[] = $comment->comment_author_url;
6563
		}
6564
6565
		return $pingbacks;
6566
	}
6567
6568
	/**
6569
	 * Sends a pingback error based on the given error code and message.
6570
	 *
6571
	 * @since 3.6.0
6572
	 *
6573
	 * @param int    $code    Error code.
6574
	 * @param string $message Error message.
6575
	 * @return IXR_Error Error object.
6576
	 */
6577
	protected function pingback_error( $code, $message ) {
6578
		/**
6579
		 * Filters the XML-RPC pingback error return.
6580
		 *
6581
		 * @since 3.5.1
6582
		 *
6583
		 * @param IXR_Error $error An IXR_Error object containing the error code and message.
6584
		 */
6585
		return apply_filters( 'xmlrpc_pingback_error', new IXR_Error( $code, $message ) );
6586
	}
6587
}
6588