Issues (2010)

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.

wp-includes/class-wp-xmlrpc-server.php (44 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
	 * @since 4.7.0
58
	 * @access protected
59
	 * @var wpdb
60
	 */
61
	protected $db;
62
63
	/**
64
	 * Registers all of the XMLRPC methods that XMLRPC server understands.
65
	 *
66
	 * Sets up server and method property. Passes XMLRPC
67
	 * methods through the {@see 'xmlrpc_methods'} filter to allow plugins to extend
68
	 * or replace XML-RPC methods.
69
	 *
70
	 * @since 1.5.0
71
	 */
72
	public function __construct() {
73
		$this->db = $GLOBALS['wpdb'];
74
75
		$this->methods = array(
76
			// WordPress API
77
			'wp.getUsersBlogs'		=> 'this:wp_getUsersBlogs',
78
			'wp.newPost'			=> 'this:wp_newPost',
79
			'wp.editPost'			=> 'this:wp_editPost',
80
			'wp.deletePost'			=> 'this:wp_deletePost',
81
			'wp.getPost'			=> 'this:wp_getPost',
82
			'wp.getPosts'			=> 'this:wp_getPosts',
83
			'wp.newTerm'			=> 'this:wp_newTerm',
84
			'wp.editTerm'			=> 'this:wp_editTerm',
85
			'wp.deleteTerm'			=> 'this:wp_deleteTerm',
86
			'wp.getTerm'			=> 'this:wp_getTerm',
87
			'wp.getTerms'			=> 'this:wp_getTerms',
88
			'wp.getTaxonomy'		=> 'this:wp_getTaxonomy',
89
			'wp.getTaxonomies'		=> 'this:wp_getTaxonomies',
90
			'wp.getUser'			=> 'this:wp_getUser',
91
			'wp.getUsers'			=> 'this:wp_getUsers',
92
			'wp.getProfile'			=> 'this:wp_getProfile',
93
			'wp.editProfile'		=> 'this:wp_editProfile',
94
			'wp.getPage'			=> 'this:wp_getPage',
95
			'wp.getPages'			=> 'this:wp_getPages',
96
			'wp.newPage'			=> 'this:wp_newPage',
97
			'wp.deletePage'			=> 'this:wp_deletePage',
98
			'wp.editPage'			=> 'this:wp_editPage',
99
			'wp.getPageList'		=> 'this:wp_getPageList',
100
			'wp.getAuthors'			=> 'this:wp_getAuthors',
101
			'wp.getCategories'		=> 'this:mw_getCategories',		// Alias
102
			'wp.getTags'			=> 'this:wp_getTags',
103
			'wp.newCategory'		=> 'this:wp_newCategory',
104
			'wp.deleteCategory'		=> 'this:wp_deleteCategory',
105
			'wp.suggestCategories'	=> 'this:wp_suggestCategories',
106
			'wp.uploadFile'			=> 'this:mw_newMediaObject',	// Alias
107
			'wp.deleteFile'			=> 'this:wp_deletePost',		// Alias
108
			'wp.getCommentCount'	=> 'this:wp_getCommentCount',
109
			'wp.getPostStatusList'	=> 'this:wp_getPostStatusList',
110
			'wp.getPageStatusList'	=> 'this:wp_getPageStatusList',
111
			'wp.getPageTemplates'	=> 'this:wp_getPageTemplates',
112
			'wp.getOptions'			=> 'this:wp_getOptions',
113
			'wp.setOptions'			=> 'this:wp_setOptions',
114
			'wp.getComment'			=> 'this:wp_getComment',
115
			'wp.getComments'		=> 'this:wp_getComments',
116
			'wp.deleteComment'		=> 'this:wp_deleteComment',
117
			'wp.editComment'		=> 'this:wp_editComment',
118
			'wp.newComment'			=> 'this:wp_newComment',
119
			'wp.getCommentStatusList' => 'this:wp_getCommentStatusList',
120
			'wp.getMediaItem'		=> 'this:wp_getMediaItem',
121
			'wp.getMediaLibrary'	=> 'this:wp_getMediaLibrary',
122
			'wp.getPostFormats'     => 'this:wp_getPostFormats',
123
			'wp.getPostType'		=> 'this:wp_getPostType',
124
			'wp.getPostTypes'		=> 'this:wp_getPostTypes',
125
			'wp.getRevisions'		=> 'this:wp_getRevisions',
126
			'wp.restoreRevision'	=> 'this:wp_restoreRevision',
127
128
			// Blogger API
129
			'blogger.getUsersBlogs' => 'this:blogger_getUsersBlogs',
130
			'blogger.getUserInfo' => 'this:blogger_getUserInfo',
131
			'blogger.getPost' => 'this:blogger_getPost',
132
			'blogger.getRecentPosts' => 'this:blogger_getRecentPosts',
133
			'blogger.newPost' => 'this:blogger_newPost',
134
			'blogger.editPost' => 'this:blogger_editPost',
135
			'blogger.deletePost' => 'this:blogger_deletePost',
136
137
			// MetaWeblog API (with MT extensions to structs)
138
			'metaWeblog.newPost' => 'this:mw_newPost',
139
			'metaWeblog.editPost' => 'this:mw_editPost',
140
			'metaWeblog.getPost' => 'this:mw_getPost',
141
			'metaWeblog.getRecentPosts' => 'this:mw_getRecentPosts',
142
			'metaWeblog.getCategories' => 'this:mw_getCategories',
143
			'metaWeblog.newMediaObject' => 'this:mw_newMediaObject',
144
145
			// MetaWeblog API aliases for Blogger API
146
			// see http://www.xmlrpc.com/stories/storyReader$2460
147
			'metaWeblog.deletePost' => 'this:blogger_deletePost',
148
			'metaWeblog.getUsersBlogs' => 'this:blogger_getUsersBlogs',
149
150
			// MovableType API
151
			'mt.getCategoryList' => 'this:mt_getCategoryList',
152
			'mt.getRecentPostTitles' => 'this:mt_getRecentPostTitles',
153
			'mt.getPostCategories' => 'this:mt_getPostCategories',
154
			'mt.setPostCategories' => 'this:mt_setPostCategories',
155
			'mt.supportedMethods' => 'this:mt_supportedMethods',
156
			'mt.supportedTextFilters' => 'this:mt_supportedTextFilters',
157
			'mt.getTrackbackPings' => 'this:mt_getTrackbackPings',
158
			'mt.publishPost' => 'this:mt_publishPost',
159
160
			// PingBack
161
			'pingback.ping' => 'this:pingback_ping',
162
			'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks',
163
164
			'demo.sayHello' => 'this:sayHello',
165
			'demo.addTwoNumbers' => 'this:addTwoNumbers'
166
		);
167
168
		$this->initialise_blog_option_info();
169
170
		/**
171
		 * Filters the methods exposed by the XML-RPC server.
172
		 *
173
		 * This filter can be used to add new methods, and remove built-in methods.
174
		 *
175
		 * @since 1.5.0
176
		 *
177
		 * @param array $methods An array of XML-RPC methods.
178
		 */
179
		$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...
180
	}
181
182
	/**
183
	 * Make private/protected methods readable for backward compatibility.
184
	 *
185
	 * @since 4.0.0
186
	 * @access public
187
	 *
188
	 * @param callable $name      Method to call.
189
	 * @param array    $arguments Arguments to pass when calling.
190
	 * @return array|IXR_Error|false Return value of the callback, false otherwise.
191
	 */
192
	public function __call( $name, $arguments ) {
193
		if ( '_multisite_getUsersBlogs' === $name ) {
194
			return call_user_func_array( array( $this, $name ), $arguments );
195
		}
196
		return false;
197
	}
198
199
	/**
200
	 * Serves the XML-RPC request.
201
	 *
202
	 * @since 2.9.0
203
	 * @access public
204
	 */
205
	public function serve_request() {
206
		$this->IXR_Server($this->methods);
207
	}
208
209
	/**
210
	 * Test XMLRPC API by saying, "Hello!" to client.
211
	 *
212
	 * @since 1.5.0
213
	 *
214
	 * @return string Hello string response.
215
	 */
216
	public function sayHello() {
217
		return 'Hello!';
218
	}
219
220
	/**
221
	 * Test XMLRPC API by adding two numbers for client.
222
	 *
223
	 * @since 1.5.0
224
	 *
225
	 * @param array  $args {
226
	 *     Method arguments. Note: arguments must be ordered as documented.
227
	 *
228
	 *     @type int $number1 A number to add.
229
	 *     @type int $number2 A second number to add.
230
	 * }
231
	 * @return int Sum of the two given numbers.
232
	 */
233
	public function addTwoNumbers( $args ) {
234
		$number1 = $args[0];
235
		$number2 = $args[1];
236
		return $number1 + $number2;
237
	}
238
239
	/**
240
	 * Log user in.
241
	 *
242
	 * @since 2.8.0
243
	 *
244
	 * @param string $username User's username.
245
	 * @param string $password User's password.
246
	 * @return WP_User|bool WP_User object if authentication passed, false otherwise
247
	 */
248
	public function login( $username, $password ) {
249
		/*
250
		 * Respect old get_option() filters left for back-compat when the 'enable_xmlrpc'
251
		 * option was deprecated in 3.5.0. Use the 'xmlrpc_enabled' hook instead.
252
		 */
253
		$enabled = apply_filters( 'pre_option_enable_xmlrpc', false );
254
		if ( false === $enabled ) {
255
			$enabled = apply_filters( 'option_enable_xmlrpc', true );
256
		}
257
258
		/**
259
		 * Filters whether XML-RPC methods requiring authentication are enabled.
260
		 *
261
		 * Contrary to the way it's named, this filter does not control whether XML-RPC is *fully*
262
		 * enabled, rather, it only controls whether XML-RPC methods requiring authentication - such
263
		 * as for publishing purposes - are enabled.
264
		 *
265
		 * Further, the filter does not control whether pingbacks or other custom endpoints that don't
266
		 * require authentication are enabled. This behavior is expected, and due to how parity was matched
267
		 * with the `enable_xmlrpc` UI option the filter replaced when it was introduced in 3.5.
268
		 *
269
		 * To disable XML-RPC methods that require authentication, use:
270
		 *
271
		 *     add_filter( 'xmlrpc_enabled', '__return_false' );
272
		 *
273
		 * For more granular control over all XML-RPC methods and requests, see the {@see 'xmlrpc_methods'}
274
		 * and {@see 'xmlrpc_element_limit'} hooks.
275
		 *
276
		 * @since 3.5.0
277
		 *
278
		 * @param bool $enabled Whether XML-RPC is enabled. Default true.
279
		 */
280
		$enabled = apply_filters( 'xmlrpc_enabled', $enabled );
281
282 View Code Duplication
		if ( ! $enabled ) {
283
			$this->error = new IXR_Error( 405, sprintf( __( 'XML-RPC services are disabled on this site.' ) ) );
284
			return false;
285
		}
286
287
		if ( $this->auth_failed ) {
288
			$user = new WP_Error( 'login_prevented' );
289
		} else {
290
			$user = wp_authenticate( $username, $password );
291
		}
292
293
		if ( is_wp_error( $user ) ) {
294
			$this->error = new IXR_Error( 403, __( 'Incorrect username or password.' ) );
295
296
			// Flag that authentication has failed once on this wp_xmlrpc_server instance
297
			$this->auth_failed = true;
298
299
			/**
300
			 * Filters the XML-RPC user login error message.
301
			 *
302
			 * @since 3.5.0
303
			 *
304
			 * @param string  $error The XML-RPC error message.
305
			 * @param WP_User $user  WP_User object.
306
			 */
307
			$this->error = apply_filters( 'xmlrpc_login_error', $this->error, $user );
308
			return false;
309
		}
310
311
		wp_set_current_user( $user->ID );
312
		return $user;
313
	}
314
315
	/**
316
	 * Check user's credentials. Deprecated.
317
	 *
318
	 * @since 1.5.0
319
	 * @deprecated 2.8.0 Use wp_xmlrpc_server::login()
320
	 * @see wp_xmlrpc_server::login()
321
	 *
322
	 * @param string $username User's username.
323
	 * @param string $password User's password.
324
	 * @return bool Whether authentication passed.
325
	 */
326
	public function login_pass_ok( $username, $password ) {
327
		return (bool) $this->login( $username, $password );
328
	}
329
330
	/**
331
	 * Escape string or array of strings for database.
332
	 *
333
	 * @since 1.5.2
334
	 *
335
	 * @param string|array $data Escape single string or array of strings.
336
	 * @return string|void Returns with string is passed, alters by-reference
337
	 *                     when array is passed.
338
	 */
339
	public function escape( &$data ) {
340
		if ( ! is_array( $data ) )
341
			return wp_slash( $data );
342
343
		foreach ( $data as &$v ) {
344
			if ( is_array( $v ) )
345
				$this->escape( $v );
346
			elseif ( ! is_object( $v ) )
347
				$v = wp_slash( $v );
348
		}
349
	}
350
351
	/**
352
	 * Retrieve custom fields for post.
353
	 *
354
	 * @since 2.5.0
355
	 *
356
	 * @param int $post_id Post ID.
357
	 * @return array Custom fields, if exist.
358
	 */
359
	public function get_custom_fields($post_id) {
360
		$post_id = (int) $post_id;
361
362
		$custom_fields = array();
363
364
		foreach ( (array) has_meta($post_id) as $meta ) {
365
			// Don't expose protected fields.
366
			if ( ! current_user_can( 'edit_post_meta', $post_id , $meta['meta_key'] ) )
367
				continue;
368
369
			$custom_fields[] = array(
370
				"id"    => $meta['meta_id'],
371
				"key"   => $meta['meta_key'],
372
				"value" => $meta['meta_value']
373
			);
374
		}
375
376
		return $custom_fields;
377
	}
378
379
	/**
380
	 * Set custom fields for post.
381
	 *
382
	 * @since 2.5.0
383
	 *
384
	 * @param int $post_id Post ID.
385
	 * @param array $fields Custom fields.
386
	 */
387
	public function set_custom_fields($post_id, $fields) {
388
		$post_id = (int) $post_id;
389
390
		foreach ( (array) $fields as $meta ) {
391
			if ( isset($meta['id']) ) {
392
				$meta['id'] = (int) $meta['id'];
393
				$pmeta = get_metadata_by_mid( 'post', $meta['id'] );
394
				if ( isset($meta['key']) ) {
395
					$meta['key'] = wp_unslash( $meta['key'] );
396
					if ( $meta['key'] !== $pmeta->meta_key )
397
						continue;
398
					$meta['value'] = wp_unslash( $meta['value'] );
399
					if ( current_user_can( 'edit_post_meta', $post_id, $meta['key'] ) )
400
						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...
401
				} elseif ( current_user_can( 'delete_post_meta', $post_id, $pmeta->meta_key ) ) {
402
					delete_metadata_by_mid( 'post', $meta['id'] );
403
				}
404
			} elseif ( current_user_can( 'add_post_meta', $post_id, wp_unslash( $meta['key'] ) ) ) {
405
				add_post_meta( $post_id, $meta['key'], $meta['value'] );
406
			}
407
		}
408
	}
409
410
	/**
411
	 * Set up blog options property.
412
	 *
413
	 * Passes property through {@see 'xmlrpc_blog_options'} filter.
414
	 *
415
	 * @since 2.6.0
416
	 *
417
	 * @global string $wp_version
418
	 */
419
	public function initialise_blog_option_info() {
420
		global $wp_version;
421
422
		$this->blog_options = array(
423
			// Read only options
424
			'software_name'     => array(
425
				'desc'          => __( 'Software Name' ),
426
				'readonly'      => true,
427
				'value'         => 'WordPress'
428
			),
429
			'software_version'  => array(
430
				'desc'          => __( 'Software Version' ),
431
				'readonly'      => true,
432
				'value'         => $wp_version
433
			),
434
			'blog_url'          => array(
435
				'desc'          => __( 'WordPress Address (URL)' ),
436
				'readonly'      => true,
437
				'option'        => 'siteurl'
438
			),
439
			'home_url'          => array(
440
				'desc'          => __( 'Site Address (URL)' ),
441
				'readonly'      => true,
442
				'option'        => 'home'
443
			),
444
			'login_url'          => array(
445
				'desc'          => __( 'Login Address (URL)' ),
446
				'readonly'      => true,
447
				'value'         => wp_login_url( )
448
			),
449
			'admin_url'          => array(
450
				'desc'          => __( 'The URL to the admin area' ),
451
				'readonly'      => true,
452
				'value'         => get_admin_url( )
453
			),
454
			'image_default_link_type' => array(
455
				'desc'          => __( 'Image default link type' ),
456
				'readonly'      => true,
457
				'option'        => 'image_default_link_type'
458
			),
459
			'image_default_size' => array(
460
				'desc'          => __( 'Image default size' ),
461
				'readonly'      => true,
462
				'option'        => 'image_default_size'
463
			),
464
			'image_default_align' => array(
465
				'desc'          => __( 'Image default align' ),
466
				'readonly'      => true,
467
				'option'        => 'image_default_align'
468
			),
469
			'template'          => array(
470
				'desc'          => __( 'Template' ),
471
				'readonly'      => true,
472
				'option'        => 'template'
473
			),
474
			'stylesheet'        => array(
475
				'desc'          => __( 'Stylesheet' ),
476
				'readonly'      => true,
477
				'option'        => 'stylesheet'
478
			),
479
			'post_thumbnail'    => array(
480
				'desc'          => __('Post Thumbnail'),
481
				'readonly'      => true,
482
				'value'         => current_theme_supports( 'post-thumbnails' )
483
			),
484
485
			// Updatable options
486
			'time_zone'         => array(
487
				'desc'          => __( 'Time Zone' ),
488
				'readonly'      => false,
489
				'option'        => 'gmt_offset'
490
			),
491
			'blog_title'        => array(
492
				'desc'          => __( 'Site Title' ),
493
				'readonly'      => false,
494
				'option'        => 'blogname'
495
			),
496
			'blog_tagline'      => array(
497
				'desc'          => __( 'Site Tagline' ),
498
				'readonly'      => false,
499
				'option'        => 'blogdescription'
500
			),
501
			'date_format'       => array(
502
				'desc'          => __( 'Date Format' ),
503
				'readonly'      => false,
504
				'option'        => 'date_format'
505
			),
506
			'time_format'       => array(
507
				'desc'          => __( 'Time Format' ),
508
				'readonly'      => false,
509
				'option'        => 'time_format'
510
			),
511
			'users_can_register' => array(
512
				'desc'          => __( 'Allow new users to sign up' ),
513
				'readonly'      => false,
514
				'option'        => 'users_can_register'
515
			),
516
			'thumbnail_size_w'  => array(
517
				'desc'          => __( 'Thumbnail Width' ),
518
				'readonly'      => false,
519
				'option'        => 'thumbnail_size_w'
520
			),
521
			'thumbnail_size_h'  => array(
522
				'desc'          => __( 'Thumbnail Height' ),
523
				'readonly'      => false,
524
				'option'        => 'thumbnail_size_h'
525
			),
526
			'thumbnail_crop'    => array(
527
				'desc'          => __( 'Crop thumbnail to exact dimensions' ),
528
				'readonly'      => false,
529
				'option'        => 'thumbnail_crop'
530
			),
531
			'medium_size_w'     => array(
532
				'desc'          => __( 'Medium size image width' ),
533
				'readonly'      => false,
534
				'option'        => 'medium_size_w'
535
			),
536
			'medium_size_h'     => array(
537
				'desc'          => __( 'Medium size image height' ),
538
				'readonly'      => false,
539
				'option'        => 'medium_size_h'
540
			),
541
			'medium_large_size_w'   => array(
542
				'desc'          => __( 'Medium-Large size image width' ),
543
				'readonly'      => false,
544
				'option'        => 'medium_large_size_w'
545
			),
546
			'medium_large_size_h'   => array(
547
				'desc'          => __( 'Medium-Large size image height' ),
548
				'readonly'      => false,
549
				'option'        => 'medium_large_size_h'
550
			),
551
			'large_size_w'      => array(
552
				'desc'          => __( 'Large size image width' ),
553
				'readonly'      => false,
554
				'option'        => 'large_size_w'
555
			),
556
			'large_size_h'      => array(
557
				'desc'          => __( 'Large size image height' ),
558
				'readonly'      => false,
559
				'option'        => 'large_size_h'
560
			),
561
			'default_comment_status' => array(
562
				'desc'          => __( 'Allow people to post comments on new articles' ),
563
				'readonly'      => false,
564
				'option'        => 'default_comment_status'
565
			),
566
			'default_ping_status' => array(
567
				'desc'          => __( 'Allow link notifications from other blogs (pingbacks and trackbacks) on new articles' ),
568
				'readonly'      => false,
569
				'option'        => 'default_ping_status'
570
			)
571
		);
572
573
		/**
574
		 * Filters the XML-RPC blog options property.
575
		 *
576
		 * @since 2.6.0
577
		 *
578
		 * @param array $blog_options An array of XML-RPC blog options.
579
		 */
580
		$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...
581
	}
582
583
	/**
584
	 * Retrieve the blogs of the user.
585
	 *
586
	 * @since 2.6.0
587
	 *
588
	 * @param array $args {
589
	 *     Method arguments. Note: arguments must be ordered as documented.
590
	 *
591
	 *     @type string $username Username.
592
	 *     @type string $password Password.
593
	 * }
594
	 * @return array|IXR_Error Array contains:
595
	 *  - 'isAdmin'
596
	 *  - 'isPrimary' - whether the blog is the user's primary blog
597
	 *  - 'url'
598
	 *  - 'blogid'
599
	 *  - 'blogName'
600
	 *  - 'xmlrpc' - url of xmlrpc endpoint
601
	 */
602
	public function wp_getUsersBlogs( $args ) {
603
		// If this isn't on WPMU then just use blogger_getUsersBlogs
604
		if ( !is_multisite() ) {
605
			array_unshift( $args, 1 );
606
			return $this->blogger_getUsersBlogs( $args );
607
		}
608
609
		$this->escape( $args );
610
611
		$username = $args[0];
612
		$password = $args[1];
613
614
		if ( !$user = $this->login($username, $password) )
615
			return $this->error;
616
617
		/**
618
		 * Fires after the XML-RPC user has been authenticated but before the rest of
619
		 * the method logic begins.
620
		 *
621
		 * All built-in XML-RPC methods use the action xmlrpc_call, with a parameter
622
		 * equal to the method's name, e.g., wp.getUsersBlogs, wp.newPost, etc.
623
		 *
624
		 * @since 2.5.0
625
		 *
626
		 * @param string $name The method name.
627
		 */
628
		do_action( 'xmlrpc_call', 'wp.getUsersBlogs' );
629
630
		$blogs = (array) get_blogs_of_user( $user->ID );
631
		$struct = array();
632
		$primary_blog_id = 0;
633
		$active_blog = get_active_blog_for_user( $user->ID );
634
		if ( $active_blog ) {
635
			$primary_blog_id = (int) $active_blog->blog_id;
636
		}
637
638
		foreach ( $blogs as $blog ) {
639
			// Don't include blogs that aren't hosted at this site.
640
			if ( $blog->site_id != get_current_site()->id )
641
				continue;
642
643
			$blog_id = $blog->userblog_id;
644
645
			switch_to_blog( $blog_id );
646
647
			$is_admin = current_user_can( 'manage_options' );
648
			$is_primary = ( (int) $blog_id === $primary_blog_id );
649
650
			$struct[] = array(
651
				'isAdmin'   => $is_admin,
652
				'isPrimary' => $is_primary,
653
				'url'       => home_url( '/' ),
654
				'blogid'    => (string) $blog_id,
655
				'blogName'  => get_option( 'blogname' ),
656
				'xmlrpc'    => site_url( 'xmlrpc.php', 'rpc' ),
657
			);
658
659
			restore_current_blog();
660
		}
661
662
		return $struct;
663
	}
664
665
	/**
666
	 * Checks if the method received at least the minimum number of arguments.
667
	 *
668
	 * @since 3.4.0
669
	 * @access protected
670
	 *
671
	 * @param string|array $args Sanitize single string or array of strings.
672
	 * @param int $count         Minimum number of arguments.
673
	 * @return bool if `$args` contains at least $count arguments.
674
	 */
675
	protected function minimum_args( $args, $count ) {
676 View Code Duplication
		if ( count( $args ) < $count ) {
677
			$this->error = new IXR_Error( 400, __( 'Insufficient arguments passed to this XML-RPC method.' ) );
678
			return false;
679
		}
680
681
		return true;
682
	}
683
684
	/**
685
	 * Prepares taxonomy data for return in an XML-RPC object.
686
	 *
687
	 * @access protected
688
	 *
689
	 * @param object $taxonomy The unprepared taxonomy data.
690
	 * @param array $fields    The subset of taxonomy fields to return.
691
	 * @return array The prepared taxonomy data.
692
	 */
693
	protected function _prepare_taxonomy( $taxonomy, $fields ) {
694
		$_taxonomy = array(
695
			'name' => $taxonomy->name,
696
			'label' => $taxonomy->label,
697
			'hierarchical' => (bool) $taxonomy->hierarchical,
698
			'public' => (bool) $taxonomy->public,
699
			'show_ui' => (bool) $taxonomy->show_ui,
700
			'_builtin' => (bool) $taxonomy->_builtin,
701
		);
702
703
		if ( in_array( 'labels', $fields ) )
704
			$_taxonomy['labels'] = (array) $taxonomy->labels;
705
706
		if ( in_array( 'cap', $fields ) )
707
			$_taxonomy['cap'] = (array) $taxonomy->cap;
708
709
		if ( in_array( 'menu', $fields ) )
710
			$_taxonomy['show_in_menu'] = (bool) $_taxonomy->show_in_menu;
711
712
		if ( in_array( 'object_type', $fields ) )
713
			$_taxonomy['object_type'] = array_unique( (array) $taxonomy->object_type );
714
715
		/**
716
		 * Filters XML-RPC-prepared data for the given taxonomy.
717
		 *
718
		 * @since 3.4.0
719
		 *
720
		 * @param array  $_taxonomy An array of taxonomy data.
721
		 * @param object $taxonomy  Taxonomy object.
722
		 * @param array  $fields    The subset of taxonomy fields to return.
723
		 */
724
		return apply_filters( 'xmlrpc_prepare_taxonomy', $_taxonomy, $taxonomy, $fields );
725
	}
726
727
	/**
728
	 * Prepares term data for return in an XML-RPC object.
729
	 *
730
	 * @access protected
731
	 *
732
	 * @param array|object $term The unprepared term data.
733
	 * @return array The prepared term data.
734
	 */
735
	protected function _prepare_term( $term ) {
736
		$_term = $term;
737
		if ( ! is_array( $_term ) )
738
			$_term = get_object_vars( $_term );
739
740
		// For integers which may be larger than XML-RPC supports ensure we return strings.
741
		$_term['term_id'] = strval( $_term['term_id'] );
742
		$_term['term_group'] = strval( $_term['term_group'] );
743
		$_term['term_taxonomy_id'] = strval( $_term['term_taxonomy_id'] );
744
		$_term['parent'] = strval( $_term['parent'] );
745
746
		// Count we are happy to return as an integer because people really shouldn't use terms that much.
747
		$_term['count'] = intval( $_term['count'] );
748
749
		/**
750
		 * Filters XML-RPC-prepared data for the given term.
751
		 *
752
		 * @since 3.4.0
753
		 *
754
		 * @param array        $_term An array of term data.
755
		 * @param array|object $term  Term object or array.
756
		 */
757
		return apply_filters( 'xmlrpc_prepare_term', $_term, $term );
758
	}
759
760
	/**
761
	 * Convert a WordPress date string to an IXR_Date object.
762
	 *
763
	 * @access protected
764
	 *
765
	 * @param string $date Date string to convert.
766
	 * @return IXR_Date IXR_Date object.
767
	 */
768
	protected function _convert_date( $date ) {
769
		if ( $date === '0000-00-00 00:00:00' ) {
770
			return new IXR_Date( '00000000T00:00:00Z' );
771
		}
772
		return new IXR_Date( mysql2date( 'Ymd\TH:i:s', $date, false ) );
773
	}
774
775
	/**
776
	 * Convert a WordPress GMT date string to an IXR_Date object.
777
	 *
778
	 * @access protected
779
	 *
780
	 * @param string $date_gmt WordPress GMT date string.
781
	 * @param string $date     Date string.
782
	 * @return IXR_Date IXR_Date object.
783
	 */
784
	protected function _convert_date_gmt( $date_gmt, $date ) {
785
		if ( $date !== '0000-00-00 00:00:00' && $date_gmt === '0000-00-00 00:00:00' ) {
786
			return new IXR_Date( get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $date, false ), 'Ymd\TH:i:s' ) );
787
		}
788
		return $this->_convert_date( $date_gmt );
789
	}
790
791
	/**
792
	 * Prepares post data for return in an XML-RPC object.
793
	 *
794
	 * @access protected
795
	 *
796
	 * @param array $post   The unprepared post data.
797
	 * @param array $fields The subset of post type fields to return.
798
	 * @return array The prepared post data.
799
	 */
800
	protected function _prepare_post( $post, $fields ) {
801
		// Holds the data for this post. built up based on $fields.
802
		$_post = array( 'post_id' => strval( $post['ID'] ) );
803
804
		// Prepare common post fields.
805
		$post_fields = array(
806
			'post_title'        => $post['post_title'],
807
			'post_date'         => $this->_convert_date( $post['post_date'] ),
808
			'post_date_gmt'     => $this->_convert_date_gmt( $post['post_date_gmt'], $post['post_date'] ),
809
			'post_modified'     => $this->_convert_date( $post['post_modified'] ),
810
			'post_modified_gmt' => $this->_convert_date_gmt( $post['post_modified_gmt'], $post['post_modified'] ),
811
			'post_status'       => $post['post_status'],
812
			'post_type'         => $post['post_type'],
813
			'post_name'         => $post['post_name'],
814
			'post_author'       => $post['post_author'],
815
			'post_password'     => $post['post_password'],
816
			'post_excerpt'      => $post['post_excerpt'],
817
			'post_content'      => $post['post_content'],
818
			'post_parent'       => strval( $post['post_parent'] ),
819
			'post_mime_type'    => $post['post_mime_type'],
820
			'link'              => get_permalink( $post['ID'] ),
821
			'guid'              => $post['guid'],
822
			'menu_order'        => intval( $post['menu_order'] ),
823
			'comment_status'    => $post['comment_status'],
824
			'ping_status'       => $post['ping_status'],
825
			'sticky'            => ( $post['post_type'] === 'post' && is_sticky( $post['ID'] ) ),
826
		);
827
828
		// Thumbnail.
829
		$post_fields['post_thumbnail'] = array();
830
		$thumbnail_id = get_post_thumbnail_id( $post['ID'] );
831
		if ( $thumbnail_id ) {
832
			$thumbnail_size = current_theme_supports('post-thumbnail') ? 'post-thumbnail' : 'thumbnail';
833
			$post_fields['post_thumbnail'] = $this->_prepare_media_item( get_post( $thumbnail_id ), $thumbnail_size );
834
		}
835
836
		// Consider future posts as published.
837
		if ( $post_fields['post_status'] === 'future' )
838
			$post_fields['post_status'] = 'publish';
839
840
		// Fill in blank post format.
841
		$post_fields['post_format'] = get_post_format( $post['ID'] );
842
		if ( empty( $post_fields['post_format'] ) )
843
			$post_fields['post_format'] = 'standard';
844
845
		// Merge requested $post_fields fields into $_post.
846
		if ( in_array( 'post', $fields ) ) {
847
			$_post = array_merge( $_post, $post_fields );
848
		} else {
849
			$requested_fields = array_intersect_key( $post_fields, array_flip( $fields ) );
850
			$_post = array_merge( $_post, $requested_fields );
851
		}
852
853
		$all_taxonomy_fields = in_array( 'taxonomies', $fields );
854
855
		if ( $all_taxonomy_fields || in_array( 'terms', $fields ) ) {
856
			$post_type_taxonomies = get_object_taxonomies( $post['post_type'], 'names' );
857
			$terms = wp_get_object_terms( $post['ID'], $post_type_taxonomies );
858
			$_post['terms'] = array();
859
			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...
860
				$_post['terms'][] = $this->_prepare_term( $term );
861
			}
862
		}
863
864
		if ( in_array( 'custom_fields', $fields ) )
865
			$_post['custom_fields'] = $this->get_custom_fields( $post['ID'] );
866
867
		if ( in_array( 'enclosure', $fields ) ) {
868
			$_post['enclosure'] = array();
869
			$enclosures = (array) get_post_meta( $post['ID'], 'enclosure' );
870
			if ( ! empty( $enclosures ) ) {
871
				$encdata = explode( "\n", $enclosures[0] );
872
				$_post['enclosure']['url'] = trim( htmlspecialchars( $encdata[0] ) );
873
				$_post['enclosure']['length'] = (int) trim( $encdata[1] );
874
				$_post['enclosure']['type'] = trim( $encdata[2] );
875
			}
876
		}
877
878
		/**
879
		 * Filters XML-RPC-prepared date for the given post.
880
		 *
881
		 * @since 3.4.0
882
		 *
883
		 * @param array $_post  An array of modified post data.
884
		 * @param array $post   An array of post data.
885
		 * @param array $fields An array of post fields.
886
		 */
887
		return apply_filters( 'xmlrpc_prepare_post', $_post, $post, $fields );
888
	}
889
890
	/**
891
	 * Prepares post data for return in an XML-RPC object.
892
	 *
893
	 * @since 3.4.0
894
	 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
895
	 * @access protected
896
	 *
897
	 * @param WP_Post_Type $post_type Post type object.
898
	 * @param array        $fields    The subset of post fields to return.
899
	 * @return array The prepared post type data.
900
	 */
901
	protected function _prepare_post_type( $post_type, $fields ) {
902
		$_post_type = array(
903
			'name' => $post_type->name,
904
			'label' => $post_type->label,
905
			'hierarchical' => (bool) $post_type->hierarchical,
906
			'public' => (bool) $post_type->public,
907
			'show_ui' => (bool) $post_type->show_ui,
908
			'_builtin' => (bool) $post_type->_builtin,
909
			'has_archive' => (bool) $post_type->has_archive,
910
			'supports' => get_all_post_type_supports( $post_type->name ),
911
		);
912
913
		if ( in_array( 'labels', $fields ) ) {
914
			$_post_type['labels'] = (array) $post_type->labels;
915
		}
916
917
		if ( in_array( 'cap', $fields ) ) {
918
			$_post_type['cap'] = (array) $post_type->cap;
919
			$_post_type['map_meta_cap'] = (bool) $post_type->map_meta_cap;
920
		}
921
922
		if ( in_array( 'menu', $fields ) ) {
923
			$_post_type['menu_position'] = (int) $post_type->menu_position;
924
			$_post_type['menu_icon'] = $post_type->menu_icon;
925
			$_post_type['show_in_menu'] = (bool) $post_type->show_in_menu;
926
		}
927
928
		if ( in_array( 'taxonomies', $fields ) )
929
			$_post_type['taxonomies'] = get_object_taxonomies( $post_type->name, 'names' );
930
931
		/**
932
		 * Filters XML-RPC-prepared date for the given post type.
933
		 *
934
		 * @since 3.4.0
935
		 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
936
		 *
937
		 * @param array        $_post_type An array of post type data.
938
		 * @param WP_Post_Type $post_type  Post type object.
939
		 */
940
		return apply_filters( 'xmlrpc_prepare_post_type', $_post_type, $post_type );
941
	}
942
943
	/**
944
	 * Prepares media item data for return in an XML-RPC object.
945
	 *
946
	 * @access protected
947
	 *
948
	 * @param object $media_item     The unprepared media item data.
949
	 * @param string $thumbnail_size The image size to use for the thumbnail URL.
950
	 * @return array The prepared media item data.
951
	 */
952
	protected function _prepare_media_item( $media_item, $thumbnail_size = 'thumbnail' ) {
953
		$_media_item = array(
954
			'attachment_id'    => strval( $media_item->ID ),
955
			'date_created_gmt' => $this->_convert_date_gmt( $media_item->post_date_gmt, $media_item->post_date ),
956
			'parent'           => $media_item->post_parent,
957
			'link'             => wp_get_attachment_url( $media_item->ID ),
958
			'title'            => $media_item->post_title,
959
			'caption'          => $media_item->post_excerpt,
960
			'description'      => $media_item->post_content,
961
			'metadata'         => wp_get_attachment_metadata( $media_item->ID ),
962
			'type'             => $media_item->post_mime_type
963
		);
964
965
		$thumbnail_src = image_downsize( $media_item->ID, $thumbnail_size );
966
		if ( $thumbnail_src )
967
			$_media_item['thumbnail'] = $thumbnail_src[0];
968
		else
969
			$_media_item['thumbnail'] = $_media_item['link'];
970
971
		/**
972
		 * Filters XML-RPC-prepared data for the given media item.
973
		 *
974
		 * @since 3.4.0
975
		 *
976
		 * @param array  $_media_item    An array of media item data.
977
		 * @param object $media_item     Media item object.
978
		 * @param string $thumbnail_size Image size.
979
		 */
980
		return apply_filters( 'xmlrpc_prepare_media_item', $_media_item, $media_item, $thumbnail_size );
981
	}
982
983
	/**
984
	 * Prepares page data for return in an XML-RPC object.
985
	 *
986
	 * @access protected
987
	 *
988
	 * @param object $page The unprepared page data.
989
	 * @return array The prepared page data.
990
	 */
991
	protected function _prepare_page( $page ) {
992
		// Get all of the page content and link.
993
		$full_page = get_extended( $page->post_content );
994
		$link = get_permalink( $page->ID );
995
996
		// Get info the page parent if there is one.
997
		$parent_title = "";
998
		if ( ! empty( $page->post_parent ) ) {
999
			$parent = get_post( $page->post_parent );
1000
			$parent_title = $parent->post_title;
1001
		}
1002
1003
		// Determine comment and ping settings.
1004
		$allow_comments = comments_open( $page->ID ) ? 1 : 0;
1005
		$allow_pings = pings_open( $page->ID ) ? 1 : 0;
1006
1007
		// Format page date.
1008
		$page_date = $this->_convert_date( $page->post_date );
1009
		$page_date_gmt = $this->_convert_date_gmt( $page->post_date_gmt, $page->post_date );
1010
1011
		// Pull the categories info together.
1012
		$categories = array();
1013
		if ( is_object_in_taxonomy( 'page', 'category' ) ) {
1014
			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...
1015
				$categories[] = get_cat_name( $cat_id );
1016
			}
1017
		}
1018
1019
		// Get the author info.
1020
		$author = get_userdata( $page->post_author );
1021
1022
		$page_template = get_page_template_slug( $page->ID );
1023
		if ( empty( $page_template ) )
1024
			$page_template = 'default';
1025
1026
		$_page = array(
1027
			'dateCreated'            => $page_date,
1028
			'userid'                 => $page->post_author,
1029
			'page_id'                => $page->ID,
1030
			'page_status'            => $page->post_status,
1031
			'description'            => $full_page['main'],
1032
			'title'                  => $page->post_title,
1033
			'link'                   => $link,
1034
			'permaLink'              => $link,
1035
			'categories'             => $categories,
1036
			'excerpt'                => $page->post_excerpt,
1037
			'text_more'              => $full_page['extended'],
1038
			'mt_allow_comments'      => $allow_comments,
1039
			'mt_allow_pings'         => $allow_pings,
1040
			'wp_slug'                => $page->post_name,
1041
			'wp_password'            => $page->post_password,
1042
			'wp_author'              => $author->display_name,
1043
			'wp_page_parent_id'      => $page->post_parent,
1044
			'wp_page_parent_title'   => $parent_title,
1045
			'wp_page_order'          => $page->menu_order,
1046
			'wp_author_id'           => (string) $author->ID,
1047
			'wp_author_display_name' => $author->display_name,
1048
			'date_created_gmt'       => $page_date_gmt,
1049
			'custom_fields'          => $this->get_custom_fields( $page->ID ),
1050
			'wp_page_template'       => $page_template
1051
		);
1052
1053
		/**
1054
		 * Filters XML-RPC-prepared data for the given page.
1055
		 *
1056
		 * @since 3.4.0
1057
		 *
1058
		 * @param array   $_page An array of page data.
1059
		 * @param WP_Post $page  Page object.
1060
		 */
1061
		return apply_filters( 'xmlrpc_prepare_page', $_page, $page );
1062
	}
1063
1064
	/**
1065
	 * Prepares comment data for return in an XML-RPC object.
1066
	 *
1067
	 * @access protected
1068
	 *
1069
	 * @param object $comment The unprepared comment data.
1070
	 * @return array The prepared comment data.
1071
	 */
1072
	protected function _prepare_comment( $comment ) {
1073
		// Format page date.
1074
		$comment_date_gmt = $this->_convert_date_gmt( $comment->comment_date_gmt, $comment->comment_date );
1075
1076
		if ( '0' == $comment->comment_approved ) {
1077
			$comment_status = 'hold';
1078
		} elseif ( 'spam' == $comment->comment_approved ) {
1079
			$comment_status = 'spam';
1080
		} elseif ( '1' == $comment->comment_approved ) {
1081
			$comment_status = 'approve';
1082
		} else {
1083
			$comment_status = $comment->comment_approved;
1084
		}
1085
		$_comment = array(
1086
			'date_created_gmt' => $comment_date_gmt,
1087
			'user_id'          => $comment->user_id,
1088
			'comment_id'       => $comment->comment_ID,
1089
			'parent'           => $comment->comment_parent,
1090
			'status'           => $comment_status,
1091
			'content'          => $comment->comment_content,
1092
			'link'             => get_comment_link($comment),
1093
			'post_id'          => $comment->comment_post_ID,
1094
			'post_title'       => get_the_title($comment->comment_post_ID),
1095
			'author'           => $comment->comment_author,
1096
			'author_url'       => $comment->comment_author_url,
1097
			'author_email'     => $comment->comment_author_email,
1098
			'author_ip'        => $comment->comment_author_IP,
1099
			'type'             => $comment->comment_type,
1100
		);
1101
1102
		/**
1103
		 * Filters XML-RPC-prepared data for the given comment.
1104
		 *
1105
		 * @since 3.4.0
1106
		 *
1107
		 * @param array      $_comment An array of prepared comment data.
1108
		 * @param WP_Comment $comment  Comment object.
1109
		 */
1110
		return apply_filters( 'xmlrpc_prepare_comment', $_comment, $comment );
1111
	}
1112
1113
	/**
1114
	 * Prepares user data for return in an XML-RPC object.
1115
	 *
1116
	 * @access protected
1117
	 *
1118
	 * @param WP_User $user   The unprepared user object.
1119
	 * @param array   $fields The subset of user fields to return.
1120
	 * @return array The prepared user data.
1121
	 */
1122
	protected function _prepare_user( $user, $fields ) {
1123
		$_user = array( 'user_id' => strval( $user->ID ) );
1124
1125
		$user_fields = array(
1126
			'username'          => $user->user_login,
1127
			'first_name'        => $user->user_firstname,
1128
			'last_name'         => $user->user_lastname,
1129
			'registered'        => $this->_convert_date( $user->user_registered ),
1130
			'bio'               => $user->user_description,
1131
			'email'             => $user->user_email,
1132
			'nickname'          => $user->nickname,
1133
			'nicename'          => $user->user_nicename,
1134
			'url'               => $user->user_url,
1135
			'display_name'      => $user->display_name,
1136
			'roles'             => $user->roles,
1137
		);
1138
1139
		if ( in_array( 'all', $fields ) ) {
1140
			$_user = array_merge( $_user, $user_fields );
1141
		} else {
1142
			if ( in_array( 'basic', $fields ) ) {
1143
				$basic_fields = array( 'username', 'email', 'registered', 'display_name', 'nicename' );
1144
				$fields = array_merge( $fields, $basic_fields );
1145
			}
1146
			$requested_fields = array_intersect_key( $user_fields, array_flip( $fields ) );
1147
			$_user = array_merge( $_user, $requested_fields );
1148
		}
1149
1150
		/**
1151
		 * Filters XML-RPC-prepared data for the given user.
1152
		 *
1153
		 * @since 3.5.0
1154
		 *
1155
		 * @param array   $_user  An array of user data.
1156
		 * @param WP_User $user   User object.
1157
		 * @param array   $fields An array of user fields.
1158
		 */
1159
		return apply_filters( 'xmlrpc_prepare_user', $_user, $user, $fields );
1160
	}
1161
1162
	/**
1163
	 * Create a new post for any registered post type.
1164
	 *
1165
	 * @since 3.4.0
1166
	 *
1167
	 * @link https://en.wikipedia.org/wiki/RSS_enclosure for information on RSS enclosures.
1168
	 *
1169
	 * @param array  $args {
1170
	 *     Method arguments. Note: top-level arguments must be ordered as documented.
1171
	 *
1172
	 *     @type int    $blog_id        Blog ID (unused).
1173
	 *     @type string $username       Username.
1174
	 *     @type string $password       Password.
1175
	 *     @type array  $content_struct {
1176
	 *         Content struct for adding a new post. See wp_insert_post() for information on
1177
	 *         additional post fields
1178
	 *
1179
	 *         @type string $post_type      Post type. Default 'post'.
1180
	 *         @type string $post_status    Post status. Default 'draft'
1181
	 *         @type string $post_title     Post title.
1182
	 *         @type int    $post_author    Post author ID.
1183
	 *         @type string $post_excerpt   Post excerpt.
1184
	 *         @type string $post_content   Post content.
1185
	 *         @type string $post_date_gmt  Post date in GMT.
1186
	 *         @type string $post_date      Post date.
1187
	 *         @type string $post_password  Post password (20-character limit).
1188
	 *         @type string $comment_status Post comment enabled status. Accepts 'open' or 'closed'.
1189
	 *         @type string $ping_status    Post ping status. Accepts 'open' or 'closed'.
1190
	 *         @type bool   $sticky         Whether the post should be sticky. Automatically false if
1191
	 *                                      `$post_status` is 'private'.
1192
	 *         @type int    $post_thumbnail ID of an image to use as the post thumbnail/featured image.
1193
	 *         @type array  $custom_fields  Array of meta key/value pairs to add to the post.
1194
	 *         @type array  $terms          Associative array with taxonomy names as keys and arrays
1195
	 *                                      of term IDs as values.
1196
	 *         @type array  $terms_names    Associative array with taxonomy names as keys and arrays
1197
	 *                                      of term names as values.
1198
	 *         @type array  $enclosure      {
1199
	 *             Array of feed enclosure data to add to post meta.
1200
	 *
1201
	 *             @type string $url    URL for the feed enclosure.
1202
	 *             @type int    $length Size in bytes of the enclosure.
1203
	 *             @type string $type   Mime-type for the enclosure.
1204
	 *         }
1205
	 *     }
1206
	 * }
1207
	 * @return int|IXR_Error Post ID on success, IXR_Error instance otherwise.
1208
	 */
1209
	public function wp_newPost( $args ) {
1210
		if ( ! $this->minimum_args( $args, 4 ) )
1211
			return $this->error;
1212
1213
		$this->escape( $args );
1214
1215
		$username       = $args[1];
1216
		$password       = $args[2];
1217
		$content_struct = $args[3];
1218
1219
		if ( ! $user = $this->login( $username, $password ) )
1220
			return $this->error;
1221
1222
		// convert the date field back to IXR form
1223
		if ( isset( $content_struct['post_date'] ) && ! ( $content_struct['post_date'] instanceof IXR_Date ) ) {
1224
			$content_struct['post_date'] = $this->_convert_date( $content_struct['post_date'] );
1225
		}
1226
1227
		// ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct,
1228
		// since _insert_post will ignore the non-GMT date if the GMT date is set
1229
		if ( isset( $content_struct['post_date_gmt'] ) && ! ( $content_struct['post_date_gmt'] instanceof IXR_Date ) ) {
1230 View Code Duplication
			if ( $content_struct['post_date_gmt'] == '0000-00-00 00:00:00' || isset( $content_struct['post_date'] ) ) {
1231
				unset( $content_struct['post_date_gmt'] );
1232
			} else {
1233
				$content_struct['post_date_gmt'] = $this->_convert_date( $content_struct['post_date_gmt'] );
1234
			}
1235
		}
1236
1237
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1238
		do_action( 'xmlrpc_call', 'wp.newPost' );
1239
1240
		unset( $content_struct['ID'] );
1241
1242
		return $this->_insert_post( $user, $content_struct );
1243
	}
1244
1245
	/**
1246
	 * Helper method for filtering out elements from an array.
1247
	 *
1248
	 * @since 3.4.0
1249
	 *
1250
	 * @param int $count Number to compare to one.
1251
	 */
1252
	private function _is_greater_than_one( $count ) {
1253
		return $count > 1;
1254
	}
1255
1256
	/**
1257
	 * Encapsulate the logic for sticking a post
1258
	 * and determining if the user has permission to do so
1259
	 *
1260
	 * @since 4.3.0
1261
	 * @access private
1262
	 *
1263
	 * @param array $post_data
1264
	 * @param bool  $update
1265
	 * @return void|IXR_Error
1266
	 */
1267
	private function _toggle_sticky( $post_data, $update = false ) {
1268
		$post_type = get_post_type_object( $post_data['post_type'] );
1269
1270
		// Private and password-protected posts cannot be stickied.
1271
		if ( 'private' === $post_data['post_status'] || ! empty( $post_data['post_password'] ) ) {
1272
			// Error if the client tried to stick the post, otherwise, silently unstick.
1273
			if ( ! empty( $post_data['sticky'] ) ) {
1274
				return new IXR_Error( 401, __( 'Sorry, you cannot stick a private post.' ) );
1275
			}
1276
1277
			if ( $update ) {
1278
				unstick_post( $post_data['ID'] );
1279
			}
1280
		} elseif ( isset( $post_data['sticky'] ) )  {
1281
			if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
1282
				return new IXR_Error( 401, __( 'Sorry, you are not allowed to stick this post.' ) );
1283
			}
1284
1285
			$sticky = wp_validate_boolean( $post_data['sticky'] );
1286
			if ( $sticky ) {
1287
				stick_post( $post_data['ID'] );
1288
			} else {
1289
				unstick_post( $post_data['ID'] );
1290
			}
1291
		}
1292
	}
1293
1294
	/**
1295
	 * Helper method for wp_newPost() and wp_editPost(), containing shared logic.
1296
	 *
1297
	 * @since 3.4.0
1298
	 * @access protected
1299
	 *
1300
	 * @see wp_insert_post()
1301
	 *
1302
	 * @param WP_User         $user           The post author if post_author isn't set in $content_struct.
1303
	 * @param array|IXR_Error $content_struct Post data to insert.
1304
	 * @return IXR_Error|string
1305
	 */
1306
	protected function _insert_post( $user, $content_struct ) {
1307
		$defaults = array( 'post_status' => 'draft', 'post_type' => 'post', 'post_author' => 0,
1308
			'post_password' => '', 'post_excerpt' => '', 'post_content' => '', 'post_title' => '' );
1309
1310
		$post_data = wp_parse_args( $content_struct, $defaults );
1311
1312
		$post_type = get_post_type_object( $post_data['post_type'] );
1313
		if ( ! $post_type )
1314
			return new IXR_Error( 403, __( 'Invalid post type.' ) );
1315
1316
		$update = ! empty( $post_data['ID'] );
1317
1318
		if ( $update ) {
1319
			if ( ! get_post( $post_data['ID'] ) )
1320
				return new IXR_Error( 401, __( 'Invalid post ID.' ) );
1321
			if ( ! current_user_can( 'edit_post', $post_data['ID'] ) )
1322
				return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
1323
			if ( $post_data['post_type'] != get_post_type( $post_data['ID'] ) )
1324
				return new IXR_Error( 401, __( 'The post type may not be changed.' ) );
1325 View Code Duplication
		} else {
1326
			if ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( $post_type->cap->edit_posts ) )
1327
				return new IXR_Error( 401, __( 'Sorry, you are not allowed to post on this site.' ) );
1328
		}
1329
1330
		switch ( $post_data['post_status'] ) {
1331
			case 'draft':
1332
			case 'pending':
1333
				break;
1334 View Code Duplication
			case 'private':
1335
				if ( ! current_user_can( $post_type->cap->publish_posts ) )
1336
					return new IXR_Error( 401, __( 'Sorry, you are not allowed to create private posts in this post type' ) );
1337
				break;
1338
			case 'publish':
1339 View Code Duplication
			case 'future':
1340
				if ( ! current_user_can( $post_type->cap->publish_posts ) )
1341
					return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts in this post type' ) );
1342
				break;
1343
			default:
1344
				if ( ! get_post_status_object( $post_data['post_status'] ) )
1345
					$post_data['post_status'] = 'draft';
1346
			break;
1347
		}
1348
1349
		if ( ! empty( $post_data['post_password'] ) && ! current_user_can( $post_type->cap->publish_posts ) )
1350
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to create password protected posts in this post type' ) );
1351
1352
		$post_data['post_author'] = absint( $post_data['post_author'] );
1353
		if ( ! empty( $post_data['post_author'] ) && $post_data['post_author'] != $user->ID ) {
1354
			if ( ! current_user_can( $post_type->cap->edit_others_posts ) )
1355
				return new IXR_Error( 401, __( 'Sorry, you are not allowed to create posts as this user.' ) );
1356
1357
			$author = get_userdata( $post_data['post_author'] );
1358
1359
			if ( ! $author )
1360
				return new IXR_Error( 404, __( 'Invalid author ID.' ) );
1361
		} else {
1362
			$post_data['post_author'] = $user->ID;
1363
		}
1364
1365 View Code Duplication
		if ( isset( $post_data['comment_status'] ) && $post_data['comment_status'] != 'open' && $post_data['comment_status'] != 'closed' )
1366
			unset( $post_data['comment_status'] );
1367
1368 View Code Duplication
		if ( isset( $post_data['ping_status'] ) && $post_data['ping_status'] != 'open' && $post_data['ping_status'] != 'closed' )
1369
			unset( $post_data['ping_status'] );
1370
1371
		// Do some timestamp voodoo.
1372 View Code Duplication
		if ( ! empty( $post_data['post_date_gmt'] ) ) {
1373
			// We know this is supposed to be GMT, so we're going to slap that Z on there by force.
1374
			$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...
1375
		} elseif ( ! empty( $post_data['post_date'] ) ) {
1376
			$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...
1377
		}
1378
1379
		// Default to not flagging the post date to be edited unless it's intentional.
1380
		$post_data['edit_date'] = false;
1381
1382
		if ( ! empty( $dateCreated ) ) {
1383
			$post_data['post_date'] = get_date_from_gmt( iso8601_to_datetime( $dateCreated ) );
1384
			$post_data['post_date_gmt'] = iso8601_to_datetime( $dateCreated, 'GMT' );
1385
1386
			// Flag the post date to be edited.
1387
			$post_data['edit_date'] = true;
1388
		}
1389
1390
		if ( ! isset( $post_data['ID'] ) )
1391
			$post_data['ID'] = get_default_post_to_edit( $post_data['post_type'], true )->ID;
1392
		$post_ID = $post_data['ID'];
1393
1394
		if ( $post_data['post_type'] == 'post' ) {
1395
			$error = $this->_toggle_sticky( $post_data, $update );
1396
			if ( $error ) {
1397
				return $error;
1398
			}
1399
		}
1400
1401
		if ( isset( $post_data['post_thumbnail'] ) ) {
1402
			// empty value deletes, non-empty value adds/updates.
1403
			if ( ! $post_data['post_thumbnail'] )
1404
				delete_post_thumbnail( $post_ID );
1405
			elseif ( ! get_post( absint( $post_data['post_thumbnail'] ) ) )
1406
				return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
1407
			set_post_thumbnail( $post_ID, $post_data['post_thumbnail'] );
1408
			unset( $content_struct['post_thumbnail'] );
1409
		}
1410
1411
		if ( isset( $post_data['custom_fields'] ) )
1412
			$this->set_custom_fields( $post_ID, $post_data['custom_fields'] );
1413
1414
		if ( isset( $post_data['terms'] ) || isset( $post_data['terms_names'] ) ) {
1415
			$post_type_taxonomies = get_object_taxonomies( $post_data['post_type'], 'objects' );
1416
1417
			// Accumulate term IDs from terms and terms_names.
1418
			$terms = array();
1419
1420
			// First validate the terms specified by ID.
1421
			if ( isset( $post_data['terms'] ) && is_array( $post_data['terms'] ) ) {
1422
				$taxonomies = array_keys( $post_data['terms'] );
1423
1424
				// Validating term ids.
1425
				foreach ( $taxonomies as $taxonomy ) {
1426
					if ( ! array_key_exists( $taxonomy , $post_type_taxonomies ) )
1427
						return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
1428
1429 View Code Duplication
					if ( ! current_user_can( $post_type_taxonomies[$taxonomy]->cap->assign_terms ) )
1430
						return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
1431
1432
					$term_ids = $post_data['terms'][$taxonomy];
1433
					$terms[ $taxonomy ] = array();
1434
					foreach ( $term_ids as $term_id ) {
1435
						$term = get_term_by( 'id', $term_id, $taxonomy );
1436
1437
						if ( ! $term )
1438
							return new IXR_Error( 403, __( 'Invalid term ID.' ) );
1439
1440
						$terms[$taxonomy][] = (int) $term_id;
1441
					}
1442
				}
1443
			}
1444
1445
			// Now validate terms specified by name.
1446
			if ( isset( $post_data['terms_names'] ) && is_array( $post_data['terms_names'] ) ) {
1447
				$taxonomies = array_keys( $post_data['terms_names'] );
1448
1449
				foreach ( $taxonomies as $taxonomy ) {
1450
					if ( ! array_key_exists( $taxonomy , $post_type_taxonomies ) )
1451
						return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
1452
1453 View Code Duplication
					if ( ! current_user_can( $post_type_taxonomies[$taxonomy]->cap->assign_terms ) )
1454
						return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
1455
1456
					/*
1457
					 * For hierarchical taxonomies, we can't assign a term when multiple terms
1458
					 * in the hierarchy share the same name.
1459
					 */
1460
					$ambiguous_terms = array();
1461
					if ( is_taxonomy_hierarchical( $taxonomy ) ) {
1462
						$tax_term_names = get_terms( $taxonomy, array( 'fields' => 'names', 'hide_empty' => false ) );
1463
1464
						// Count the number of terms with the same name.
1465
						$tax_term_names_count = array_count_values( $tax_term_names );
1466
1467
						// Filter out non-ambiguous term names.
1468
						$ambiguous_tax_term_counts = array_filter( $tax_term_names_count, array( $this, '_is_greater_than_one') );
1469
1470
						$ambiguous_terms = array_keys( $ambiguous_tax_term_counts );
1471
					}
1472
1473
					$term_names = $post_data['terms_names'][$taxonomy];
1474
					foreach ( $term_names as $term_name ) {
1475
						if ( in_array( $term_name, $ambiguous_terms ) )
1476
							return new IXR_Error( 401, __( 'Ambiguous term name used in a hierarchical taxonomy. Please use term ID instead.' ) );
1477
1478
						$term = get_term_by( 'name', $term_name, $taxonomy );
1479
1480
						if ( ! $term ) {
1481
							// Term doesn't exist, so check that the user is allowed to create new terms.
1482 View Code Duplication
							if ( ! current_user_can( $post_type_taxonomies[$taxonomy]->cap->edit_terms ) )
1483
								return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a term to one of the given taxonomies.' ) );
1484
1485
							// Create the new term.
1486
							$term_info = wp_insert_term( $term_name, $taxonomy );
1487
							if ( is_wp_error( $term_info ) )
1488
								return new IXR_Error( 500, $term_info->get_error_message() );
1489
1490
							$terms[$taxonomy][] = (int) $term_info['term_id'];
1491
						} else {
1492
							$terms[$taxonomy][] = (int) $term->term_id;
1493
						}
1494
					}
1495
				}
1496
			}
1497
1498
			$post_data['tax_input'] = $terms;
1499
			unset( $post_data['terms'], $post_data['terms_names'] );
1500
		} else {
1501
			// Do not allow direct submission of 'tax_input', clients must use 'terms' and/or 'terms_names'.
1502
			unset( $post_data['tax_input'], $post_data['post_category'], $post_data['tags_input'] );
1503
		}
1504
1505
		if ( isset( $post_data['post_format'] ) ) {
1506
			$format = set_post_format( $post_ID, $post_data['post_format'] );
1507
1508
			if ( is_wp_error( $format ) )
1509
				return new IXR_Error( 500, $format->get_error_message() );
1510
1511
			unset( $post_data['post_format'] );
1512
		}
1513
1514
		// Handle enclosures.
1515
		$enclosure = isset( $post_data['enclosure'] ) ? $post_data['enclosure'] : null;
1516
		$this->add_enclosure_if_new( $post_ID, $enclosure );
1517
1518
		$this->attach_uploads( $post_ID, $post_data['post_content'] );
1519
1520
		/**
1521
		 * Filters post data array to be inserted via XML-RPC.
1522
		 *
1523
		 * @since 3.4.0
1524
		 *
1525
		 * @param array $post_data      Parsed array of post data.
1526
		 * @param array $content_struct Post data array.
1527
		 */
1528
		$post_data = apply_filters( 'xmlrpc_wp_insert_post_data', $post_data, $content_struct );
1529
1530
		$post_ID = $update ? wp_update_post( $post_data, true ) : wp_insert_post( $post_data, true );
1531
		if ( is_wp_error( $post_ID ) )
1532
			return new IXR_Error( 500, $post_ID->get_error_message() );
1533
1534
		if ( ! $post_ID )
1535
			return new IXR_Error( 401, __( 'Sorry, your entry could not be posted. Something wrong happened.' ) );
1536
1537
		return strval( $post_ID );
1538
	}
1539
1540
	/**
1541
	 * Edit a post for any registered post type.
1542
	 *
1543
	 * The $content_struct parameter only needs to contain fields that
1544
	 * should be changed. All other fields will retain their existing values.
1545
	 *
1546
	 * @since 3.4.0
1547
	 *
1548
	 * @param array  $args {
1549
	 *     Method arguments. Note: arguments must be ordered as documented.
1550
	 *
1551
	 *     @type int    $blog_id        Blog ID (unused).
1552
	 *     @type string $username       Username.
1553
	 *     @type string $password       Password.
1554
	 *     @type int    $post_id        Post ID.
1555
	 *     @type array  $content_struct Extra content arguments.
1556
	 * }
1557
	 * @return true|IXR_Error True on success, IXR_Error on failure.
1558
	 */
1559
	public function wp_editPost( $args ) {
1560
		if ( ! $this->minimum_args( $args, 5 ) )
1561
			return $this->error;
1562
1563
		$this->escape( $args );
1564
1565
		$username       = $args[1];
1566
		$password       = $args[2];
1567
		$post_id        = (int) $args[3];
1568
		$content_struct = $args[4];
1569
1570
		if ( ! $user = $this->login( $username, $password ) )
1571
			return $this->error;
1572
1573
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1574
		do_action( 'xmlrpc_call', 'wp.editPost' );
1575
1576
		$post = get_post( $post_id, ARRAY_A );
1577
1578
		if ( empty( $post['ID'] ) )
1579
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1580
1581
		if ( isset( $content_struct['if_not_modified_since'] ) ) {
1582
			// If the post has been modified since the date provided, return an error.
1583
			if ( mysql2date( 'U', $post['post_modified_gmt'] ) > $content_struct['if_not_modified_since']->getTimestamp() ) {
1584
				return new IXR_Error( 409, __( 'There is a revision of this post that is more recent.' ) );
1585
			}
1586
		}
1587
1588
		// Convert the date field back to IXR form.
1589
		$post['post_date'] = $this->_convert_date( $post['post_date'] );
1590
1591
		/*
1592
		 * Ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct,
1593
		 * since _insert_post() will ignore the non-GMT date if the GMT date is set.
1594
		 */
1595 View Code Duplication
		if ( $post['post_date_gmt'] == '0000-00-00 00:00:00' || isset( $content_struct['post_date'] ) )
1596
			unset( $post['post_date_gmt'] );
1597
		else
1598
			$post['post_date_gmt'] = $this->_convert_date( $post['post_date_gmt'] );
1599
1600
		$this->escape( $post );
1601
		$merged_content_struct = array_merge( $post, $content_struct );
1602
1603
		$retval = $this->_insert_post( $user, $merged_content_struct );
1604
		if ( $retval instanceof IXR_Error )
1605
			return $retval;
1606
1607
		return true;
1608
	}
1609
1610
	/**
1611
	 * Delete a post for any registered post type.
1612
	 *
1613
	 * @since 3.4.0
1614
	 *
1615
	 * @see wp_delete_post()
1616
	 *
1617
	 * @param array  $args {
1618
	 *     Method arguments. Note: arguments must be ordered as documented.
1619
	 *
1620
	 *     @type int    $blog_id  Blog ID (unused).
1621
	 *     @type string $username Username.
1622
	 *     @type string $password Password.
1623
	 *     @type int    $post_id  Post ID.
1624
	 * }
1625
	 * @return true|IXR_Error True on success, IXR_Error instance on failure.
1626
	 */
1627
	public function wp_deletePost( $args ) {
1628
		if ( ! $this->minimum_args( $args, 4 ) )
1629
			return $this->error;
1630
1631
		$this->escape( $args );
1632
1633
		$username   = $args[1];
1634
		$password   = $args[2];
1635
		$post_id    = (int) $args[3];
1636
1637
		if ( ! $user = $this->login( $username, $password ) )
1638
			return $this->error;
1639
1640
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1641
		do_action( 'xmlrpc_call', 'wp.deletePost' );
1642
1643
		$post = get_post( $post_id, ARRAY_A );
1644
		if ( empty( $post['ID'] ) ) {
1645
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1646
		}
1647
1648
		if ( ! current_user_can( 'delete_post', $post_id ) ) {
1649
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this post.' ) );
1650
		}
1651
1652
		$result = wp_delete_post( $post_id );
1653
1654
		if ( ! $result ) {
1655
			return new IXR_Error( 500, __( 'The post cannot be deleted.' ) );
1656
		}
1657
1658
		return true;
1659
	}
1660
1661
	/**
1662
	 * Retrieve a post.
1663
	 *
1664
	 * @since 3.4.0
1665
	 *
1666
	 * The optional $fields parameter specifies what fields will be included
1667
	 * in the response array. This should be a list of field names. 'post_id' will
1668
	 * always be included in the response regardless of the value of $fields.
1669
	 *
1670
	 * Instead of, or in addition to, individual field names, conceptual group
1671
	 * names can be used to specify multiple fields. The available conceptual
1672
	 * groups are 'post' (all basic fields), 'taxonomies', 'custom_fields',
1673
	 * and 'enclosure'.
1674
	 *
1675
	 * @see get_post()
1676
	 *
1677
	 * @param array $args {
1678
	 *     Method arguments. Note: arguments must be ordered as documented.
1679
	 *
1680
	 *     @type int    $blog_id  Blog ID (unused).
1681
	 *     @type string $username Username.
1682
	 *     @type string $password Password.
1683
	 *     @type int    $post_id  Post ID.
1684
	 *     @type array  $fields   The subset of post type fields to return.
1685
	 * }
1686
	 * @return array|IXR_Error Array contains (based on $fields parameter):
1687
	 *  - 'post_id'
1688
	 *  - 'post_title'
1689
	 *  - 'post_date'
1690
	 *  - 'post_date_gmt'
1691
	 *  - 'post_modified'
1692
	 *  - 'post_modified_gmt'
1693
	 *  - 'post_status'
1694
	 *  - 'post_type'
1695
	 *  - 'post_name'
1696
	 *  - 'post_author'
1697
	 *  - 'post_password'
1698
	 *  - 'post_excerpt'
1699
	 *  - 'post_content'
1700
	 *  - 'link'
1701
	 *  - 'comment_status'
1702
	 *  - 'ping_status'
1703
	 *  - 'sticky'
1704
	 *  - 'custom_fields'
1705
	 *  - 'terms'
1706
	 *  - 'categories'
1707
	 *  - 'tags'
1708
	 *  - 'enclosure'
1709
	 */
1710
	public function wp_getPost( $args ) {
1711
		if ( ! $this->minimum_args( $args, 4 ) )
1712
			return $this->error;
1713
1714
		$this->escape( $args );
1715
1716
		$username = $args[1];
1717
		$password = $args[2];
1718
		$post_id  = (int) $args[3];
1719
1720 View Code Duplication
		if ( isset( $args[4] ) ) {
1721
			$fields = $args[4];
1722
		} else {
1723
			/**
1724
			 * Filters the list of post query fields used by the given XML-RPC method.
1725
			 *
1726
			 * @since 3.4.0
1727
			 *
1728
			 * @param array  $fields Array of post fields. Default array contains 'post', 'terms', and 'custom_fields'.
1729
			 * @param string $method Method name.
1730
			 */
1731
			$fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPost' );
1732
		}
1733
1734
		if ( ! $user = $this->login( $username, $password ) )
1735
			return $this->error;
1736
1737
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1738
		do_action( 'xmlrpc_call', 'wp.getPost' );
1739
1740
		$post = get_post( $post_id, ARRAY_A );
1741
1742
		if ( empty( $post['ID'] ) )
1743
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
1744
1745
		if ( ! current_user_can( 'edit_post', $post_id ) )
1746
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
1747
1748
		return $this->_prepare_post( $post, $fields );
1749
	}
1750
1751
	/**
1752
	 * Retrieve posts.
1753
	 *
1754
	 * @since 3.4.0
1755
	 *
1756
	 * @see wp_get_recent_posts()
1757
	 * @see wp_getPost() for more on `$fields`
1758
	 * @see get_posts() for more on `$filter` values
1759
	 *
1760
	 * @param array $args {
1761
	 *     Method arguments. Note: arguments must be ordered as documented.
1762
	 *
1763
	 *     @type int    $blog_id  Blog ID (unused).
1764
	 *     @type string $username Username.
1765
	 *     @type string $password Password.
1766
	 *     @type array  $filter   Optional. Modifies the query used to retrieve posts. Accepts 'post_type',
1767
	 *                            'post_status', 'number', 'offset', 'orderby', 's', and 'order'.
1768
	 *                            Default empty array.
1769
	 *     @type array  $fields   Optional. The subset of post type fields to return in the response array.
1770
	 * }
1771
	 * @return array|IXR_Error Array contains a collection of posts.
1772
	 */
1773
	public function wp_getPosts( $args ) {
1774
		if ( ! $this->minimum_args( $args, 3 ) )
1775
			return $this->error;
1776
1777
		$this->escape( $args );
1778
1779
		$username = $args[1];
1780
		$password = $args[2];
1781
		$filter   = isset( $args[3] ) ? $args[3] : array();
1782
1783 View Code Duplication
		if ( isset( $args[4] ) ) {
1784
			$fields = $args[4];
1785
		} else {
1786
			/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1787
			$fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPosts' );
1788
		}
1789
1790
		if ( ! $user = $this->login( $username, $password ) )
1791
			return $this->error;
1792
1793
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1794
		do_action( 'xmlrpc_call', 'wp.getPosts' );
1795
1796
		$query = array();
1797
1798
		if ( isset( $filter['post_type'] ) ) {
1799
			$post_type = get_post_type_object( $filter['post_type'] );
1800
			if ( ! ( (bool) $post_type ) )
1801
				return new IXR_Error( 403, __( 'The post type specified is not valid' ) );
1802
		} else {
1803
			$post_type = get_post_type_object( 'post' );
1804
		}
1805
1806
		if ( ! current_user_can( $post_type->cap->edit_posts ) )
1807
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type.' ));
1808
1809
		$query['post_type'] = $post_type->name;
1810
1811
		if ( isset( $filter['post_status'] ) )
1812
			$query['post_status'] = $filter['post_status'];
1813
1814
		if ( isset( $filter['number'] ) )
1815
			$query['numberposts'] = absint( $filter['number'] );
1816
1817
		if ( isset( $filter['offset'] ) )
1818
			$query['offset'] = absint( $filter['offset'] );
1819
1820 View Code Duplication
		if ( isset( $filter['orderby'] ) ) {
1821
			$query['orderby'] = $filter['orderby'];
1822
1823
			if ( isset( $filter['order'] ) )
1824
				$query['order'] = $filter['order'];
1825
		}
1826
1827
		if ( isset( $filter['s'] ) ) {
1828
			$query['s'] = $filter['s'];
1829
		}
1830
1831
		$posts_list = wp_get_recent_posts( $query );
1832
1833
		if ( ! $posts_list )
1834
			return array();
1835
1836
		// Holds all the posts data.
1837
		$struct = array();
1838
1839
		foreach ( $posts_list as $post ) {
1840
			if ( ! current_user_can( 'edit_post', $post['ID'] ) )
1841
				continue;
1842
1843
			$struct[] = $this->_prepare_post( $post, $fields );
1844
		}
1845
1846
		return $struct;
1847
	}
1848
1849
	/**
1850
	 * Create a new term.
1851
	 *
1852
	 * @since 3.4.0
1853
	 *
1854
	 * @see wp_insert_term()
1855
	 *
1856
	 * @param array $args {
1857
	 *     Method arguments. Note: arguments must be ordered as documented.
1858
	 *
1859
	 *     @type int    $blog_id        Blog ID (unused).
1860
	 *     @type string $username       Username.
1861
	 *     @type string $password       Password.
1862
	 *     @type array  $content_struct Content struct for adding a new term. The struct must contain
1863
	 *                                  the term 'name' and 'taxonomy'. Optional accepted values include
1864
	 *                                  'parent', 'description', and 'slug'.
1865
	 * }
1866
	 * @return int|IXR_Error The term ID on success, or an IXR_Error object on failure.
1867
	 */
1868
	public function wp_newTerm( $args ) {
1869
		if ( ! $this->minimum_args( $args, 4 ) )
1870
			return $this->error;
1871
1872
		$this->escape( $args );
1873
1874
		$username       = $args[1];
1875
		$password       = $args[2];
1876
		$content_struct = $args[3];
1877
1878
		if ( ! $user = $this->login( $username, $password ) )
1879
			return $this->error;
1880
1881
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1882
		do_action( 'xmlrpc_call', 'wp.newTerm' );
1883
1884
		if ( ! taxonomy_exists( $content_struct['taxonomy'] ) )
1885
			return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
1886
1887
		$taxonomy = get_taxonomy( $content_struct['taxonomy'] );
1888
1889
		if ( ! current_user_can( $taxonomy->cap->manage_terms ) )
1890
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to create terms in this taxonomy.' ) );
1891
1892
		$taxonomy = (array) $taxonomy;
1893
1894
		// hold the data of the term
1895
		$term_data = array();
1896
1897
		$term_data['name'] = trim( $content_struct['name'] );
1898
		if ( empty( $term_data['name'] ) )
1899
			return new IXR_Error( 403, __( 'The term name cannot be empty.' ) );
1900
1901 View Code Duplication
		if ( isset( $content_struct['parent'] ) ) {
1902
			if ( ! $taxonomy['hierarchical'] )
1903
				return new IXR_Error( 403, __( 'This taxonomy is not hierarchical.' ) );
1904
1905
			$parent_term_id = (int) $content_struct['parent'];
1906
			$parent_term = get_term( $parent_term_id , $taxonomy['name'] );
1907
1908
			if ( is_wp_error( $parent_term ) )
1909
				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...
1910
1911
			if ( ! $parent_term )
1912
				return new IXR_Error( 403, __( 'Parent term does not exist.' ) );
1913
1914
			$term_data['parent'] = $content_struct['parent'];
1915
		}
1916
1917
		if ( isset( $content_struct['description'] ) )
1918
			$term_data['description'] = $content_struct['description'];
1919
1920
		if ( isset( $content_struct['slug'] ) )
1921
			$term_data['slug'] = $content_struct['slug'];
1922
1923
		$term = wp_insert_term( $term_data['name'] , $taxonomy['name'] , $term_data );
1924
1925
		if ( is_wp_error( $term ) )
1926
			return new IXR_Error( 500, $term->get_error_message() );
1927
1928
		if ( ! $term )
1929
			return new IXR_Error( 500, __( 'Sorry, your term could not be created. Something wrong happened.' ) );
1930
1931
		return strval( $term['term_id'] );
1932
	}
1933
1934
	/**
1935
	 * Edit a term.
1936
	 *
1937
	 * @since 3.4.0
1938
	 *
1939
	 * @see wp_update_term()
1940
	 *
1941
	 * @param array $args {
1942
	 *     Method arguments. Note: arguments must be ordered as documented.
1943
	 *
1944
	 *     @type int    $blog_id        Blog ID (unused).
1945
	 *     @type string $username       Username.
1946
	 *     @type string $password       Password.
1947
	 *     @type int    $term_id        Term ID.
1948
	 *     @type array  $content_struct Content struct for editing a term. The struct must contain the
1949
	 *                                  term ''taxonomy'. Optional accepted values include 'name', 'parent',
1950
	 *                                  'description', and 'slug'.
1951
	 * }
1952
	 * @return true|IXR_Error True on success, IXR_Error instance on failure.
1953
	 */
1954
	public function wp_editTerm( $args ) {
1955
		if ( ! $this->minimum_args( $args, 5 ) )
1956
			return $this->error;
1957
1958
		$this->escape( $args );
1959
1960
		$username       = $args[1];
1961
		$password       = $args[2];
1962
		$term_id        = (int) $args[3];
1963
		$content_struct = $args[4];
1964
1965
		if ( ! $user = $this->login( $username, $password ) )
1966
			return $this->error;
1967
1968
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
1969
		do_action( 'xmlrpc_call', 'wp.editTerm' );
1970
1971
		if ( ! taxonomy_exists( $content_struct['taxonomy'] ) )
1972
			return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
1973
1974
		$taxonomy = get_taxonomy( $content_struct['taxonomy'] );
1975
1976
		if ( ! current_user_can( $taxonomy->cap->edit_terms ) )
1977
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit terms in this taxonomy.' ) );
1978
1979
		$taxonomy = (array) $taxonomy;
1980
1981
		// hold the data of the term
1982
		$term_data = array();
1983
1984
		$term = get_term( $term_id , $content_struct['taxonomy'] );
1985
1986
		if ( is_wp_error( $term ) )
1987
			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...
1988
1989
		if ( ! $term )
1990
			return new IXR_Error( 404, __( 'Invalid term ID.' ) );
1991
1992
		if ( isset( $content_struct['name'] ) ) {
1993
			$term_data['name'] = trim( $content_struct['name'] );
1994
1995
			if ( empty( $term_data['name'] ) )
1996
				return new IXR_Error( 403, __( 'The term name cannot be empty.' ) );
1997
		}
1998
1999 View Code Duplication
		if ( ! empty( $content_struct['parent'] ) ) {
2000
			if ( ! $taxonomy['hierarchical'] )
2001
				return new IXR_Error( 403, __( "This taxonomy is not hierarchical so you can't set a parent." ) );
2002
2003
			$parent_term_id = (int) $content_struct['parent'];
2004
			$parent_term = get_term( $parent_term_id , $taxonomy['name'] );
2005
2006
			if ( is_wp_error( $parent_term ) )
2007
				return new IXR_Error( 500, $parent_term->get_error_message() );
2008
2009
			if ( ! $parent_term )
2010
				return new IXR_Error( 403, __( 'Parent term does not exist.' ) );
2011
2012
			$term_data['parent'] = $content_struct['parent'];
2013
		}
2014
2015
		if ( isset( $content_struct['description'] ) )
2016
			$term_data['description'] = $content_struct['description'];
2017
2018
		if ( isset( $content_struct['slug'] ) )
2019
			$term_data['slug'] = $content_struct['slug'];
2020
2021
		$term = wp_update_term( $term_id , $taxonomy['name'] , $term_data );
2022
2023
		if ( is_wp_error( $term ) )
2024
			return new IXR_Error( 500, $term->get_error_message() );
2025
2026
		if ( ! $term )
2027
			return new IXR_Error( 500, __( 'Sorry, editing the term failed.' ) );
2028
2029
		return true;
2030
	}
2031
2032
	/**
2033
	 * Delete a term.
2034
	 *
2035
	 * @since 3.4.0
2036
	 *
2037
	 * @see wp_delete_term()
2038
	 *
2039
	 * @param array  $args {
2040
	 *     Method arguments. Note: arguments must be ordered as documented.
2041
	 *
2042
	 *     @type int    $blog_id      Blog ID (unused).
2043
	 *     @type string $username     Username.
2044
	 *     @type string $password     Password.
2045
	 *     @type string $taxnomy_name Taxonomy name.
2046
	 *     @type int    $term_id      Term ID.
2047
	 * }
2048
	 * @return bool|IXR_Error True on success, IXR_Error instance on failure.
2049
	 */
2050
	public function wp_deleteTerm( $args ) {
2051
		if ( ! $this->minimum_args( $args, 5 ) )
2052
			return $this->error;
2053
2054
		$this->escape( $args );
2055
2056
		$username           = $args[1];
2057
		$password           = $args[2];
2058
		$taxonomy           = $args[3];
2059
		$term_id            = (int) $args[4];
2060
2061
		if ( ! $user = $this->login( $username, $password ) )
2062
			return $this->error;
2063
2064
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2065
		do_action( 'xmlrpc_call', 'wp.deleteTerm' );
2066
2067
		if ( ! taxonomy_exists( $taxonomy ) )
2068
			return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2069
2070
		$taxonomy = get_taxonomy( $taxonomy );
2071
2072
		if ( ! current_user_can( $taxonomy->cap->delete_terms ) )
2073
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete terms in this taxonomy.' ) );
2074
2075
		$term = get_term( $term_id, $taxonomy->name );
2076
2077
		if ( is_wp_error( $term ) )
2078
			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...
2079
2080
		if ( ! $term )
2081
			return new IXR_Error( 404, __( 'Invalid term ID.' ) );
2082
2083
		$result = wp_delete_term( $term_id, $taxonomy->name );
2084
2085
		if ( is_wp_error( $result ) )
2086
			return new IXR_Error( 500, $term->get_error_message() );
2087
2088
		if ( ! $result )
2089
			return new IXR_Error( 500, __( 'Sorry, deleting the term failed.' ) );
2090
2091
		return $result;
2092
	}
2093
2094
	/**
2095
	 * Retrieve a term.
2096
	 *
2097
	 * @since 3.4.0
2098
	 *
2099
	 * @see get_term()
2100
	 *
2101
	 * @param array  $args {
2102
	 *     Method arguments. Note: arguments must be ordered as documented.
2103
	 *
2104
	 *     @type int    $blog_id  Blog ID (unused).
2105
	 *     @type string $username Username.
2106
	 *     @type string $password Password.
2107
	 *     @type string $taxnomy  Taxonomy name.
2108
	 *     @type string $term_id  Term ID.
2109
	 * }
2110
	 * @return array|IXR_Error IXR_Error on failure, array on success, containing:
2111
	 *  - 'term_id'
2112
	 *  - 'name'
2113
	 *  - 'slug'
2114
	 *  - 'term_group'
2115
	 *  - 'term_taxonomy_id'
2116
	 *  - 'taxonomy'
2117
	 *  - 'description'
2118
	 *  - 'parent'
2119
	 *  - 'count'
2120
	 */
2121
	public function wp_getTerm( $args ) {
2122
		if ( ! $this->minimum_args( $args, 5 ) )
2123
			return $this->error;
2124
2125
		$this->escape( $args );
2126
2127
		$username           = $args[1];
2128
		$password           = $args[2];
2129
		$taxonomy           = $args[3];
2130
		$term_id            = (int) $args[4];
2131
2132
		if ( ! $user = $this->login( $username, $password ) )
2133
			return $this->error;
2134
2135
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2136
		do_action( 'xmlrpc_call', 'wp.getTerm' );
2137
2138
		if ( ! taxonomy_exists( $taxonomy ) )
2139
			return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2140
2141
		$taxonomy = get_taxonomy( $taxonomy );
2142
2143
		if ( ! current_user_can( $taxonomy->cap->assign_terms ) )
2144
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) );
2145
2146
		$term = get_term( $term_id , $taxonomy->name, ARRAY_A );
2147
2148
		if ( is_wp_error( $term ) )
2149
			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...
2150
2151
		if ( ! $term )
2152
			return new IXR_Error( 404, __( 'Invalid term ID.' ) );
2153
2154
		return $this->_prepare_term( $term );
2155
	}
2156
2157
	/**
2158
	 * Retrieve all terms for a taxonomy.
2159
	 *
2160
	 * @since 3.4.0
2161
	 *
2162
	 * The optional $filter parameter modifies the query used to retrieve terms.
2163
	 * Accepted keys are 'number', 'offset', 'orderby', 'order', 'hide_empty', and 'search'.
2164
	 *
2165
	 * @see get_terms()
2166
	 *
2167
	 * @param array  $args {
2168
	 *     Method arguments. Note: arguments must be ordered as documented.
2169
	 *
2170
	 *     @type int    $blog_id  Blog ID (unused).
2171
	 *     @type string $username Username.
2172
	 *     @type string $password Password.
2173
	 *     @type string $taxnomy  Taxonomy name.
2174
	 *     @type array  $filter   Optional. Modifies the query used to retrieve posts. Accepts 'number',
2175
	 *                            'offset', 'orderby', 'order', 'hide_empty', and 'search'. Default empty array.
2176
	 * }
2177
	 * @return array|IXR_Error An associative array of terms data on success, IXR_Error instance otherwise.
2178
	 */
2179
	public function wp_getTerms( $args ) {
2180
		if ( ! $this->minimum_args( $args, 4 ) )
2181
			return $this->error;
2182
2183
		$this->escape( $args );
2184
2185
		$username       = $args[1];
2186
		$password       = $args[2];
2187
		$taxonomy       = $args[3];
2188
		$filter         = isset( $args[4] ) ? $args[4] : array();
2189
2190
		if ( ! $user = $this->login( $username, $password ) )
2191
			return $this->error;
2192
2193
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2194
		do_action( 'xmlrpc_call', 'wp.getTerms' );
2195
2196
		if ( ! taxonomy_exists( $taxonomy ) )
2197
			return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2198
2199
		$taxonomy = get_taxonomy( $taxonomy );
2200
2201
		if ( ! current_user_can( $taxonomy->cap->assign_terms ) )
2202
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) );
2203
2204
		$query = array();
2205
2206
		if ( isset( $filter['number'] ) )
2207
			$query['number'] = absint( $filter['number'] );
2208
2209
		if ( isset( $filter['offset'] ) )
2210
			$query['offset'] = absint( $filter['offset'] );
2211
2212 View Code Duplication
		if ( isset( $filter['orderby'] ) ) {
2213
			$query['orderby'] = $filter['orderby'];
2214
2215
			if ( isset( $filter['order'] ) )
2216
				$query['order'] = $filter['order'];
2217
		}
2218
2219
		if ( isset( $filter['hide_empty'] ) )
2220
			$query['hide_empty'] = $filter['hide_empty'];
2221
		else
2222
			$query['get'] = 'all';
2223
2224
		if ( isset( $filter['search'] ) )
2225
			$query['search'] = $filter['search'];
2226
2227
		$terms = get_terms( $taxonomy->name, $query );
2228
2229
		if ( is_wp_error( $terms ) )
2230
			return new IXR_Error( 500, $terms->get_error_message() );
2231
2232
		$struct = array();
2233
2234
		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...
2235
			$struct[] = $this->_prepare_term( $term );
2236
		}
2237
2238
		return $struct;
2239
	}
2240
2241
	/**
2242
	 * Retrieve a taxonomy.
2243
	 *
2244
	 * @since 3.4.0
2245
	 *
2246
	 * @see get_taxonomy()
2247
	 *
2248
	 * @param array  $args {
2249
	 *     Method arguments. Note: arguments must be ordered as documented.
2250
	 *
2251
	 *     @type int    $blog_id  Blog ID (unused).
2252
	 *     @type string $username Username.
2253
	 *     @type string $password Password.
2254
	 *     @type string $taxnomy  Taxonomy name.
2255
	 *     @type array  $fields   Optional. Array of taxonomy fields to limit to in the return.
2256
	 *                            Accepts 'labels', 'cap', 'menu', and 'object_type'.
2257
	 *                            Default empty array.
2258
	 * }
2259
	 * @return array|IXR_Error An array of taxonomy data on success, IXR_Error instance otherwise.
2260
	 */
2261 View Code Duplication
	public function wp_getTaxonomy( $args ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2262
		if ( ! $this->minimum_args( $args, 4 ) )
2263
			return $this->error;
2264
2265
		$this->escape( $args );
2266
2267
		$username = $args[1];
2268
		$password = $args[2];
2269
		$taxonomy = $args[3];
2270
2271
		if ( isset( $args[4] ) ) {
2272
			$fields = $args[4];
2273
		} else {
2274
			/**
2275
			 * Filters the taxonomy query fields used by the given XML-RPC method.
2276
			 *
2277
			 * @since 3.4.0
2278
			 *
2279
			 * @param array  $fields An array of taxonomy fields to retrieve.
2280
			 * @param string $method The method name.
2281
			 */
2282
			$fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomy' );
2283
		}
2284
2285
		if ( ! $user = $this->login( $username, $password ) )
2286
			return $this->error;
2287
2288
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2289
		do_action( 'xmlrpc_call', 'wp.getTaxonomy' );
2290
2291
		if ( ! taxonomy_exists( $taxonomy ) )
2292
			return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
2293
2294
		$taxonomy = get_taxonomy( $taxonomy );
2295
2296
		if ( ! current_user_can( $taxonomy->cap->assign_terms ) )
2297
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) );
2298
2299
		return $this->_prepare_taxonomy( $taxonomy, $fields );
2300
	}
2301
2302
	/**
2303
	 * Retrieve all taxonomies.
2304
	 *
2305
	 * @since 3.4.0
2306
	 *
2307
	 * @see get_taxonomies()
2308
	 *
2309
	 * @param array  $args {
2310
	 *     Method arguments. Note: arguments must be ordered as documented.
2311
	 *
2312
	 *     @type int    $blog_id  Blog ID (unused).
2313
	 *     @type string $username Username.
2314
	 *     @type string $password Password.
2315
	 *     @type array  $filter   Optional. An array of arguments for retrieving taxonomies.
2316
	 *     @type array  $fields   Optional. The subset of taxonomy fields to return.
2317
	 * }
2318
	 * @return array|IXR_Error An associative array of taxonomy data with returned fields determined
2319
	 *                         by `$fields`, or an IXR_Error instance on failure.
2320
	 */
2321 View Code Duplication
	public function wp_getTaxonomies( $args ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2322
		if ( ! $this->minimum_args( $args, 3 ) )
2323
			return $this->error;
2324
2325
		$this->escape( $args );
2326
2327
		$username = $args[1];
2328
		$password = $args[2];
2329
		$filter   = isset( $args[3] ) ? $args[3] : array( 'public' => true );
2330
2331
		if ( isset( $args[4] ) ) {
2332
			$fields = $args[4];
2333
		} else {
2334
			/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2335
			$fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomies' );
2336
		}
2337
2338
		if ( ! $user = $this->login( $username, $password ) )
2339
			return $this->error;
2340
2341
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2342
		do_action( 'xmlrpc_call', 'wp.getTaxonomies' );
2343
2344
		$taxonomies = get_taxonomies( $filter, 'objects' );
2345
2346
		// holds all the taxonomy data
2347
		$struct = array();
2348
2349
		foreach ( $taxonomies as $taxonomy ) {
2350
			// capability check for post_types
2351
			if ( ! current_user_can( $taxonomy->cap->assign_terms ) )
2352
				continue;
2353
2354
			$struct[] = $this->_prepare_taxonomy( $taxonomy, $fields );
2355
		}
2356
2357
		return $struct;
2358
	}
2359
2360
	/**
2361
	 * Retrieve a user.
2362
	 *
2363
	 * The optional $fields parameter specifies what fields will be included
2364
	 * in the response array. This should be a list of field names. 'user_id' will
2365
	 * always be included in the response regardless of the value of $fields.
2366
	 *
2367
	 * Instead of, or in addition to, individual field names, conceptual group
2368
	 * names can be used to specify multiple fields. The available conceptual
2369
	 * groups are 'basic' and 'all'.
2370
	 *
2371
	 * @uses get_userdata()
2372
	 *
2373
	 * @param array  $args {
2374
	 *     Method arguments. Note: arguments must be ordered as documented.
2375
	 *
2376
	 *     @type int    $blog_id (unused)
2377
	 *     @type string $username
2378
	 *     @type string $password
2379
	 *     @type int    $user_id
2380
	 *     @type array  $fields (optional)
2381
	 * }
2382
	 * @return array|IXR_Error Array contains (based on $fields parameter):
2383
	 *  - 'user_id'
2384
	 *  - 'username'
2385
	 *  - 'first_name'
2386
	 *  - 'last_name'
2387
	 *  - 'registered'
2388
	 *  - 'bio'
2389
	 *  - 'email'
2390
	 *  - 'nickname'
2391
	 *  - 'nicename'
2392
	 *  - 'url'
2393
	 *  - 'display_name'
2394
	 *  - 'roles'
2395
	 */
2396
	public function wp_getUser( $args ) {
2397
		if ( ! $this->minimum_args( $args, 4 ) )
2398
			return $this->error;
2399
2400
		$this->escape( $args );
2401
2402
		$username = $args[1];
2403
		$password = $args[2];
2404
		$user_id  = (int) $args[3];
2405
2406 View Code Duplication
		if ( isset( $args[4] ) ) {
2407
			$fields = $args[4];
2408
		} else {
2409
			/**
2410
			 * Filters the default user query fields used by the given XML-RPC method.
2411
			 *
2412
			 * @since 3.5.0
2413
			 *
2414
			 * @param array  $fields User query fields for given method. Default 'all'.
2415
			 * @param string $method The method name.
2416
			 */
2417
			$fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUser' );
2418
		}
2419
2420
		if ( ! $user = $this->login( $username, $password ) )
2421
			return $this->error;
2422
2423
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2424
		do_action( 'xmlrpc_call', 'wp.getUser' );
2425
2426
		if ( ! current_user_can( 'edit_user', $user_id ) )
2427
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this user.' ) );
2428
2429
		$user_data = get_userdata( $user_id );
2430
2431
		if ( ! $user_data )
2432
			return new IXR_Error( 404, __( 'Invalid user ID.' ) );
2433
2434
		return $this->_prepare_user( $user_data, $fields );
2435
	}
2436
2437
	/**
2438
	 * Retrieve users.
2439
	 *
2440
	 * The optional $filter parameter modifies the query used to retrieve users.
2441
	 * Accepted keys are 'number' (default: 50), 'offset' (default: 0), 'role',
2442
	 * 'who', 'orderby', and 'order'.
2443
	 *
2444
	 * The optional $fields parameter specifies what fields will be included
2445
	 * in the response array.
2446
	 *
2447
	 * @uses get_users()
2448
	 * @see wp_getUser() for more on $fields and return values
2449
	 *
2450
	 * @param array  $args {
2451
	 *     Method arguments. Note: arguments must be ordered as documented.
2452
	 *
2453
	 *     @type int    $blog_id (unused)
2454
	 *     @type string $username
2455
	 *     @type string $password
2456
	 *     @type array  $filter (optional)
2457
	 *     @type array  $fields (optional)
2458
	 * }
2459
	 * @return array|IXR_Error users data
2460
	 */
2461
	public function wp_getUsers( $args ) {
2462
		if ( ! $this->minimum_args( $args, 3 ) )
2463
			return $this->error;
2464
2465
		$this->escape( $args );
2466
2467
		$username = $args[1];
2468
		$password = $args[2];
2469
		$filter   = isset( $args[3] ) ? $args[3] : array();
2470
2471 View Code Duplication
		if ( isset( $args[4] ) ) {
2472
			$fields = $args[4];
2473
		} else {
2474
			/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2475
			$fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUsers' );
2476
		}
2477
2478
		if ( ! $user = $this->login( $username, $password ) )
2479
			return $this->error;
2480
2481
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2482
		do_action( 'xmlrpc_call', 'wp.getUsers' );
2483
2484
		if ( ! current_user_can( 'list_users' ) )
2485
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to browse users.' ) );
2486
2487
		$query = array( 'fields' => 'all_with_meta' );
2488
2489
		$query['number'] = ( isset( $filter['number'] ) ) ? absint( $filter['number'] ) : 50;
2490
		$query['offset'] = ( isset( $filter['offset'] ) ) ? absint( $filter['offset'] ) : 0;
2491
2492 View Code Duplication
		if ( isset( $filter['orderby'] ) ) {
2493
			$query['orderby'] = $filter['orderby'];
2494
2495
			if ( isset( $filter['order'] ) )
2496
				$query['order'] = $filter['order'];
2497
		}
2498
2499
		if ( isset( $filter['role'] ) ) {
2500
			if ( get_role( $filter['role'] ) === null )
2501
				return new IXR_Error( 403, __( 'The role specified is not valid' ) );
2502
2503
			$query['role'] = $filter['role'];
2504
		}
2505
2506
		if ( isset( $filter['who'] ) ) {
2507
			$query['who'] = $filter['who'];
2508
		}
2509
2510
		$users = get_users( $query );
2511
2512
		$_users = array();
2513
		foreach ( $users as $user_data ) {
2514
			if ( current_user_can( 'edit_user', $user_data->ID ) )
2515
				$_users[] = $this->_prepare_user( $user_data, $fields );
2516
		}
2517
		return $_users;
2518
	}
2519
2520
	/**
2521
	 * Retrieve information about the requesting user.
2522
	 *
2523
	 * @uses get_userdata()
2524
	 *
2525
	 * @param array  $args {
2526
	 *     Method arguments. Note: arguments must be ordered as documented.
2527
	 *
2528
	 *     @type int    $blog_id (unused)
2529
	 *     @type string $username
2530
	 *     @type string $password
2531
	 *     @type array  $fields (optional)
2532
	 * }
2533
	 * @return array|IXR_Error (@see wp_getUser)
2534
	 */
2535
	public function wp_getProfile( $args ) {
2536
		if ( ! $this->minimum_args( $args, 3 ) )
2537
			return $this->error;
2538
2539
		$this->escape( $args );
2540
2541
		$username = $args[1];
2542
		$password = $args[2];
2543
2544 View Code Duplication
		if ( isset( $args[3] ) ) {
2545
			$fields = $args[3];
2546
		} else {
2547
			/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2548
			$fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getProfile' );
2549
		}
2550
2551
		if ( ! $user = $this->login( $username, $password ) )
2552
			return $this->error;
2553
2554
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2555
		do_action( 'xmlrpc_call', 'wp.getProfile' );
2556
2557
		if ( ! current_user_can( 'edit_user', $user->ID ) )
2558
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit your profile.' ) );
2559
2560
		$user_data = get_userdata( $user->ID );
2561
2562
		return $this->_prepare_user( $user_data, $fields );
2563
	}
2564
2565
	/**
2566
	 * Edit user's profile.
2567
	 *
2568
	 * @uses wp_update_user()
2569
	 *
2570
	 * @param array  $args {
2571
	 *     Method arguments. Note: arguments must be ordered as documented.
2572
	 *
2573
	 *     @type int    $blog_id (unused)
2574
	 *     @type string $username
2575
	 *     @type string $password
2576
	 *     @type array  $content_struct It can optionally contain:
2577
	 *      - 'first_name'
2578
	 *      - 'last_name'
2579
	 *      - 'website'
2580
	 *      - 'display_name'
2581
	 *      - 'nickname'
2582
	 *      - 'nicename'
2583
	 *      - 'bio'
2584
	 * }
2585
	 * @return true|IXR_Error True, on success.
2586
	 */
2587
	public function wp_editProfile( $args ) {
2588
		if ( ! $this->minimum_args( $args, 4 ) )
2589
			return $this->error;
2590
2591
		$this->escape( $args );
2592
2593
		$username       = $args[1];
2594
		$password       = $args[2];
2595
		$content_struct = $args[3];
2596
2597
		if ( ! $user = $this->login( $username, $password ) )
2598
			return $this->error;
2599
2600
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2601
		do_action( 'xmlrpc_call', 'wp.editProfile' );
2602
2603
		if ( ! current_user_can( 'edit_user', $user->ID ) )
2604
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit your profile.' ) );
2605
2606
		// holds data of the user
2607
		$user_data = array();
2608
		$user_data['ID'] = $user->ID;
2609
2610
		// only set the user details if it was given
2611
		if ( isset( $content_struct['first_name'] ) )
2612
			$user_data['first_name'] = $content_struct['first_name'];
2613
2614
		if ( isset( $content_struct['last_name'] ) )
2615
			$user_data['last_name'] = $content_struct['last_name'];
2616
2617
		if ( isset( $content_struct['url'] ) )
2618
			$user_data['user_url'] = $content_struct['url'];
2619
2620
		if ( isset( $content_struct['display_name'] ) )
2621
			$user_data['display_name'] = $content_struct['display_name'];
2622
2623
		if ( isset( $content_struct['nickname'] ) )
2624
			$user_data['nickname'] = $content_struct['nickname'];
2625
2626
		if ( isset( $content_struct['nicename'] ) )
2627
			$user_data['user_nicename'] = $content_struct['nicename'];
2628
2629
		if ( isset( $content_struct['bio'] ) )
2630
			$user_data['description'] = $content_struct['bio'];
2631
2632
		$result = wp_update_user( $user_data );
2633
2634
		if ( is_wp_error( $result ) )
2635
			return new IXR_Error( 500, $result->get_error_message() );
2636
2637
		if ( ! $result )
2638
			return new IXR_Error( 500, __( 'Sorry, the user cannot be updated.' ) );
2639
2640
		return true;
2641
	}
2642
2643
	/**
2644
	 * Retrieve page.
2645
	 *
2646
	 * @since 2.2.0
2647
	 *
2648
	 * @param array  $args {
2649
	 *     Method arguments. Note: arguments must be ordered as documented.
2650
	 *
2651
	 *     @type int    $blog_id (unused)
2652
	 *     @type int    $page_id
2653
	 *     @type string $username
2654
	 *     @type string $password
2655
	 * }
2656
	 * @return array|IXR_Error
2657
	 */
2658
	public function wp_getPage( $args ) {
2659
		$this->escape( $args );
2660
2661
		$page_id  = (int) $args[1];
2662
		$username = $args[2];
2663
		$password = $args[3];
2664
2665
		if ( !$user = $this->login($username, $password) ) {
2666
			return $this->error;
2667
		}
2668
2669
		$page = get_post($page_id);
2670
		if ( ! $page )
2671
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
2672
2673
		if ( !current_user_can( 'edit_page', $page_id ) )
2674
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this page.' ) );
2675
2676
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2677
		do_action( 'xmlrpc_call', 'wp.getPage' );
2678
2679
		// If we found the page then format the data.
2680
		if ( $page->ID && ($page->post_type == 'page') ) {
2681
			return $this->_prepare_page( $page );
2682
		}
2683
		// If the page doesn't exist indicate that.
2684
		else {
2685
			return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
2686
		}
2687
	}
2688
2689
	/**
2690
	 * Retrieve Pages.
2691
	 *
2692
	 * @since 2.2.0
2693
	 *
2694
	 * @param array  $args {
2695
	 *     Method arguments. Note: arguments must be ordered as documented.
2696
	 *
2697
	 *     @type int    $blog_id (unused)
2698
	 *     @type string $username
2699
	 *     @type string $password
2700
	 *     @type int    $num_pages
2701
	 * }
2702
	 * @return array|IXR_Error
2703
	 */
2704
	public function wp_getPages( $args ) {
2705
		$this->escape( $args );
2706
2707
		$username  = $args[1];
2708
		$password  = $args[2];
2709
		$num_pages = isset($args[3]) ? (int) $args[3] : 10;
2710
2711
		if ( !$user = $this->login($username, $password) )
2712
			return $this->error;
2713
2714
		if ( !current_user_can( 'edit_pages' ) )
2715
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit pages.' ) );
2716
2717
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2718
		do_action( 'xmlrpc_call', 'wp.getPages' );
2719
2720
		$pages = get_posts( array('post_type' => 'page', 'post_status' => 'any', 'numberposts' => $num_pages) );
2721
		$num_pages = count($pages);
2722
2723
		// If we have pages, put together their info.
2724
		if ( $num_pages >= 1 ) {
2725
			$pages_struct = array();
2726
2727
			foreach ($pages as $page) {
2728
				if ( current_user_can( 'edit_page', $page->ID ) )
2729
					$pages_struct[] = $this->_prepare_page( $page );
2730
			}
2731
2732
			return $pages_struct;
2733
		}
2734
2735
		return array();
2736
	}
2737
2738
	/**
2739
	 * Create new page.
2740
	 *
2741
	 * @since 2.2.0
2742
	 *
2743
	 * @see wp_xmlrpc_server::mw_newPost()
2744
	 *
2745
	 * @param array  $args {
2746
	 *     Method arguments. Note: arguments must be ordered as documented.
2747
	 *
2748
	 *     @type int    $blog_id (unused)
2749
	 *     @type string $username
2750
	 *     @type string $password
2751
	 *     @type array  $content_struct
2752
	 * }
2753
	 * @return int|IXR_Error
2754
	 */
2755
	public function wp_newPage( $args ) {
2756
		// Items not escaped here will be escaped in newPost.
2757
		$username = $this->escape( $args[1] );
2758
		$password = $this->escape( $args[2] );
2759
2760
		if ( !$user = $this->login($username, $password) )
2761
			return $this->error;
2762
2763
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2764
		do_action( 'xmlrpc_call', 'wp.newPage' );
2765
2766
		// Mark this as content for a page.
2767
		$args[3]["post_type"] = 'page';
2768
2769
		// Let mw_newPost do all of the heavy lifting.
2770
		return $this->mw_newPost( $args );
2771
	}
2772
2773
	/**
2774
	 * Delete page.
2775
	 *
2776
	 * @since 2.2.0
2777
	 *
2778
	 * @param array  $args {
2779
	 *     Method arguments. Note: arguments must be ordered as documented.
2780
	 *
2781
	 *     @type int    $blog_id (unused)
2782
	 *     @type string $username
2783
	 *     @type string $password
2784
	 *     @type int    $page_id
2785
	 * }
2786
	 * @return true|IXR_Error True, if success.
2787
	 */
2788 View Code Duplication
	public function wp_deletePage( $args ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2789
		$this->escape( $args );
2790
2791
		$username = $args[1];
2792
		$password = $args[2];
2793
		$page_id  = (int) $args[3];
2794
2795
		if ( !$user = $this->login($username, $password) )
2796
			return $this->error;
2797
2798
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2799
		do_action( 'xmlrpc_call', 'wp.deletePage' );
2800
2801
		// Get the current page based on the page_id and
2802
		// make sure it is a page and not a post.
2803
		$actual_page = get_post($page_id, ARRAY_A);
2804
		if ( !$actual_page || ($actual_page['post_type'] != 'page') )
2805
			return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
2806
2807
		// Make sure the user can delete pages.
2808
		if ( !current_user_can('delete_page', $page_id) )
2809
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this page.' ) );
2810
2811
		// Attempt to delete the page.
2812
		$result = wp_delete_post($page_id);
2813
		if ( !$result )
2814
			return new IXR_Error( 500, __( 'Failed to delete the page.' ) );
2815
2816
		/**
2817
		 * Fires after a page has been successfully deleted via XML-RPC.
2818
		 *
2819
		 * @since 3.4.0
2820
		 *
2821
		 * @param int   $page_id ID of the deleted page.
2822
		 * @param array $args    An array of arguments to delete the page.
2823
		 */
2824
		do_action( 'xmlrpc_call_success_wp_deletePage', $page_id, $args );
2825
2826
		return true;
2827
	}
2828
2829
	/**
2830
	 * Edit page.
2831
	 *
2832
	 * @since 2.2.0
2833
	 *
2834
	 * @param array  $args {
2835
	 *     Method arguments. Note: arguments must be ordered as documented.
2836
	 *
2837
	 *     @type int    $blog_id (unused)
2838
	 *     @type int    $page_id
2839
	 *     @type string $username
2840
	 *     @type string $password
2841
	 *     @type string $content
2842
	 *     @type string $publish
2843
	 * }
2844
	 * @return array|IXR_Error
2845
	 */
2846
	public function wp_editPage( $args ) {
2847
		// Items will be escaped in mw_editPost.
2848
		$page_id  = (int) $args[1];
2849
		$username = $args[2];
2850
		$password = $args[3];
2851
		$content  = $args[4];
2852
		$publish  = $args[5];
2853
2854
		$escaped_username = $this->escape( $username );
2855
		$escaped_password = $this->escape( $password );
2856
2857
		if ( !$user = $this->login( $escaped_username, $escaped_password ) ) {
2858
			return $this->error;
2859
		}
2860
2861
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2862
		do_action( 'xmlrpc_call', 'wp.editPage' );
2863
2864
		// Get the page data and make sure it is a page.
2865
		$actual_page = get_post($page_id, ARRAY_A);
2866
		if ( !$actual_page || ($actual_page['post_type'] != 'page') )
2867
			return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
2868
2869
		// Make sure the user is allowed to edit pages.
2870
		if ( !current_user_can('edit_page', $page_id) )
2871
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this page.' ) );
2872
2873
		// Mark this as content for a page.
2874
		$content['post_type'] = 'page';
2875
2876
		// Arrange args in the way mw_editPost understands.
2877
		$args = array(
2878
			$page_id,
2879
			$username,
2880
			$password,
2881
			$content,
2882
			$publish
2883
		);
2884
2885
		// Let mw_editPost do all of the heavy lifting.
2886
		return $this->mw_editPost( $args );
2887
	}
2888
2889
	/**
2890
	 * Retrieve page list.
2891
	 *
2892
	 * @since 2.2.0
2893
	 *
2894
	 * @param array  $args {
2895
	 *     Method arguments. Note: arguments must be ordered as documented.
2896
	 *
2897
	 *     @type int    $blog_id (unused)
2898
	 *     @type string $username
2899
	 *     @type string $password
2900
	 * }
2901
	 * @return array|IXR_Error
2902
	 */
2903
	public function wp_getPageList( $args ) {
2904
		$this->escape( $args );
2905
2906
		$username = $args[1];
2907
		$password = $args[2];
2908
2909
		if ( !$user = $this->login($username, $password) )
2910
			return $this->error;
2911
2912
		if ( !current_user_can( 'edit_pages' ) )
2913
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit pages.' ) );
2914
2915
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2916
		do_action( 'xmlrpc_call', 'wp.getPageList' );
2917
2918
		// Get list of pages ids and titles
2919
		$page_list = $this->db->get_results("
2920
			SELECT ID page_id,
2921
				post_title page_title,
2922
				post_parent page_parent_id,
2923
				post_date_gmt,
2924
				post_date,
2925
				post_status
2926
			FROM {$this->db->posts}
2927
			WHERE post_type = 'page'
2928
			ORDER BY ID
2929
		");
2930
2931
		// The date needs to be formatted properly.
2932
		$num_pages = count($page_list);
2933
		for ( $i = 0; $i < $num_pages; $i++ ) {
2934
			$page_list[$i]->dateCreated = $this->_convert_date(  $page_list[$i]->post_date );
2935
			$page_list[$i]->date_created_gmt = $this->_convert_date_gmt( $page_list[$i]->post_date_gmt, $page_list[$i]->post_date );
2936
2937
			unset($page_list[$i]->post_date_gmt);
2938
			unset($page_list[$i]->post_date);
2939
			unset($page_list[$i]->post_status);
2940
		}
2941
2942
		return $page_list;
2943
	}
2944
2945
	/**
2946
	 * Retrieve authors list.
2947
	 *
2948
	 * @since 2.2.0
2949
	 *
2950
	 * @param array  $args {
2951
	 *     Method arguments. Note: arguments must be ordered as documented.
2952
	 *
2953
	 *     @type int    $blog_id (unused)
2954
	 *     @type string $username
2955
	 *     @type string $password
2956
	 * }
2957
	 * @return array|IXR_Error
2958
	 */
2959
	public function wp_getAuthors( $args ) {
2960
		$this->escape( $args );
2961
2962
		$username = $args[1];
2963
		$password = $args[2];
2964
2965
		if ( !$user = $this->login($username, $password) )
2966
			return $this->error;
2967
2968
		if ( !current_user_can('edit_posts') )
2969
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
2970
2971
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
2972
		do_action( 'xmlrpc_call', 'wp.getAuthors' );
2973
2974
		$authors = array();
2975
		foreach ( get_users( array( 'fields' => array('ID','user_login','display_name') ) ) as $user ) {
2976
			$authors[] = array(
2977
				'user_id'       => $user->ID,
2978
				'user_login'    => $user->user_login,
2979
				'display_name'  => $user->display_name
2980
			);
2981
		}
2982
2983
		return $authors;
2984
	}
2985
2986
	/**
2987
	 * Get list of all tags
2988
	 *
2989
	 * @since 2.7.0
2990
	 *
2991
	 * @param array  $args {
2992
	 *     Method arguments. Note: arguments must be ordered as documented.
2993
	 *
2994
	 *     @type int    $blog_id (unused)
2995
	 *     @type string $username
2996
	 *     @type string $password
2997
	 * }
2998
	 * @return array|IXR_Error
2999
	 */
3000
	public function wp_getTags( $args ) {
3001
		$this->escape( $args );
3002
3003
		$username = $args[1];
3004
		$password = $args[2];
3005
3006
		if ( !$user = $this->login($username, $password) )
3007
			return $this->error;
3008
3009
		if ( !current_user_can( 'edit_posts' ) )
3010
			return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view tags.' ) );
3011
3012
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3013
		do_action( 'xmlrpc_call', 'wp.getKeywords' );
3014
3015
		$tags = array();
3016
3017
		if ( $all_tags = get_tags() ) {
3018
			foreach ( (array) $all_tags as $tag ) {
3019
				$struct = array();
3020
				$struct['tag_id']			= $tag->term_id;
3021
				$struct['name']				= $tag->name;
3022
				$struct['count']			= $tag->count;
3023
				$struct['slug']				= $tag->slug;
3024
				$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...
3025
				$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...
3026
3027
				$tags[] = $struct;
3028
			}
3029
		}
3030
3031
		return $tags;
3032
	}
3033
3034
	/**
3035
	 * Create new category.
3036
	 *
3037
	 * @since 2.2.0
3038
	 *
3039
	 * @param array  $args {
3040
	 *     Method arguments. Note: arguments must be ordered as documented.
3041
	 *
3042
	 *     @type int    $blog_id (unused)
3043
	 *     @type string $username
3044
	 *     @type string $password
3045
	 *     @type array  $category
3046
	 * }
3047
	 * @return int|IXR_Error Category ID.
3048
	 */
3049
	public function wp_newCategory( $args ) {
3050
		$this->escape( $args );
3051
3052
		$username = $args[1];
3053
		$password = $args[2];
3054
		$category = $args[3];
3055
3056
		if ( !$user = $this->login($username, $password) )
3057
			return $this->error;
3058
3059
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3060
		do_action( 'xmlrpc_call', 'wp.newCategory' );
3061
3062
		// Make sure the user is allowed to add a category.
3063
		if ( !current_user_can('manage_categories') )
3064
			return new IXR_Error(401, __('Sorry, you are not allowed to add a category.'));
3065
3066
		// If no slug was provided make it empty so that
3067
		// WordPress will generate one.
3068
		if ( empty($category['slug']) )
3069
			$category['slug'] = '';
3070
3071
		// If no parent_id was provided make it empty
3072
		// so that it will be a top level page (no parent).
3073
		if ( !isset($category['parent_id']) )
3074
			$category['parent_id'] = '';
3075
3076
		// If no description was provided make it empty.
3077
		if ( empty($category["description"]) )
3078
			$category["description"] = "";
3079
3080
		$new_category = array(
3081
			'cat_name'				=> $category['name'],
3082
			'category_nicename'		=> $category['slug'],
3083
			'category_parent'		=> $category['parent_id'],
3084
			'category_description'	=> $category['description']
3085
		);
3086
3087
		$cat_id = wp_insert_category($new_category, true);
3088
		if ( is_wp_error( $cat_id ) ) {
3089
			if ( 'term_exists' == $cat_id->get_error_code() )
3090
				return (int) $cat_id->get_error_data();
3091
			else
3092
				return new IXR_Error(500, __('Sorry, the new category failed.'));
3093
		} elseif ( ! $cat_id ) {
3094
			return new IXR_Error(500, __('Sorry, the new category failed.'));
3095
		}
3096
3097
		/**
3098
		 * Fires after a new category has been successfully created via XML-RPC.
3099
		 *
3100
		 * @since 3.4.0
3101
		 *
3102
		 * @param int   $cat_id ID of the new category.
3103
		 * @param array $args   An array of new category arguments.
3104
		 */
3105
		do_action( 'xmlrpc_call_success_wp_newCategory', $cat_id, $args );
3106
3107
		return $cat_id;
3108
	}
3109
3110
	/**
3111
	 * Remove category.
3112
	 *
3113
	 * @since 2.5.0
3114
	 *
3115
	 * @param array  $args {
3116
	 *     Method arguments. Note: arguments must be ordered as documented.
3117
	 *
3118
	 *     @type int    $blog_id (unused)
3119
	 *     @type string $username
3120
	 *     @type string $password
3121
	 *     @type int    $category_id
3122
	 * }
3123
	 * @return bool|IXR_Error See wp_delete_term() for return info.
3124
	 */
3125
	public function wp_deleteCategory( $args ) {
3126
		$this->escape( $args );
3127
3128
		$username    = $args[1];
3129
		$password    = $args[2];
3130
		$category_id = (int) $args[3];
3131
3132
		if ( !$user = $this->login($username, $password) )
3133
			return $this->error;
3134
3135
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3136
		do_action( 'xmlrpc_call', 'wp.deleteCategory' );
3137
3138
		if ( !current_user_can('manage_categories') )
3139
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete a category.' ) );
3140
3141
		$status = wp_delete_term( $category_id, 'category' );
3142
3143
		if ( true == $status ) {
3144
			/**
3145
			 * Fires after a category has been successfully deleted via XML-RPC.
3146
			 *
3147
			 * @since 3.4.0
3148
			 *
3149
			 * @param int   $category_id ID of the deleted category.
3150
			 * @param array $args        An array of arguments to delete the category.
3151
			 */
3152
			do_action( 'xmlrpc_call_success_wp_deleteCategory', $category_id, $args );
3153
		}
3154
3155
		return $status;
3156
	}
3157
3158
	/**
3159
	 * Retrieve category list.
3160
	 *
3161
	 * @since 2.2.0
3162
	 *
3163
	 * @param array  $args {
3164
	 *     Method arguments. Note: arguments must be ordered as documented.
3165
	 *
3166
	 *     @type int    $blog_id (unused)
3167
	 *     @type string $username
3168
	 *     @type string $password
3169
	 *     @type array  $category
3170
	 *     @type int    $max_results
3171
	 * }
3172
	 * @return array|IXR_Error
3173
	 */
3174
	public function wp_suggestCategories( $args ) {
3175
		$this->escape( $args );
3176
3177
		$username    = $args[1];
3178
		$password    = $args[2];
3179
		$category    = $args[3];
3180
		$max_results = (int) $args[4];
3181
3182
		if ( !$user = $this->login($username, $password) )
3183
			return $this->error;
3184
3185
		if ( !current_user_can( 'edit_posts' ) )
3186
			return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
3187
3188
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3189
		do_action( 'xmlrpc_call', 'wp.suggestCategories' );
3190
3191
		$category_suggestions = array();
3192
		$args = array('get' => 'all', 'number' => $max_results, 'name__like' => $category);
3193
		foreach ( (array) get_categories($args) as $cat ) {
3194
			$category_suggestions[] = array(
3195
				'category_id'	=> $cat->term_id,
3196
				'category_name'	=> $cat->name
3197
			);
3198
		}
3199
3200
		return $category_suggestions;
3201
	}
3202
3203
	/**
3204
	 * Retrieve comment.
3205
	 *
3206
	 * @since 2.7.0
3207
	 *
3208
	 * @param array  $args {
3209
	 *     Method arguments. Note: arguments must be ordered as documented.
3210
	 *
3211
	 *     @type int    $blog_id (unused)
3212
	 *     @type string $username
3213
	 *     @type string $password
3214
	 *     @type int    $comment_id
3215
	 * }
3216
	 * @return array|IXR_Error
3217
	 */
3218 View Code Duplication
	public function wp_getComment($args) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3219
		$this->escape($args);
3220
3221
		$username	= $args[1];
3222
		$password	= $args[2];
3223
		$comment_id	= (int) $args[3];
3224
3225
		if ( ! $user = $this->login( $username, $password ) ) {
3226
			return $this->error;
3227
		}
3228
3229
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3230
		do_action( 'xmlrpc_call', 'wp.getComment' );
3231
3232
		if ( ! $comment = get_comment( $comment_id ) ) {
3233
			return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
3234
		}
3235
3236
		if ( ! current_user_can( 'edit_comment', $comment_id ) ) {
3237
			return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) );
3238
		}
3239
3240
		return $this->_prepare_comment( $comment );
3241
	}
3242
3243
	/**
3244
	 * Retrieve comments.
3245
	 *
3246
	 * Besides the common blog_id (unused), username, and password arguments, it takes a filter
3247
	 * array as last argument.
3248
	 *
3249
	 * Accepted 'filter' keys are 'status', 'post_id', 'offset', and 'number'.
3250
	 *
3251
	 * The defaults are as follows:
3252
	 * - 'status' - Default is ''. Filter by status (e.g., 'approve', 'hold')
3253
	 * - 'post_id' - Default is ''. The post where the comment is posted. Empty string shows all comments.
3254
	 * - 'number' - Default is 10. Total number of media items to retrieve.
3255
	 * - 'offset' - Default is 0. See WP_Query::query() for more.
3256
	 *
3257
	 * @since 2.7.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  $struct
3266
	 * }
3267
	 * @return array|IXR_Error Contains a collection of comments. See wp_xmlrpc_server::wp_getComment() for a description of each item contents
3268
	 */
3269
	public function wp_getComments( $args ) {
3270
		$this->escape( $args );
3271
3272
		$username = $args[1];
3273
		$password = $args[2];
3274
		$struct	  = isset( $args[3] ) ? $args[3] : array();
3275
3276
		if ( ! $user = $this->login( $username, $password ) ) {
3277
			return $this->error;
3278
		}
3279
3280
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3281
		do_action( 'xmlrpc_call', 'wp.getComments' );
3282
3283
		if ( isset( $struct['status'] ) ) {
3284
			$status = $struct['status'];
3285
		} else {
3286
			$status = '';
3287
		}
3288
3289
		if ( ! current_user_can( 'moderate_comments' ) && 'approve' !== $status ) {
3290
			return new IXR_Error( 401, __( 'Invalid comment status.' ) );
3291
		}
3292
3293
		$post_id = '';
3294
		if ( isset( $struct['post_id'] ) ) {
3295
			$post_id = absint( $struct['post_id'] );
3296
		}
3297
3298
		$post_type = '';
3299 View Code Duplication
		if ( isset( $struct['post_type'] ) ) {
3300
			$post_type_object = get_post_type_object( $struct['post_type'] );
3301
			if ( ! $post_type_object || ! post_type_supports( $post_type_object->name, 'comments' ) ) {
3302
				return new IXR_Error( 404, __( 'Invalid post type.' ) );
3303
			}
3304
			$post_type = $struct['post_type'];
3305
		}
3306
3307
		$offset = 0;
3308
		if ( isset( $struct['offset'] ) ) {
3309
			$offset = absint( $struct['offset'] );
3310
		}
3311
3312
		$number = 10;
3313
		if ( isset( $struct['number'] ) ) {
3314
			$number = absint( $struct['number'] );
3315
		}
3316
3317
		$comments = get_comments( array(
3318
			'status' => $status,
3319
			'post_id' => $post_id,
3320
			'offset' => $offset,
3321
			'number' => $number,
3322
			'post_type' => $post_type,
3323
		) );
3324
3325
		$comments_struct = array();
3326
		if ( is_array( $comments ) ) {
3327
			foreach ( $comments as $comment ) {
3328
				$comments_struct[] = $this->_prepare_comment( $comment );
3329
			}
3330
		}
3331
3332
		return $comments_struct;
3333
	}
3334
3335
	/**
3336
	 * Delete a comment.
3337
	 *
3338
	 * By default, the comment will be moved to the trash instead of deleted.
3339
	 * See wp_delete_comment() for more information on this behavior.
3340
	 *
3341
	 * @since 2.7.0
3342
	 *
3343
	 * @param array  $args {
3344
	 *     Method arguments. Note: arguments must be ordered as documented.
3345
	 *
3346
	 *     @type int    $blog_id (unused)
3347
	 *     @type string $username
3348
	 *     @type string $password
3349
	 *     @type int    $comment_ID
3350
	 * }
3351
	 * @return bool|IXR_Error See wp_delete_comment().
3352
	 */
3353
	public function wp_deleteComment( $args ) {
3354
		$this->escape($args);
3355
3356
		$username	= $args[1];
3357
		$password	= $args[2];
3358
		$comment_ID	= (int) $args[3];
3359
3360
		if ( ! $user = $this->login( $username, $password ) ) {
3361
			return $this->error;
3362
		}
3363
3364
		if ( ! get_comment( $comment_ID ) ) {
3365
			return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
3366
		}
3367
3368
		if ( !current_user_can( 'edit_comment', $comment_ID ) ) {
3369
			return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) );
3370
		}
3371
3372
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3373
		do_action( 'xmlrpc_call', 'wp.deleteComment' );
3374
3375
		$status = wp_delete_comment( $comment_ID );
3376
3377
		if ( $status ) {
3378
			/**
3379
			 * Fires after a comment has been successfully deleted via XML-RPC.
3380
			 *
3381
			 * @since 3.4.0
3382
			 *
3383
			 * @param int   $comment_ID ID of the deleted comment.
3384
			 * @param array $args       An array of arguments to delete the comment.
3385
			 */
3386
			do_action( 'xmlrpc_call_success_wp_deleteComment', $comment_ID, $args );
3387
		}
3388
3389
		return $status;
3390
	}
3391
3392
	/**
3393
	 * Edit comment.
3394
	 *
3395
	 * Besides the common blog_id (unused), username, and password arguments, it takes a
3396
	 * comment_id integer and a content_struct array as last argument.
3397
	 *
3398
	 * The allowed keys in the content_struct array are:
3399
	 *  - 'author'
3400
	 *  - 'author_url'
3401
	 *  - 'author_email'
3402
	 *  - 'content'
3403
	 *  - 'date_created_gmt'
3404
	 *  - 'status'. Common statuses are 'approve', 'hold', 'spam'. See get_comment_statuses() for more details
3405
	 *
3406
	 * @since 2.7.0
3407
	 *
3408
	 * @param array  $args {
3409
	 *     Method arguments. Note: arguments must be ordered as documented.
3410
	 *
3411
	 *     @type int    $blog_id (unused)
3412
	 *     @type string $username
3413
	 *     @type string $password
3414
	 *     @type int    $comment_ID
3415
	 *     @type array  $content_struct
3416
	 * }
3417
	 * @return true|IXR_Error True, on success.
3418
	 */
3419
	public function wp_editComment( $args ) {
3420
		$this->escape( $args );
3421
3422
		$username	= $args[1];
3423
		$password	= $args[2];
3424
		$comment_ID	= (int) $args[3];
3425
		$content_struct = $args[4];
3426
3427
		if ( !$user = $this->login( $username, $password ) ) {
3428
			return $this->error;
3429
		}
3430
3431
		if ( ! get_comment( $comment_ID ) ) {
3432
			return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
3433
		}
3434
3435
		if ( ! current_user_can( 'edit_comment', $comment_ID ) ) {
3436
			return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) );
3437
		}
3438
3439
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3440
		do_action( 'xmlrpc_call', 'wp.editComment' );
3441
3442 View Code Duplication
		if ( isset($content_struct['status']) ) {
3443
			$statuses = get_comment_statuses();
3444
			$statuses = array_keys($statuses);
3445
3446
			if ( ! in_array($content_struct['status'], $statuses) )
3447
				return new IXR_Error( 401, __( 'Invalid comment status.' ) );
3448
			$comment_approved = $content_struct['status'];
3449
		}
3450
3451
		// Do some timestamp voodoo
3452
		if ( !empty( $content_struct['date_created_gmt'] ) ) {
3453
			// We know this is supposed to be GMT, so we're going to slap that Z on there by force
3454
			$dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
3455
			$comment_date = get_date_from_gmt(iso8601_to_datetime($dateCreated));
3456
			$comment_date_gmt = iso8601_to_datetime($dateCreated, 'GMT');
3457
		}
3458
3459
		if ( isset($content_struct['content']) )
3460
			$comment_content = $content_struct['content'];
3461
3462
		if ( isset($content_struct['author']) )
3463
			$comment_author = $content_struct['author'];
3464
3465
		if ( isset($content_struct['author_url']) )
3466
			$comment_author_url = $content_struct['author_url'];
3467
3468
		if ( isset($content_struct['author_email']) )
3469
			$comment_author_email = $content_struct['author_email'];
3470
3471
		// We've got all the data -- post it:
3472
		$comment = compact('comment_ID', 'comment_content', 'comment_approved', 'comment_date', 'comment_date_gmt', 'comment_author', 'comment_author_email', 'comment_author_url');
3473
3474
		$result = wp_update_comment($comment);
3475
		if ( is_wp_error( $result ) )
3476
			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...
3477
3478
		if ( !$result )
3479
			return new IXR_Error(500, __('Sorry, the comment could not be edited. Something wrong happened.'));
3480
3481
		/**
3482
		 * Fires after a comment has been successfully updated via XML-RPC.
3483
		 *
3484
		 * @since 3.4.0
3485
		 *
3486
		 * @param int   $comment_ID ID of the updated comment.
3487
		 * @param array $args       An array of arguments to update the comment.
3488
		 */
3489
		do_action( 'xmlrpc_call_success_wp_editComment', $comment_ID, $args );
3490
3491
		return true;
3492
	}
3493
3494
	/**
3495
	 * Create new comment.
3496
	 *
3497
	 * @since 2.7.0
3498
	 *
3499
	 * @param array  $args {
3500
	 *     Method arguments. Note: arguments must be ordered as documented.
3501
	 *
3502
	 *     @type int        $blog_id (unused)
3503
	 *     @type string     $username
3504
	 *     @type string     $password
3505
	 *     @type string|int $post
3506
	 *     @type array      $content_struct
3507
	 * }
3508
	 * @return int|IXR_Error See wp_new_comment().
3509
	 */
3510
	public function wp_newComment($args) {
3511
		$this->escape($args);
3512
3513
		$username       = $args[1];
3514
		$password       = $args[2];
3515
		$post           = $args[3];
3516
		$content_struct = $args[4];
3517
3518
		/**
3519
		 * Filters whether to allow anonymous comments over XML-RPC.
3520
		 *
3521
		 * @since 2.7.0
3522
		 *
3523
		 * @param bool $allow Whether to allow anonymous commenting via XML-RPC.
3524
		 *                    Default false.
3525
		 */
3526
		$allow_anon = apply_filters( 'xmlrpc_allow_anonymous_comments', false );
3527
3528
		$user = $this->login($username, $password);
3529
3530
		if ( !$user ) {
3531
			$logged_in = false;
3532
			if ( $allow_anon && get_option('comment_registration') ) {
3533
				return new IXR_Error( 403, __( 'You must be registered to comment' ) );
3534
			} elseif ( ! $allow_anon ) {
3535
				return $this->error;
3536
			}
3537
		} else {
3538
			$logged_in = true;
3539
		}
3540
3541
		if ( is_numeric($post) )
3542
			$post_id = absint($post);
3543
		else
3544
			$post_id = url_to_postid($post);
3545
3546
		if ( ! $post_id ) {
3547
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
3548
		}
3549
3550
		if ( ! get_post( $post_id ) ) {
3551
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
3552
		}
3553
3554
		if ( ! comments_open( $post_id ) ) {
3555
			return new IXR_Error( 403, __( 'Sorry, comments are closed for this item.' ) );
3556
		}
3557
3558
		$comment = array();
3559
		$comment['comment_post_ID'] = $post_id;
3560
3561
		if ( $logged_in ) {
3562
			$display_name = $user->display_name;
3563
			$user_email = $user->user_email;
3564
			$user_url = $user->user_url;
3565
3566
			$comment['comment_author'] = $this->escape( $display_name );
3567
			$comment['comment_author_email'] = $this->escape( $user_email );
3568
			$comment['comment_author_url'] = $this->escape( $user_url );
3569
			$comment['user_ID'] = $user->ID;
3570
		} else {
3571
			$comment['comment_author'] = '';
3572
			if ( isset($content_struct['author']) )
3573
				$comment['comment_author'] = $content_struct['author'];
3574
3575
			$comment['comment_author_email'] = '';
3576
			if ( isset($content_struct['author_email']) )
3577
				$comment['comment_author_email'] = $content_struct['author_email'];
3578
3579
			$comment['comment_author_url'] = '';
3580
			if ( isset($content_struct['author_url']) )
3581
				$comment['comment_author_url'] = $content_struct['author_url'];
3582
3583
			$comment['user_ID'] = 0;
3584
3585
			if ( get_option('require_name_email') ) {
3586
				if ( 6 > strlen($comment['comment_author_email']) || '' == $comment['comment_author'] )
3587
					return new IXR_Error( 403, __( 'Comment author name and email are required' ) );
3588
				elseif ( !is_email($comment['comment_author_email']) )
3589
					return new IXR_Error( 403, __( 'A valid email address is required' ) );
3590
			}
3591
		}
3592
3593
		$comment['comment_parent'] = isset($content_struct['comment_parent']) ? absint($content_struct['comment_parent']) : 0;
3594
3595
		$comment['comment_content'] =  isset($content_struct['content']) ? $content_struct['content'] : null;
3596
3597
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3598
		do_action( 'xmlrpc_call', 'wp.newComment' );
3599
3600
		$comment_ID = wp_new_comment( $comment );
3601
3602
		/**
3603
		 * Fires after a new comment has been successfully created via XML-RPC.
3604
		 *
3605
		 * @since 3.4.0
3606
		 *
3607
		 * @param int   $comment_ID ID of the new comment.
3608
		 * @param array $args       An array of new comment arguments.
3609
		 */
3610
		do_action( 'xmlrpc_call_success_wp_newComment', $comment_ID, $args );
3611
3612
		return $comment_ID;
3613
	}
3614
3615
	/**
3616
	 * Retrieve all of the comment status.
3617
	 *
3618
	 * @since 2.7.0
3619
	 *
3620
	 * @param array  $args {
3621
	 *     Method arguments. Note: arguments must be ordered as documented.
3622
	 *
3623
	 *     @type int    $blog_id (unused)
3624
	 *     @type string $username
3625
	 *     @type string $password
3626
	 * }
3627
	 * @return array|IXR_Error
3628
	 */
3629 View Code Duplication
	public function wp_getCommentStatusList( $args ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3630
		$this->escape( $args );
3631
3632
		$username = $args[1];
3633
		$password = $args[2];
3634
3635
		if ( ! $user = $this->login( $username, $password ) ) {
3636
			return $this->error;
3637
		}
3638
3639
		if ( ! current_user_can( 'publish_posts' ) ) {
3640
			return new IXR_Error( 403, __( 'Sorry, you are not allowed access to details about this site.' ) );
3641
		}
3642
3643
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3644
		do_action( 'xmlrpc_call', 'wp.getCommentStatusList' );
3645
3646
		return get_comment_statuses();
3647
	}
3648
3649
	/**
3650
	 * Retrieve comment count.
3651
	 *
3652
	 * @since 2.5.0
3653
	 *
3654
	 * @param array  $args {
3655
	 *     Method arguments. Note: arguments must be ordered as documented.
3656
	 *
3657
	 *     @type int    $blog_id (unused)
3658
	 *     @type string $username
3659
	 *     @type string $password
3660
	 *     @type int    $post_id
3661
	 * }
3662
	 * @return array|IXR_Error
3663
	 */
3664
	public function wp_getCommentCount( $args ) {
3665
		$this->escape( $args );
3666
3667
		$username	= $args[1];
3668
		$password	= $args[2];
3669
		$post_id	= (int) $args[3];
3670
3671
		if ( ! $user = $this->login( $username, $password ) ) {
3672
			return $this->error;
3673
		}
3674
3675
		$post = get_post( $post_id, ARRAY_A );
3676
		if ( empty( $post['ID'] ) ) {
3677
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
3678
		}
3679
3680
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
3681
			return new IXR_Error( 403, __( 'Sorry, you are not allowed access to details of this post.' ) );
3682
		}
3683
3684
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3685
		do_action( 'xmlrpc_call', 'wp.getCommentCount' );
3686
3687
		$count = wp_count_comments( $post_id );
3688
3689
		return array(
3690
			'approved' => $count->approved,
3691
			'awaiting_moderation' => $count->moderated,
3692
			'spam' => $count->spam,
3693
			'total_comments' => $count->total_comments
3694
		);
3695
	}
3696
3697
	/**
3698
	 * Retrieve post statuses.
3699
	 *
3700
	 * @since 2.5.0
3701
	 *
3702
	 * @param array  $args {
3703
	 *     Method arguments. Note: arguments must be ordered as documented.
3704
	 *
3705
	 *     @type int    $blog_id (unused)
3706
	 *     @type string $username
3707
	 *     @type string $password
3708
	 * }
3709
	 * @return array|IXR_Error
3710
	 */
3711 View Code Duplication
	public function wp_getPostStatusList( $args ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3712
		$this->escape( $args );
3713
3714
		$username = $args[1];
3715
		$password = $args[2];
3716
3717
		if ( !$user = $this->login($username, $password) )
3718
			return $this->error;
3719
3720
		if ( !current_user_can( 'edit_posts' ) )
3721
			return new IXR_Error( 403, __( 'Sorry, you are not allowed access to details about this site.' ) );
3722
3723
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3724
		do_action( 'xmlrpc_call', 'wp.getPostStatusList' );
3725
3726
		return get_post_statuses();
3727
	}
3728
3729
	/**
3730
	 * Retrieve page statuses.
3731
	 *
3732
	 * @since 2.5.0
3733
	 *
3734
	 * @param array  $args {
3735
	 *     Method arguments. Note: arguments must be ordered as documented.
3736
	 *
3737
	 *     @type int    $blog_id (unused)
3738
	 *     @type string $username
3739
	 *     @type string $password
3740
	 * }
3741
	 * @return array|IXR_Error
3742
	 */
3743 View Code Duplication
	public function wp_getPageStatusList( $args ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3744
		$this->escape( $args );
3745
3746
		$username = $args[1];
3747
		$password = $args[2];
3748
3749
		if ( !$user = $this->login($username, $password) )
3750
			return $this->error;
3751
3752
		if ( !current_user_can( 'edit_pages' ) )
3753
			return new IXR_Error( 403, __( 'Sorry, you are not allowed access to details about this site.' ) );
3754
3755
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3756
		do_action( 'xmlrpc_call', 'wp.getPageStatusList' );
3757
3758
		return get_page_statuses();
3759
	}
3760
3761
	/**
3762
	 * Retrieve page templates.
3763
	 *
3764
	 * @since 2.6.0
3765
	 *
3766
	 * @param array  $args {
3767
	 *     Method arguments. Note: arguments must be ordered as documented.
3768
	 *
3769
	 *     @type int    $blog_id (unused)
3770
	 *     @type string $username
3771
	 *     @type string $password
3772
	 * }
3773
	 * @return array|IXR_Error
3774
	 */
3775
	public function wp_getPageTemplates( $args ) {
3776
		$this->escape( $args );
3777
3778
		$username = $args[1];
3779
		$password = $args[2];
3780
3781
		if ( !$user = $this->login($username, $password) )
3782
			return $this->error;
3783
3784
		if ( !current_user_can( 'edit_pages' ) )
3785
			return new IXR_Error( 403, __( 'Sorry, you are not allowed access to details about this site.' ) );
3786
3787
		$templates = get_page_templates();
3788
		$templates['Default'] = 'default';
3789
3790
		return $templates;
3791
	}
3792
3793
	/**
3794
	 * Retrieve blog options.
3795
	 *
3796
	 * @since 2.6.0
3797
	 *
3798
	 * @param array  $args {
3799
	 *     Method arguments. Note: arguments must be ordered as documented.
3800
	 *
3801
	 *     @type int    $blog_id (unused)
3802
	 *     @type string $username
3803
	 *     @type string $password
3804
	 *     @type array  $options
3805
	 * }
3806
	 * @return array|IXR_Error
3807
	 */
3808
	public function wp_getOptions( $args ) {
3809
		$this->escape( $args );
3810
3811
		$username	= $args[1];
3812
		$password	= $args[2];
3813
		$options	= isset( $args[3] ) ? (array) $args[3] : array();
3814
3815
		if ( !$user = $this->login($username, $password) )
3816
			return $this->error;
3817
3818
		// If no specific options where asked for, return all of them
3819
		if ( count( $options ) == 0 )
3820
			$options = array_keys($this->blog_options);
3821
3822
		return $this->_getOptions($options);
3823
	}
3824
3825
	/**
3826
	 * Retrieve blog options value from list.
3827
	 *
3828
	 * @since 2.6.0
3829
	 *
3830
	 * @param array $options Options to retrieve.
3831
	 * @return array
3832
	 */
3833
	public function _getOptions($options) {
3834
		$data = array();
3835
		$can_manage = current_user_can( 'manage_options' );
3836
		foreach ( $options as $option ) {
3837
			if ( array_key_exists( $option, $this->blog_options ) ) {
3838
				$data[$option] = $this->blog_options[$option];
3839
				//Is the value static or dynamic?
3840
				if ( isset( $data[$option]['option'] ) ) {
3841
					$data[$option]['value'] = get_option( $data[$option]['option'] );
3842
					unset($data[$option]['option']);
3843
				}
3844
3845
				if ( ! $can_manage )
3846
					$data[$option]['readonly'] = true;
3847
			}
3848
		}
3849
3850
		return $data;
3851
	}
3852
3853
	/**
3854
	 * Update blog options.
3855
	 *
3856
	 * @since 2.6.0
3857
	 *
3858
	 * @param array  $args {
3859
	 *     Method arguments. Note: arguments must be ordered as documented.
3860
	 *
3861
	 *     @type int    $blog_id (unused)
3862
	 *     @type string $username
3863
	 *     @type string $password
3864
	 *     @type array  $options
3865
	 * }
3866
	 * @return array|IXR_Error
3867
	 */
3868
	public function wp_setOptions( $args ) {
3869
		$this->escape( $args );
3870
3871
		$username	= $args[1];
3872
		$password	= $args[2];
3873
		$options	= (array) $args[3];
3874
3875
		if ( !$user = $this->login($username, $password) )
3876
			return $this->error;
3877
3878
		if ( !current_user_can( 'manage_options' ) )
3879
			return new IXR_Error( 403, __( 'Sorry, you are not allowed to update options.' ) );
3880
3881
		$option_names = array();
3882
		foreach ( $options as $o_name => $o_value ) {
3883
			$option_names[] = $o_name;
3884
			if ( !array_key_exists( $o_name, $this->blog_options ) )
3885
				continue;
3886
3887
			if ( $this->blog_options[$o_name]['readonly'] == true )
3888
				continue;
3889
3890
			update_option( $this->blog_options[$o_name]['option'], wp_unslash( $o_value ) );
3891
		}
3892
3893
		//Now return the updated values
3894
		return $this->_getOptions($option_names);
3895
	}
3896
3897
	/**
3898
	 * Retrieve a media item by ID
3899
	 *
3900
	 * @since 3.1.0
3901
	 *
3902
	 * @param array  $args {
3903
	 *     Method arguments. Note: arguments must be ordered as documented.
3904
	 *
3905
	 *     @type int    $blog_id (unused)
3906
	 *     @type string $username
3907
	 *     @type string $password
3908
	 *     @type int    $attachment_id
3909
	 * }
3910
	 * @return array|IXR_Error Associative array contains:
3911
	 *  - 'date_created_gmt'
3912
	 *  - 'parent'
3913
	 *  - 'link'
3914
	 *  - 'thumbnail'
3915
	 *  - 'title'
3916
	 *  - 'caption'
3917
	 *  - 'description'
3918
	 *  - 'metadata'
3919
	 */
3920 View Code Duplication
	public function wp_getMediaItem( $args ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3921
		$this->escape( $args );
3922
3923
		$username		= $args[1];
3924
		$password		= $args[2];
3925
		$attachment_id	= (int) $args[3];
3926
3927
		if ( !$user = $this->login($username, $password) )
3928
			return $this->error;
3929
3930
		if ( !current_user_can( 'upload_files' ) )
3931
			return new IXR_Error( 403, __( 'Sorry, you are not allowed to upload files.' ) );
3932
3933
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3934
		do_action( 'xmlrpc_call', 'wp.getMediaItem' );
3935
3936
		if ( ! $attachment = get_post($attachment_id) )
3937
			return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
3938
3939
		return $this->_prepare_media_item( $attachment );
3940
	}
3941
3942
	/**
3943
	 * Retrieves a collection of media library items (or attachments)
3944
	 *
3945
	 * Besides the common blog_id (unused), username, and password arguments, it takes a filter
3946
	 * array as last argument.
3947
	 *
3948
	 * Accepted 'filter' keys are 'parent_id', 'mime_type', 'offset', and 'number'.
3949
	 *
3950
	 * The defaults are as follows:
3951
	 * - 'number' - Default is 5. Total number of media items to retrieve.
3952
	 * - 'offset' - Default is 0. See WP_Query::query() for more.
3953
	 * - 'parent_id' - Default is ''. The post where the media item is attached. Empty string shows all media items. 0 shows unattached media items.
3954
	 * - 'mime_type' - Default is ''. Filter by mime type (e.g., 'image/jpeg', 'application/pdf')
3955
	 *
3956
	 * @since 3.1.0
3957
	 *
3958
	 * @param array  $args {
3959
	 *     Method arguments. Note: arguments must be ordered as documented.
3960
	 *
3961
	 *     @type int    $blog_id (unused)
3962
	 *     @type string $username
3963
	 *     @type string $password
3964
	 *     @type array  $struct
3965
	 * }
3966
	 * @return array|IXR_Error Contains a collection of media items. See wp_xmlrpc_server::wp_getMediaItem() for a description of each item contents
3967
	 */
3968
	public function wp_getMediaLibrary($args) {
3969
		$this->escape($args);
3970
3971
		$username	= $args[1];
3972
		$password	= $args[2];
3973
		$struct		= isset( $args[3] ) ? $args[3] : array() ;
3974
3975
		if ( !$user = $this->login($username, $password) )
3976
			return $this->error;
3977
3978
		if ( !current_user_can( 'upload_files' ) )
3979
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to upload files.' ) );
3980
3981
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
3982
		do_action( 'xmlrpc_call', 'wp.getMediaLibrary' );
3983
3984
		$parent_id = ( isset($struct['parent_id']) ) ? absint($struct['parent_id']) : '' ;
3985
		$mime_type = ( isset($struct['mime_type']) ) ? $struct['mime_type'] : '' ;
3986
		$offset = ( isset($struct['offset']) ) ? absint($struct['offset']) : 0 ;
3987
		$number = ( isset($struct['number']) ) ? absint($struct['number']) : -1 ;
3988
3989
		$attachments = get_posts( array('post_type' => 'attachment', 'post_parent' => $parent_id, 'offset' => $offset, 'numberposts' => $number, 'post_mime_type' => $mime_type ) );
3990
3991
		$attachments_struct = array();
3992
3993
		foreach ($attachments as $attachment )
3994
			$attachments_struct[] = $this->_prepare_media_item( $attachment );
3995
3996
		return $attachments_struct;
3997
	}
3998
3999
	/**
4000
	 * Retrieves a list of post formats used by the site.
4001
	 *
4002
	 * @since 3.1.0
4003
	 *
4004
	 * @param array  $args {
4005
	 *     Method arguments. Note: arguments must be ordered as documented.
4006
	 *
4007
	 *     @type int    $blog_id (unused)
4008
	 *     @type string $username
4009
	 *     @type string $password
4010
	 * }
4011
	 * @return array|IXR_Error List of post formats, otherwise IXR_Error object.
4012
	 */
4013
	public function wp_getPostFormats( $args ) {
4014
		$this->escape( $args );
4015
4016
		$username = $args[1];
4017
		$password = $args[2];
4018
4019
		if ( !$user = $this->login( $username, $password ) )
4020
			return $this->error;
4021
4022
		if ( !current_user_can( 'edit_posts' ) )
4023
			return new IXR_Error( 403, __( 'Sorry, you are not allowed access to details about this site.' ) );
4024
4025
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4026
		do_action( 'xmlrpc_call', 'wp.getPostFormats' );
4027
4028
		$formats = get_post_format_strings();
4029
4030
		// find out if they want a list of currently supports formats
4031
		if ( isset( $args[3] ) && is_array( $args[3] ) ) {
4032
			if ( $args[3]['show-supported'] ) {
4033
				if ( current_theme_supports( 'post-formats' ) ) {
4034
					$supported = get_theme_support( 'post-formats' );
4035
4036
					$data = array();
4037
					$data['all'] = $formats;
4038
					$data['supported'] = $supported[0];
4039
4040
					$formats = $data;
4041
				}
4042
			}
4043
		}
4044
4045
		return $formats;
4046
	}
4047
4048
	/**
4049
	 * Retrieves a post type
4050
	 *
4051
	 * @since 3.4.0
4052
	 *
4053
	 * @see get_post_type_object()
4054
	 *
4055
	 * @param array  $args {
4056
	 *     Method arguments. Note: arguments must be ordered as documented.
4057
	 *
4058
	 *     @type int    $blog_id (unused)
4059
	 *     @type string $username
4060
	 *     @type string $password
4061
	 *     @type string $post_type_name
4062
	 *     @type array  $fields (optional)
4063
	 * }
4064
	 * @return array|IXR_Error Array contains:
4065
	 *  - 'labels'
4066
	 *  - 'description'
4067
	 *  - 'capability_type'
4068
	 *  - 'cap'
4069
	 *  - 'map_meta_cap'
4070
	 *  - 'hierarchical'
4071
	 *  - 'menu_position'
4072
	 *  - 'taxonomies'
4073
	 *  - 'supports'
4074
	 */
4075 View Code Duplication
	public function wp_getPostType( $args ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4076
		if ( ! $this->minimum_args( $args, 4 ) )
4077
			return $this->error;
4078
4079
		$this->escape( $args );
4080
4081
		$username       = $args[1];
4082
		$password       = $args[2];
4083
		$post_type_name = $args[3];
4084
4085
		if ( isset( $args[4] ) ) {
4086
			$fields = $args[4];
4087
		} else {
4088
			/**
4089
			 * Filters the default query fields used by the given XML-RPC method.
4090
			 *
4091
			 * @since 3.4.0
4092
			 *
4093
			 * @param array  $fields An array of post type query fields for the given method.
4094
			 * @param string $method The method name.
4095
			 */
4096
			$fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostType' );
4097
		}
4098
4099
		if ( !$user = $this->login( $username, $password ) )
4100
			return $this->error;
4101
4102
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4103
		do_action( 'xmlrpc_call', 'wp.getPostType' );
4104
4105
		if ( ! post_type_exists( $post_type_name ) )
4106
			return new IXR_Error( 403, __( 'Invalid post type.' ) );
4107
4108
		$post_type = get_post_type_object( $post_type_name );
4109
4110
		if ( ! current_user_can( $post_type->cap->edit_posts ) )
4111
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post type.' ) );
4112
4113
		return $this->_prepare_post_type( $post_type, $fields );
4114
	}
4115
4116
	/**
4117
	 * Retrieves a post types
4118
	 *
4119
	 * @since 3.4.0
4120
	 *
4121
	 * @see get_post_types()
4122
	 *
4123
	 * @param array  $args {
4124
	 *     Method arguments. Note: arguments must be ordered as documented.
4125
	 *
4126
	 *     @type int    $blog_id (unused)
4127
	 *     @type string $username
4128
	 *     @type string $password
4129
	 *     @type array  $filter (optional)
4130
	 *     @type array  $fields (optional)
4131
	 * }
4132
	 * @return array|IXR_Error
4133
	 */
4134 View Code Duplication
	public function wp_getPostTypes( $args ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4135
		if ( ! $this->minimum_args( $args, 3 ) )
4136
			return $this->error;
4137
4138
		$this->escape( $args );
4139
4140
		$username = $args[1];
4141
		$password = $args[2];
4142
		$filter   = isset( $args[3] ) ? $args[3] : array( 'public' => true );
4143
4144
		if ( isset( $args[4] ) ) {
4145
			$fields = $args[4];
4146
		} else {
4147
			/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4148
			$fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostTypes' );
4149
		}
4150
4151
		if ( ! $user = $this->login( $username, $password ) )
4152
			return $this->error;
4153
4154
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4155
		do_action( 'xmlrpc_call', 'wp.getPostTypes' );
4156
4157
		$post_types = get_post_types( $filter, 'objects' );
4158
4159
		$struct = array();
4160
4161
		foreach ( $post_types as $post_type ) {
4162
			if ( ! current_user_can( $post_type->cap->edit_posts ) )
4163
				continue;
4164
4165
			$struct[$post_type->name] = $this->_prepare_post_type( $post_type, $fields );
4166
		}
4167
4168
		return $struct;
4169
	}
4170
4171
	/**
4172
	 * Retrieve revisions for a specific post.
4173
	 *
4174
	 * @since 3.5.0
4175
	 *
4176
	 * The optional $fields parameter specifies what fields will be included
4177
	 * in the response array.
4178
	 *
4179
	 * @uses wp_get_post_revisions()
4180
	 * @see wp_getPost() for more on $fields
4181
	 *
4182
	 * @param array  $args {
4183
	 *     Method arguments. Note: arguments must be ordered as documented.
4184
	 *
4185
	 *     @type int    $blog_id (unused)
4186
	 *     @type string $username
4187
	 *     @type string $password
4188
	 *     @type int    $post_id
4189
	 *     @type array  $fields (optional)
4190
	 * }
4191
	 * @return array|IXR_Error contains a collection of posts.
4192
	 */
4193
	public function wp_getRevisions( $args ) {
4194
		if ( ! $this->minimum_args( $args, 4 ) )
4195
			return $this->error;
4196
4197
		$this->escape( $args );
4198
4199
		$username = $args[1];
4200
		$password = $args[2];
4201
		$post_id  = (int) $args[3];
4202
4203
		if ( isset( $args[4] ) ) {
4204
			$fields = $args[4];
4205
		} else {
4206
			/**
4207
			 * Filters the default revision query fields used by the given XML-RPC method.
4208
			 *
4209
			 * @since 3.5.0
4210
			 *
4211
			 * @param array  $field  An array of revision query fields.
4212
			 * @param string $method The method name.
4213
			 */
4214
			$fields = apply_filters( 'xmlrpc_default_revision_fields', array( 'post_date', 'post_date_gmt' ), 'wp.getRevisions' );
4215
		}
4216
4217
		if ( ! $user = $this->login( $username, $password ) )
4218
			return $this->error;
4219
4220
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4221
		do_action( 'xmlrpc_call', 'wp.getRevisions' );
4222
4223
		if ( ! $post = get_post( $post_id ) )
4224
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4225
4226
		if ( ! current_user_can( 'edit_post', $post_id ) )
4227
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
4228
4229
		// Check if revisions are enabled.
4230
		if ( ! wp_revisions_enabled( $post ) )
0 ignored issues
show
It seems like $post defined by get_post($post_id) on line 4223 can also be of type array; however, wp_revisions_enabled() does only seem to accept object<WP_Post>, 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...
4231
			return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) );
4232
4233
		$revisions = wp_get_post_revisions( $post_id );
4234
4235
		if ( ! $revisions )
4236
			return array();
4237
4238
		$struct = array();
4239
4240
		foreach ( $revisions as $revision ) {
4241
			if ( ! current_user_can( 'read_post', $revision->ID ) )
4242
				continue;
4243
4244
			// Skip autosaves
4245
			if ( wp_is_post_autosave( $revision ) )
4246
				continue;
4247
4248
			$struct[] = $this->_prepare_post( get_object_vars( $revision ), $fields );
4249
		}
4250
4251
		return $struct;
4252
	}
4253
4254
	/**
4255
	 * Restore a post revision
4256
	 *
4257
	 * @since 3.5.0
4258
	 *
4259
	 * @uses wp_restore_post_revision()
4260
	 *
4261
	 * @param array  $args {
4262
	 *     Method arguments. Note: arguments must be ordered as documented.
4263
	 *
4264
	 *     @type int    $blog_id (unused)
4265
	 *     @type string $username
4266
	 *     @type string $password
4267
	 *     @type int    $revision_id
4268
	 * }
4269
	 * @return bool|IXR_Error false if there was an error restoring, true if success.
4270
	 */
4271
	public function wp_restoreRevision( $args ) {
4272
		if ( ! $this->minimum_args( $args, 3 ) )
4273
			return $this->error;
4274
4275
		$this->escape( $args );
4276
4277
		$username    = $args[1];
4278
		$password    = $args[2];
4279
		$revision_id = (int) $args[3];
4280
4281
		if ( ! $user = $this->login( $username, $password ) )
4282
			return $this->error;
4283
4284
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4285
		do_action( 'xmlrpc_call', 'wp.restoreRevision' );
4286
4287
		if ( ! $revision = wp_get_post_revision( $revision_id ) )
4288
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4289
4290
		if ( wp_is_post_autosave( $revision ) )
0 ignored issues
show
It seems like $revision defined by wp_get_post_revision($revision_id) on line 4287 can also be of type array; however, wp_is_post_autosave() does only seem to accept integer|object<WP_Post>, 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...
4291
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4292
4293
		if ( ! $post = get_post( $revision->post_parent ) )
4294
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4295
4296
		if ( ! current_user_can( 'edit_post', $revision->post_parent ) )
4297
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
4298
4299
		// Check if revisions are disabled.
4300
		if ( ! wp_revisions_enabled( $post ) )
0 ignored issues
show
It seems like $post defined by get_post($revision->post_parent) on line 4293 can also be of type array; however, wp_revisions_enabled() does only seem to accept object<WP_Post>, 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...
4301
			return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) );
4302
4303
		$post = wp_restore_post_revision( $revision_id );
4304
4305
		return (bool) $post;
4306
	}
4307
4308
	/* Blogger API functions.
4309
	 * specs on http://plant.blogger.com/api and https://groups.yahoo.com/group/bloggerDev/
4310
	 */
4311
4312
	/**
4313
	 * Retrieve blogs that user owns.
4314
	 *
4315
	 * Will make more sense once we support multiple blogs.
4316
	 *
4317
	 * @since 1.5.0
4318
	 *
4319
	 * @param array  $args {
4320
	 *     Method arguments. Note: arguments must be ordered as documented.
4321
	 *
4322
	 *     @type int    $blog_id (unused)
4323
	 *     @type string $username
4324
	 *     @type string $password
4325
	 * }
4326
	 * @return array|IXR_Error
4327
	 */
4328
	public function blogger_getUsersBlogs($args) {
4329
		if ( is_multisite() )
4330
			return $this->_multisite_getUsersBlogs($args);
4331
4332
		$this->escape($args);
4333
4334
		$username = $args[1];
4335
		$password = $args[2];
4336
4337
		if ( !$user = $this->login($username, $password) )
4338
			return $this->error;
4339
4340
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4341
		do_action( 'xmlrpc_call', 'blogger.getUsersBlogs' );
4342
4343
		$is_admin = current_user_can('manage_options');
4344
4345
		$struct = array(
4346
			'isAdmin'  => $is_admin,
4347
			'url'      => get_option('home') . '/',
4348
			'blogid'   => '1',
4349
			'blogName' => get_option('blogname'),
4350
			'xmlrpc'   => site_url( 'xmlrpc.php', 'rpc' ),
4351
		);
4352
4353
		return array($struct);
4354
	}
4355
4356
	/**
4357
	 * Private function for retrieving a users blogs for multisite setups
4358
	 *
4359
	 * @since 3.0.0
4360
	 * @access protected
4361
	 *
4362
	 * @param array $args {
4363
	 *     Method arguments. Note: arguments must be ordered as documented.
4364
	 *
4365
	 *     @type string $username Username.
4366
	 *     @type string $password Password.
4367
	 * }
4368
	 * @return array|IXR_Error
4369
	 */
4370
	protected function _multisite_getUsersBlogs( $args ) {
4371
		$current_blog = get_blog_details();
4372
4373
		$domain = $current_blog->domain;
4374
		$path = $current_blog->path . 'xmlrpc.php';
4375
4376
		$rpc = new IXR_Client( set_url_scheme( "http://{$domain}{$path}" ) );
4377
		$rpc->query('wp.getUsersBlogs', $args[1], $args[2]);
4378
		$blogs = $rpc->getResponse();
4379
4380
		if ( isset($blogs['faultCode']) )
4381
			return new IXR_Error($blogs['faultCode'], $blogs['faultString']);
4382
4383
		if ( $_SERVER['HTTP_HOST'] == $domain && $_SERVER['REQUEST_URI'] == $path ) {
4384
			return $blogs;
4385
		} else {
4386
			foreach ( (array) $blogs as $blog ) {
4387
				if ( strpos($blog['url'], $_SERVER['HTTP_HOST']) )
4388
					return array($blog);
4389
			}
4390
			return array();
4391
		}
4392
	}
4393
4394
	/**
4395
	 * Retrieve user's data.
4396
	 *
4397
	 * Gives your client some info about you, so you don't have to.
4398
	 *
4399
	 * @since 1.5.0
4400
	 *
4401
	 * @param array  $args {
4402
	 *     Method arguments. Note: arguments must be ordered as documented.
4403
	 *
4404
	 *     @type int    $blog_id (unused)
4405
	 *     @type string $username
4406
	 *     @type string $password
4407
	 * }
4408
	 * @return array|IXR_Error
4409
	 */
4410
	public function blogger_getUserInfo( $args ) {
4411
		$this->escape( $args );
4412
4413
		$username = $args[1];
4414
		$password = $args[2];
4415
4416
		if ( !$user = $this->login($username, $password) )
4417
			return $this->error;
4418
4419
		if ( !current_user_can( 'edit_posts' ) )
4420
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to access user data on this site.' ) );
4421
4422
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4423
		do_action( 'xmlrpc_call', 'blogger.getUserInfo' );
4424
4425
		$struct = array(
4426
			'nickname'  => $user->nickname,
4427
			'userid'    => $user->ID,
4428
			'url'       => $user->user_url,
4429
			'lastname'  => $user->last_name,
4430
			'firstname' => $user->first_name
4431
		);
4432
4433
		return $struct;
4434
	}
4435
4436
	/**
4437
	 * Retrieve post.
4438
	 *
4439
	 * @since 1.5.0
4440
	 *
4441
	 * @param array  $args {
4442
	 *     Method arguments. Note: arguments must be ordered as documented.
4443
	 *
4444
	 *     @type int    $blog_id (unused)
4445
	 *     @type int    $post_ID
4446
	 *     @type string $username
4447
	 *     @type string $password
4448
	 * }
4449
	 * @return array|IXR_Error
4450
	 */
4451
	public function blogger_getPost( $args ) {
4452
		$this->escape( $args );
4453
4454
		$post_ID  = (int) $args[1];
4455
		$username = $args[2];
4456
		$password = $args[3];
4457
4458
		if ( !$user = $this->login($username, $password) )
4459
			return $this->error;
4460
4461
		$post_data = get_post($post_ID, ARRAY_A);
4462
		if ( ! $post_data )
4463
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
4464
4465
		if ( !current_user_can( 'edit_post', $post_ID ) )
4466
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
4467
4468
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4469
		do_action( 'xmlrpc_call', 'blogger.getPost' );
4470
4471
		$categories = implode(',', wp_get_post_categories($post_ID));
4472
4473
		$content  = '<title>'.wp_unslash($post_data['post_title']).'</title>';
4474
		$content .= '<category>'.$categories.'</category>';
4475
		$content .= wp_unslash($post_data['post_content']);
4476
4477
		$struct = array(
4478
			'userid'    => $post_data['post_author'],
4479
			'dateCreated' => $this->_convert_date( $post_data['post_date'] ),
4480
			'content'     => $content,
4481
			'postid'  => (string) $post_data['ID']
4482
		);
4483
4484
		return $struct;
4485
	}
4486
4487
	/**
4488
	 * Retrieve list of recent posts.
4489
	 *
4490
	 * @since 1.5.0
4491
	 *
4492
	 * @param array  $args {
4493
	 *     Method arguments. Note: arguments must be ordered as documented.
4494
	 *
4495
	 *     @type string $appkey (unused)
4496
	 *     @type int    $blog_id (unused)
4497
	 *     @type string $username
4498
	 *     @type string $password
4499
	 *     @type int    $numberposts (optional)
4500
	 * }
4501
	 * @return array|IXR_Error
4502
	 */
4503
	public function blogger_getRecentPosts( $args ) {
4504
4505
		$this->escape($args);
4506
4507
		// $args[0] = appkey - ignored
4508
		$username = $args[2];
4509
		$password = $args[3];
4510 View Code Duplication
		if ( isset( $args[4] ) )
4511
			$query = array( 'numberposts' => absint( $args[4] ) );
4512
		else
4513
			$query = array();
4514
4515
		if ( !$user = $this->login($username, $password) )
4516
			return $this->error;
4517
4518
		if ( ! current_user_can( 'edit_posts' ) )
4519
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
4520
4521
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4522
		do_action( 'xmlrpc_call', 'blogger.getRecentPosts' );
4523
4524
		$posts_list = wp_get_recent_posts( $query );
4525
4526
		if ( !$posts_list ) {
4527
			$this->error = new IXR_Error(500, __('Either there are no posts, or something went wrong.'));
4528
			return $this->error;
4529
		}
4530
4531
		$recent_posts = array();
4532
		foreach ($posts_list as $entry) {
4533
			if ( !current_user_can( 'edit_post', $entry['ID'] ) )
4534
				continue;
4535
4536
			$post_date  = $this->_convert_date( $entry['post_date'] );
4537
			$categories = implode(',', wp_get_post_categories($entry['ID']));
4538
4539
			$content  = '<title>'.wp_unslash($entry['post_title']).'</title>';
4540
			$content .= '<category>'.$categories.'</category>';
4541
			$content .= wp_unslash($entry['post_content']);
4542
4543
			$recent_posts[] = array(
4544
				'userid' => $entry['post_author'],
4545
				'dateCreated' => $post_date,
4546
				'content' => $content,
4547
				'postid' => (string) $entry['ID'],
4548
			);
4549
		}
4550
4551
		return $recent_posts;
4552
	}
4553
4554
	/**
4555
	 * Deprecated.
4556
	 *
4557
	 * @since 1.5.0
4558
	 * @deprecated 3.5.0
4559
	 *
4560
	 * @param array $args Unused.
4561
	 * @return IXR_Error Error object.
4562
	 */
4563
	public function blogger_getTemplate($args) {
4564
		return new IXR_Error( 403, __('Sorry, that file cannot be edited.' ) );
4565
	}
4566
4567
	/**
4568
	 * Deprecated.
4569
	 *
4570
	 * @since 1.5.0
4571
	 * @deprecated 3.5.0
4572
	 *
4573
	 * @param array $args Unused.
4574
	 * @return IXR_Error Error object.
4575
	 */
4576
	public function blogger_setTemplate($args) {
4577
		return new IXR_Error( 403, __('Sorry, that file cannot be edited.' ) );
4578
	}
4579
4580
	/**
4581
	 * Creates new post.
4582
	 *
4583
	 * @since 1.5.0
4584
	 *
4585
	 * @param array $args {
4586
	 *     Method arguments. Note: arguments must be ordered as documented.
4587
	 *
4588
	 *     @type string $appkey (unused)
4589
	 *     @type int    $blog_id (unused)
4590
	 *     @type string $username
4591
	 *     @type string $password
4592
	 *     @type string $content
4593
	 *     @type string $publish
4594
	 * }
4595
	 * @return int|IXR_Error
4596
	 */
4597
	public function blogger_newPost( $args ) {
4598
		$this->escape( $args );
4599
4600
		$username = $args[2];
4601
		$password = $args[3];
4602
		$content  = $args[4];
4603
		$publish  = $args[5];
4604
4605
		if ( !$user = $this->login($username, $password) )
4606
			return $this->error;
4607
4608
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4609
		do_action( 'xmlrpc_call', 'blogger.newPost' );
4610
4611
		$cap = ($publish) ? 'publish_posts' : 'edit_posts';
4612 View Code Duplication
		if ( ! current_user_can( get_post_type_object( 'post' )->cap->create_posts ) || !current_user_can($cap) )
4613
			return new IXR_Error(401, __('Sorry, you are not allowed to post on this site.'));
4614
4615
		$post_status = ($publish) ? 'publish' : 'draft';
4616
4617
		$post_author = $user->ID;
4618
4619
		$post_title = xmlrpc_getposttitle($content);
4620
		$post_category = xmlrpc_getpostcategory($content);
4621
		$post_content = xmlrpc_removepostdata($content);
4622
4623
		$post_date = current_time('mysql');
4624
		$post_date_gmt = current_time('mysql', 1);
4625
4626
		$post_data = compact('post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status');
4627
4628
		$post_ID = wp_insert_post($post_data);
4629
		if ( is_wp_error( $post_ID ) )
4630
			return new IXR_Error(500, $post_ID->get_error_message());
4631
4632
		if ( !$post_ID )
4633
			return new IXR_Error(500, __('Sorry, your entry could not be posted. Something wrong happened.'));
4634
4635
		$this->attach_uploads( $post_ID, $post_content );
4636
4637
		/**
4638
		 * Fires after a new post has been successfully created via the XML-RPC Blogger API.
4639
		 *
4640
		 * @since 3.4.0
4641
		 *
4642
		 * @param int   $post_ID ID of the new post.
4643
		 * @param array $args    An array of new post arguments.
4644
		 */
4645
		do_action( 'xmlrpc_call_success_blogger_newPost', $post_ID, $args );
4646
4647
		return $post_ID;
4648
	}
4649
4650
	/**
4651
	 * Edit a post.
4652
	 *
4653
	 * @since 1.5.0
4654
	 *
4655
	 * @param array  $args {
4656
	 *     Method arguments. Note: arguments must be ordered as documented.
4657
	 *
4658
	 *     @type int    $blog_id (unused)
4659
	 *     @type int    $post_ID
4660
	 *     @type string $username
4661
	 *     @type string $password
4662
	 *     @type string $content
4663
	 *     @type bool   $publish
4664
	 * }
4665
	 * @return true|IXR_Error true when done.
4666
	 */
4667
	public function blogger_editPost( $args ) {
4668
4669
		$this->escape($args);
4670
4671
		$post_ID  = (int) $args[1];
4672
		$username = $args[2];
4673
		$password = $args[3];
4674
		$content  = $args[4];
4675
		$publish  = $args[5];
4676
4677
		if ( ! $user = $this->login( $username, $password ) ) {
4678
			return $this->error;
4679
		}
4680
4681
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4682
		do_action( 'xmlrpc_call', 'blogger.editPost' );
4683
4684
		$actual_post = get_post( $post_ID, ARRAY_A );
4685
4686
		if ( ! $actual_post || $actual_post['post_type'] != 'post' ) {
4687
			return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
4688
		}
4689
4690
		$this->escape($actual_post);
4691
4692
		if ( ! current_user_can( 'edit_post', $post_ID ) ) {
4693
			return new IXR_Error(401, __('Sorry, you are not allowed to edit this post.'));
4694
		}
4695
		if ( 'publish' == $actual_post['post_status'] && ! current_user_can( 'publish_posts' ) ) {
4696
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) );
4697
		}
4698
4699
		$postdata = array();
4700
		$postdata['ID'] = $actual_post['ID'];
4701
		$postdata['post_content'] = xmlrpc_removepostdata( $content );
4702
		$postdata['post_title'] = xmlrpc_getposttitle( $content );
4703
		$postdata['post_category'] = xmlrpc_getpostcategory( $content );
4704
		$postdata['post_status'] = $actual_post['post_status'];
4705
		$postdata['post_excerpt'] = $actual_post['post_excerpt'];
4706
		$postdata['post_status'] = $publish ? 'publish' : 'draft';
4707
4708
		$result = wp_update_post( $postdata );
4709
4710
		if ( ! $result ) {
4711
			return new IXR_Error(500, __('For some strange yet very annoying reason, this post could not be edited.'));
4712
		}
4713
		$this->attach_uploads( $actual_post['ID'], $postdata['post_content'] );
4714
4715
		/**
4716
		 * Fires after a post has been successfully updated via the XML-RPC Blogger API.
4717
		 *
4718
		 * @since 3.4.0
4719
		 *
4720
		 * @param int   $post_ID ID of the updated post.
4721
		 * @param array $args    An array of arguments for the post to edit.
4722
		 */
4723
		do_action( 'xmlrpc_call_success_blogger_editPost', $post_ID, $args );
4724
4725
		return true;
4726
	}
4727
4728
	/**
4729
	 * Remove a post.
4730
	 *
4731
	 * @since 1.5.0
4732
	 *
4733
	 * @param array  $args {
4734
	 *     Method arguments. Note: arguments must be ordered as documented.
4735
	 *
4736
	 *     @type int    $blog_id (unused)
4737
	 *     @type int    $post_ID
4738
	 *     @type string $username
4739
	 *     @type string $password
4740
	 * }
4741
	 * @return true|IXR_Error True when post is deleted.
4742
	 */
4743 View Code Duplication
	public function blogger_deletePost( $args ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4744
		$this->escape( $args );
4745
4746
		$post_ID  = (int) $args[1];
4747
		$username = $args[2];
4748
		$password = $args[3];
4749
4750
		if ( !$user = $this->login($username, $password) )
4751
			return $this->error;
4752
4753
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4754
		do_action( 'xmlrpc_call', 'blogger.deletePost' );
4755
4756
		$actual_post = get_post( $post_ID, ARRAY_A );
4757
4758
		if ( ! $actual_post || $actual_post['post_type'] != 'post' ) {
4759
			return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
4760
		}
4761
4762
		if ( ! current_user_can( 'delete_post', $post_ID ) ) {
4763
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this post.' ) );
4764
		}
4765
4766
		$result = wp_delete_post( $post_ID );
4767
4768
		if ( ! $result ) {
4769
			return new IXR_Error( 500, __( 'The post cannot be deleted.' ) );
4770
		}
4771
4772
		/**
4773
		 * Fires after a post has been successfully deleted via the XML-RPC Blogger API.
4774
		 *
4775
		 * @since 3.4.0
4776
		 *
4777
		 * @param int   $post_ID ID of the deleted post.
4778
		 * @param array $args    An array of arguments to delete the post.
4779
		 */
4780
		do_action( 'xmlrpc_call_success_blogger_deletePost', $post_ID, $args );
4781
4782
		return true;
4783
	}
4784
4785
	/* MetaWeblog API functions
4786
	 * specs on wherever Dave Winer wants them to be
4787
	 */
4788
4789
	/**
4790
	 * Create a new post.
4791
	 *
4792
	 * The 'content_struct' argument must contain:
4793
	 *  - title
4794
	 *  - description
4795
	 *  - mt_excerpt
4796
	 *  - mt_text_more
4797
	 *  - mt_keywords
4798
	 *  - mt_tb_ping_urls
4799
	 *  - categories
4800
	 *
4801
	 * Also, it can optionally contain:
4802
	 *  - wp_slug
4803
	 *  - wp_password
4804
	 *  - wp_page_parent_id
4805
	 *  - wp_page_order
4806
	 *  - wp_author_id
4807
	 *  - post_status | page_status - can be 'draft', 'private', 'publish', or 'pending'
4808
	 *  - mt_allow_comments - can be 'open' or 'closed'
4809
	 *  - mt_allow_pings - can be 'open' or 'closed'
4810
	 *  - date_created_gmt
4811
	 *  - dateCreated
4812
	 *  - wp_post_thumbnail
4813
	 *
4814
	 * @since 1.5.0
4815
	 *
4816
	 * @param array  $args {
4817
	 *     Method arguments. Note: arguments must be ordered as documented.
4818
	 *
4819
	 *     @type int    $blog_id (unused)
4820
	 *     @type string $username
4821
	 *     @type string $password
4822
	 *     @type array  $content_struct
4823
	 *     @type int    $publish
4824
	 * }
4825
	 * @return int|IXR_Error
4826
	 */
4827
	public function mw_newPost($args) {
4828
		$this->escape($args);
4829
4830
		$username       = $args[1];
4831
		$password       = $args[2];
4832
		$content_struct = $args[3];
4833
		$publish        = isset( $args[4] ) ? $args[4] : 0;
4834
4835
		if ( !$user = $this->login($username, $password) )
4836
			return $this->error;
4837
4838
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
4839
		do_action( 'xmlrpc_call', 'metaWeblog.newPost' );
4840
4841
		$page_template = '';
4842
		if ( !empty( $content_struct['post_type'] ) ) {
4843
			if ( $content_struct['post_type'] == 'page' ) {
4844
				if ( $publish )
4845
					$cap  = 'publish_pages';
4846
				elseif ( isset( $content_struct['page_status'] ) && 'publish' == $content_struct['page_status'] )
4847
					$cap  = 'publish_pages';
4848
				else
4849
					$cap = 'edit_pages';
4850
				$error_message = __( 'Sorry, you are not allowed to publish pages on this site.' );
4851
				$post_type = 'page';
4852
				if ( !empty( $content_struct['wp_page_template'] ) )
4853
					$page_template = $content_struct['wp_page_template'];
4854 View Code Duplication
			} elseif ( $content_struct['post_type'] == 'post' ) {
4855
				if ( $publish )
4856
					$cap  = 'publish_posts';
4857
				elseif ( isset( $content_struct['post_status'] ) && 'publish' == $content_struct['post_status'] )
4858
					$cap  = 'publish_posts';
4859
				else
4860
					$cap = 'edit_posts';
4861
				$error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
4862
				$post_type = 'post';
4863
			} else {
4864
				// No other post_type values are allowed here
4865
				return new IXR_Error( 401, __( 'Invalid post type.' ) );
4866
			}
4867 View Code Duplication
		} else {
4868
			if ( $publish )
4869
				$cap  = 'publish_posts';
4870
			elseif ( isset( $content_struct['post_status'] ) && 'publish' == $content_struct['post_status'])
4871
				$cap  = 'publish_posts';
4872
			else
4873
				$cap = 'edit_posts';
4874
			$error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
4875
			$post_type = 'post';
4876
		}
4877
4878 View Code Duplication
		if ( ! current_user_can( get_post_type_object( $post_type )->cap->create_posts ) )
4879
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts on this site.' ) );
4880
		if ( !current_user_can( $cap ) )
4881
			return new IXR_Error( 401, $error_message );
4882
4883
		// Check for a valid post format if one was given
4884 View Code Duplication
		if ( isset( $content_struct['wp_post_format'] ) ) {
4885
			$content_struct['wp_post_format'] = sanitize_key( $content_struct['wp_post_format'] );
4886
			if ( !array_key_exists( $content_struct['wp_post_format'], get_post_format_strings() ) ) {
4887
				return new IXR_Error( 404, __( 'Invalid post format' ) );
4888
			}
4889
		}
4890
4891
		// Let WordPress generate the post_name (slug) unless
4892
		// one has been provided.
4893
		$post_name = "";
4894
		if ( isset($content_struct['wp_slug']) )
4895
			$post_name = $content_struct['wp_slug'];
4896
4897
		// Only use a password if one was given.
4898
		if ( isset($content_struct['wp_password']) )
4899
			$post_password = $content_struct['wp_password'];
4900
4901
		// Only set a post parent if one was provided.
4902
		if ( isset($content_struct['wp_page_parent_id']) )
4903
			$post_parent = $content_struct['wp_page_parent_id'];
4904
4905
		// Only set the menu_order if it was provided.
4906
		if ( isset($content_struct['wp_page_order']) )
4907
			$menu_order = $content_struct['wp_page_order'];
4908
4909
		$post_author = $user->ID;
4910
4911
		// If an author id was provided then use it instead.
4912
		if ( isset( $content_struct['wp_author_id'] ) && ( $user->ID != $content_struct['wp_author_id'] ) ) {
4913 View Code Duplication
			switch ( $post_type ) {
4914
				case "post":
4915
					if ( !current_user_can( 'edit_others_posts' ) )
4916
						return new IXR_Error( 401, __( 'Sorry, you are not allowed to create posts as this user.' ) );
4917
					break;
4918
				case "page":
4919
					if ( !current_user_can( 'edit_others_pages' ) )
4920
						return new IXR_Error( 401, __( 'Sorry, you are not allowed to create pages as this user.' ) );
4921
					break;
4922
				default:
4923
					return new IXR_Error( 401, __( 'Invalid post type.' ) );
4924
			}
4925
			$author = get_userdata( $content_struct['wp_author_id'] );
4926
			if ( ! $author )
4927
				return new IXR_Error( 404, __( 'Invalid author ID.' ) );
4928
			$post_author = $content_struct['wp_author_id'];
4929
		}
4930
4931
		$post_title = isset( $content_struct['title'] ) ? $content_struct['title'] : null;
4932
		$post_content = isset( $content_struct['description'] ) ? $content_struct['description'] : null;
4933
4934
		$post_status = $publish ? 'publish' : 'draft';
4935
4936 View Code Duplication
		if ( isset( $content_struct["{$post_type}_status"] ) ) {
4937
			switch ( $content_struct["{$post_type}_status"] ) {
4938
				case 'draft':
4939
				case 'pending':
4940
				case 'private':
4941
				case 'publish':
4942
					$post_status = $content_struct["{$post_type}_status"];
4943
					break;
4944
				default:
4945
					$post_status = $publish ? 'publish' : 'draft';
4946
					break;
4947
			}
4948
		}
4949
4950
		$post_excerpt = isset($content_struct['mt_excerpt']) ? $content_struct['mt_excerpt'] : null;
4951
		$post_more = isset($content_struct['mt_text_more']) ? $content_struct['mt_text_more'] : null;
4952
4953
		$tags_input = isset($content_struct['mt_keywords']) ? $content_struct['mt_keywords'] : null;
4954
4955 View Code Duplication
		if ( isset($content_struct['mt_allow_comments']) ) {
4956
			if ( !is_numeric($content_struct['mt_allow_comments']) ) {
4957
				switch ( $content_struct['mt_allow_comments'] ) {
4958
					case 'closed':
4959
						$comment_status = 'closed';
4960
						break;
4961
					case 'open':
4962
						$comment_status = 'open';
4963
						break;
4964
					default:
4965
						$comment_status = get_default_comment_status( $post_type );
4966
						break;
4967
				}
4968
			} else {
4969
				switch ( (int) $content_struct['mt_allow_comments'] ) {
4970
					case 0:
4971
					case 2:
4972
						$comment_status = 'closed';
4973
						break;
4974
					case 1:
4975
						$comment_status = 'open';
4976
						break;
4977
					default:
4978
						$comment_status = get_default_comment_status( $post_type );
4979
						break;
4980
				}
4981
			}
4982
		} else {
4983
			$comment_status = get_default_comment_status( $post_type );
4984
		}
4985
4986 View Code Duplication
		if ( isset($content_struct['mt_allow_pings']) ) {
4987
			if ( !is_numeric($content_struct['mt_allow_pings']) ) {
4988
				switch ( $content_struct['mt_allow_pings'] ) {
4989
					case 'closed':
4990
						$ping_status = 'closed';
4991
						break;
4992
					case 'open':
4993
						$ping_status = 'open';
4994
						break;
4995
					default:
4996
						$ping_status = get_default_comment_status( $post_type, 'pingback' );
4997
						break;
4998
				}
4999
			} else {
5000
				switch ( (int) $content_struct['mt_allow_pings'] ) {
5001
					case 0:
5002
						$ping_status = 'closed';
5003
						break;
5004
					case 1:
5005
						$ping_status = 'open';
5006
						break;
5007
					default:
5008
						$ping_status = get_default_comment_status( $post_type, 'pingback' );
5009
						break;
5010
				}
5011
			}
5012
		} else {
5013
			$ping_status = get_default_comment_status( $post_type, 'pingback' );
5014
		}
5015
5016
		if ( $post_more )
5017
			$post_content = $post_content . '<!--more-->' . $post_more;
5018
5019
		$to_ping = null;
5020 View Code Duplication
		if ( isset( $content_struct['mt_tb_ping_urls'] ) ) {
5021
			$to_ping = $content_struct['mt_tb_ping_urls'];
5022
			if ( is_array($to_ping) )
5023
				$to_ping = implode(' ', $to_ping);
5024
		}
5025
5026
		// Do some timestamp voodoo
5027 View Code Duplication
		if ( !empty( $content_struct['date_created_gmt'] ) )
5028
			// We know this is supposed to be GMT, so we're going to slap that Z on there by force
5029
			$dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
5030
		elseif ( !empty( $content_struct['dateCreated']) )
5031
			$dateCreated = $content_struct['dateCreated']->getIso();
5032
5033
		if ( !empty( $dateCreated ) ) {
5034
			$post_date = get_date_from_gmt(iso8601_to_datetime($dateCreated));
5035
			$post_date_gmt = iso8601_to_datetime($dateCreated, 'GMT');
5036
		} else {
5037
			$post_date = '';
5038
			$post_date_gmt = '';
5039
		}
5040
5041
		$post_category = array();
5042 View Code Duplication
		if ( isset( $content_struct['categories'] ) ) {
5043
			$catnames = $content_struct['categories'];
5044
5045
			if ( is_array($catnames) ) {
5046
				foreach ($catnames as $cat) {
5047
					$post_category[] = get_cat_ID($cat);
5048
				}
5049
			}
5050
		}
5051
5052
		$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');
5053
5054
		$post_ID = $postdata['ID'] = get_default_post_to_edit( $post_type, true )->ID;
5055
5056
		// Only posts can be sticky
5057 View Code Duplication
		if ( $post_type == 'post' && isset( $content_struct['sticky'] ) ) {
5058
			$data = $postdata;
5059
			$data['sticky'] = $content_struct['sticky'];
5060
			$error = $this->_toggle_sticky( $data );
5061
			if ( $error ) {
5062
				return $error;
5063
			}
5064
		}
5065
5066
		if ( isset($content_struct['custom_fields']) )
5067
			$this->set_custom_fields($post_ID, $content_struct['custom_fields']);
5068
5069 View Code Duplication
		if ( isset ( $content_struct['wp_post_thumbnail'] ) ) {
5070
			if ( set_post_thumbnail( $post_ID, $content_struct['wp_post_thumbnail'] ) === false )
5071
				return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
5072
5073
			unset( $content_struct['wp_post_thumbnail'] );
5074
		}
5075
5076
		// Handle enclosures
5077
		$thisEnclosure = isset($content_struct['enclosure']) ? $content_struct['enclosure'] : null;
5078
		$this->add_enclosure_if_new($post_ID, $thisEnclosure);
5079
5080
		$this->attach_uploads( $post_ID, $post_content );
5081
5082
		// Handle post formats if assigned, value is validated earlier
5083
		// in this function
5084
		if ( isset( $content_struct['wp_post_format'] ) )
5085
			set_post_format( $post_ID, $content_struct['wp_post_format'] );
5086
5087
		$post_ID = wp_insert_post( $postdata, true );
5088
		if ( is_wp_error( $post_ID ) )
5089
			return new IXR_Error(500, $post_ID->get_error_message());
5090
5091
		if ( !$post_ID )
5092
			return new IXR_Error(500, __('Sorry, your entry could not be posted. Something wrong happened.'));
5093
5094
		/**
5095
		 * Fires after a new post has been successfully created via the XML-RPC MovableType API.
5096
		 *
5097
		 * @since 3.4.0
5098
		 *
5099
		 * @param int   $post_ID ID of the new post.
5100
		 * @param array $args    An array of arguments to create the new post.
5101
		 */
5102
		do_action( 'xmlrpc_call_success_mw_newPost', $post_ID, $args );
5103
5104
		return strval($post_ID);
5105
	}
5106
5107
	/**
5108
	 * Adds an enclosure to a post if it's new.
5109
	 *
5110
	 * @since 2.8.0
5111
	 *
5112
	 * @param integer $post_ID   Post ID.
5113
	 * @param array   $enclosure Enclosure data.
5114
	 */
5115
	public function add_enclosure_if_new( $post_ID, $enclosure ) {
5116
		if ( is_array( $enclosure ) && isset( $enclosure['url'] ) && isset( $enclosure['length'] ) && isset( $enclosure['type'] ) ) {
5117
			$encstring = $enclosure['url'] . "\n" . $enclosure['length'] . "\n" . $enclosure['type'] . "\n";
5118
			$found = false;
5119
			if ( $enclosures = get_post_meta( $post_ID, 'enclosure' ) ) {
5120
				foreach ( $enclosures as $enc ) {
5121
					// This method used to omit the trailing new line. #23219
5122
					if ( rtrim( $enc, "\n" ) == rtrim( $encstring, "\n" ) ) {
5123
						$found = true;
5124
						break;
5125
					}
5126
				}
5127
			}
5128
			if ( ! $found )
5129
				add_post_meta( $post_ID, 'enclosure', $encstring );
5130
		}
5131
	}
5132
5133
	/**
5134
	 * Attach upload to a post.
5135
	 *
5136
	 * @since 2.1.0
5137
	 *
5138
	 * @param int $post_ID Post ID.
5139
	 * @param string $post_content Post Content for attachment.
5140
	 */
5141
	public function attach_uploads( $post_ID, $post_content ) {
5142
		// find any unattached files
5143
		$attachments = $this->db->get_results( "SELECT ID, guid FROM {$this->db->posts} WHERE post_parent = '0' AND post_type = 'attachment'" );
5144
		if ( is_array( $attachments ) ) {
5145
			foreach ( $attachments as $file ) {
5146
				if ( ! empty( $file->guid ) && strpos( $post_content, $file->guid ) !== false ) {
5147
					$this->db->update( $this->db->posts, array( 'post_parent' => $post_ID ), array( 'ID' => $file->ID ) );
5148
				}
5149
			}
5150
		}
5151
	}
5152
5153
	/**
5154
	 * Edit a post.
5155
	 *
5156
	 * @since 1.5.0
5157
	 *
5158
	 * @param array  $args {
5159
	 *     Method arguments. Note: arguments must be ordered as documented.
5160
	 *
5161
	 *     @type int    $blog_id (unused)
5162
	 *     @type string $username
5163
	 *     @type string $password
5164
	 *     @type array  $content_struct
5165
	 *     @type int    $publish
5166
	 * }
5167
	 * @return bool|IXR_Error True on success.
5168
	 */
5169
	public function mw_editPost( $args ) {
5170
		$this->escape( $args );
5171
5172
		$post_ID        = (int) $args[0];
5173
		$username       = $args[1];
5174
		$password       = $args[2];
5175
		$content_struct = $args[3];
5176
		$publish        = isset( $args[4] ) ? $args[4] : 0;
5177
5178
		if ( ! $user = $this->login($username, $password) )
5179
			return $this->error;
5180
5181
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5182
		do_action( 'xmlrpc_call', 'metaWeblog.editPost' );
5183
5184
		$postdata = get_post( $post_ID, ARRAY_A );
5185
5186
		/*
5187
		 * If there is no post data for the give post id, stop now and return an error.
5188
		 * Otherwise a new post will be created (which was the old behavior).
5189
		 */
5190
		if ( ! $postdata || empty( $postdata[ 'ID' ] ) )
5191
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
5192
5193
		if ( ! current_user_can( 'edit_post', $post_ID ) )
5194
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
5195
5196
		// Use wp.editPost to edit post types other than post and page.
5197
		if ( ! in_array( $postdata[ 'post_type' ], array( 'post', 'page' ) ) )
5198
			return new IXR_Error( 401, __( 'Invalid post type.' ) );
5199
5200
		// Thwart attempt to change the post type.
5201
		if ( ! empty( $content_struct[ 'post_type' ] ) && ( $content_struct['post_type'] != $postdata[ 'post_type' ] ) )
5202
			return new IXR_Error( 401, __( 'The post type may not be changed.' ) );
5203
5204
		// Check for a valid post format if one was given
5205 View Code Duplication
		if ( isset( $content_struct['wp_post_format'] ) ) {
5206
			$content_struct['wp_post_format'] = sanitize_key( $content_struct['wp_post_format'] );
5207
			if ( !array_key_exists( $content_struct['wp_post_format'], get_post_format_strings() ) ) {
5208
				return new IXR_Error( 404, __( 'Invalid post format' ) );
5209
			}
5210
		}
5211
5212
		$this->escape($postdata);
5213
5214
		$ID = $postdata['ID'];
5215
		$post_content = $postdata['post_content'];
5216
		$post_title = $postdata['post_title'];
5217
		$post_excerpt = $postdata['post_excerpt'];
5218
		$post_password = $postdata['post_password'];
5219
		$post_parent = $postdata['post_parent'];
5220
		$post_type = $postdata['post_type'];
5221
		$menu_order = $postdata['menu_order'];
5222
5223
		// Let WordPress manage slug if none was provided.
5224
		$post_name = $postdata['post_name'];
5225
		if ( isset($content_struct['wp_slug']) )
5226
			$post_name = $content_struct['wp_slug'];
5227
5228
		// Only use a password if one was given.
5229
		if ( isset($content_struct['wp_password']) )
5230
			$post_password = $content_struct['wp_password'];
5231
5232
		// Only set a post parent if one was given.
5233
		if ( isset($content_struct['wp_page_parent_id']) )
5234
			$post_parent = $content_struct['wp_page_parent_id'];
5235
5236
		// Only set the menu_order if it was given.
5237
		if ( isset($content_struct['wp_page_order']) )
5238
			$menu_order = $content_struct['wp_page_order'];
5239
5240
		$page_template = null;
5241
		if ( ! empty( $content_struct['wp_page_template'] ) && 'page' == $post_type )
5242
			$page_template = $content_struct['wp_page_template'];
5243
5244
		$post_author = $postdata['post_author'];
5245
5246
		// Only set the post_author if one is set.
5247
		if ( isset( $content_struct['wp_author_id'] ) ) {
5248
			// Check permissions if attempting to switch author to or from another user.
5249
			if ( $user->ID != $content_struct['wp_author_id'] || $user->ID != $post_author ) {
5250 View Code Duplication
				switch ( $post_type ) {
5251
					case 'post':
5252
						if ( ! current_user_can( 'edit_others_posts' ) ) {
5253
							return new IXR_Error( 401, __( 'Sorry, you are not allowed to change the post author as this user.' ) );
5254
						}
5255
						break;
5256
					case 'page':
5257
						if ( ! current_user_can( 'edit_others_pages' ) ) {
5258
							return new IXR_Error( 401, __( 'Sorry, you are not allowed to change the page author as this user.' ) );
5259
						}
5260
						break;
5261
					default:
5262
						return new IXR_Error( 401, __( 'Invalid post type.' ) );
5263
				}
5264
				$post_author = $content_struct['wp_author_id'];
5265
			}
5266
		}
5267
5268
		if ( isset($content_struct['mt_allow_comments']) ) {
5269
			if ( !is_numeric($content_struct['mt_allow_comments']) ) {
5270
				switch ( $content_struct['mt_allow_comments'] ) {
5271
					case 'closed':
5272
						$comment_status = 'closed';
5273
						break;
5274
					case 'open':
5275
						$comment_status = 'open';
5276
						break;
5277
					default:
5278
						$comment_status = get_default_comment_status( $post_type );
5279
						break;
5280
				}
5281
			} else {
5282
				switch ( (int) $content_struct['mt_allow_comments'] ) {
5283
					case 0:
5284
					case 2:
5285
						$comment_status = 'closed';
5286
						break;
5287
					case 1:
5288
						$comment_status = 'open';
5289
						break;
5290
					default:
5291
						$comment_status = get_default_comment_status( $post_type );
5292
						break;
5293
				}
5294
			}
5295
		}
5296
5297
		if ( isset($content_struct['mt_allow_pings']) ) {
5298
			if ( !is_numeric($content_struct['mt_allow_pings']) ) {
5299
				switch ( $content_struct['mt_allow_pings'] ) {
5300
					case 'closed':
5301
						$ping_status = 'closed';
5302
						break;
5303
					case 'open':
5304
						$ping_status = 'open';
5305
						break;
5306
					default:
5307
						$ping_status = get_default_comment_status( $post_type, 'pingback' );
5308
						break;
5309
				}
5310
			} else {
5311
				switch ( (int) $content_struct["mt_allow_pings"] ) {
5312
					case 0:
5313
						$ping_status = 'closed';
5314
						break;
5315
					case 1:
5316
						$ping_status = 'open';
5317
						break;
5318
					default:
5319
						$ping_status = get_default_comment_status( $post_type, 'pingback' );
5320
						break;
5321
				}
5322
			}
5323
		}
5324
5325
		if ( isset( $content_struct['title'] ) )
5326
			$post_title =  $content_struct['title'];
5327
5328
		if ( isset( $content_struct['description'] ) )
5329
			$post_content = $content_struct['description'];
5330
5331
		$post_category = array();
5332 View Code Duplication
		if ( isset( $content_struct['categories'] ) ) {
5333
			$catnames = $content_struct['categories'];
5334
			if ( is_array($catnames) ) {
5335
				foreach ($catnames as $cat) {
5336
					$post_category[] = get_cat_ID($cat);
5337
				}
5338
			}
5339
		}
5340
5341
		if ( isset( $content_struct['mt_excerpt'] ) )
5342
			$post_excerpt =  $content_struct['mt_excerpt'];
5343
5344
		$post_more = isset( $content_struct['mt_text_more'] ) ? $content_struct['mt_text_more'] : null;
5345
5346
		$post_status = $publish ? 'publish' : 'draft';
5347 View Code Duplication
		if ( isset( $content_struct["{$post_type}_status"] ) ) {
5348
			switch( $content_struct["{$post_type}_status"] ) {
5349
				case 'draft':
5350
				case 'pending':
5351
				case 'private':
5352
				case 'publish':
5353
					$post_status = $content_struct["{$post_type}_status"];
5354
					break;
5355
				default:
5356
					$post_status = $publish ? 'publish' : 'draft';
5357
					break;
5358
			}
5359
		}
5360
5361
		$tags_input = isset( $content_struct['mt_keywords'] ) ? $content_struct['mt_keywords'] : null;
5362
5363
		if ( 'publish' == $post_status || 'private' == $post_status ) {
5364
			if ( 'page' == $post_type && ! current_user_can( 'publish_pages' ) ) {
5365
				return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this page.' ) );
5366
			} elseif ( ! current_user_can( 'publish_posts' ) ) {
5367
				return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) );
5368
			}
5369
		}
5370
5371
		if ( $post_more )
5372
			$post_content = $post_content . "<!--more-->" . $post_more;
5373
5374
		$to_ping = null;
5375 View Code Duplication
		if ( isset( $content_struct['mt_tb_ping_urls'] ) ) {
5376
			$to_ping = $content_struct['mt_tb_ping_urls'];
5377
			if ( is_array($to_ping) )
5378
				$to_ping = implode(' ', $to_ping);
5379
		}
5380
5381
		// Do some timestamp voodoo.
5382 View Code Duplication
		if ( !empty( $content_struct['date_created_gmt'] ) )
5383
			// We know this is supposed to be GMT, so we're going to slap that Z on there by force.
5384
			$dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
5385
		elseif ( !empty( $content_struct['dateCreated']) )
5386
			$dateCreated = $content_struct['dateCreated']->getIso();
5387
5388
		// Default to not flagging the post date to be edited unless it's intentional.
5389
		$edit_date = false;
5390
5391
		if ( !empty( $dateCreated ) ) {
5392
			$post_date = get_date_from_gmt(iso8601_to_datetime($dateCreated));
5393
			$post_date_gmt = iso8601_to_datetime($dateCreated, 'GMT');
5394
5395
			// Flag the post date to be edited.
5396
			$edit_date = true;
5397
		} else {
5398
			$post_date     = $postdata['post_date'];
5399
			$post_date_gmt = $postdata['post_date_gmt'];
5400
		}
5401
5402
		// We've got all the data -- post it.
5403
		$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');
5404
5405
		$result = wp_update_post($newpost, true);
5406
		if ( is_wp_error( $result ) )
5407
			return new IXR_Error(500, $result->get_error_message());
5408
5409
		if ( !$result )
5410
			return new IXR_Error(500, __('Sorry, your entry could not be edited. Something wrong happened.'));
5411
5412
		// Only posts can be sticky
5413 View Code Duplication
		if ( $post_type == 'post' && isset( $content_struct['sticky'] ) ) {
5414
			$data = $newpost;
5415
			$data['sticky'] = $content_struct['sticky'];
5416
			$data['post_type'] = 'post';
5417
			$error = $this->_toggle_sticky( $data, true );
5418
			if ( $error ) {
5419
				return $error;
5420
			}
5421
		}
5422
5423
		if ( isset($content_struct['custom_fields']) )
5424
			$this->set_custom_fields($post_ID, $content_struct['custom_fields']);
5425
5426
		if ( isset ( $content_struct['wp_post_thumbnail'] ) ) {
5427
5428
			// Empty value deletes, non-empty value adds/updates.
5429 View Code Duplication
			if ( empty( $content_struct['wp_post_thumbnail'] ) ) {
5430
				delete_post_thumbnail( $post_ID );
5431
			} else {
5432
				if ( set_post_thumbnail( $post_ID, $content_struct['wp_post_thumbnail'] ) === false )
5433
					return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
5434
			}
5435
			unset( $content_struct['wp_post_thumbnail'] );
5436
		}
5437
5438
		// Handle enclosures.
5439
		$thisEnclosure = isset($content_struct['enclosure']) ? $content_struct['enclosure'] : null;
5440
		$this->add_enclosure_if_new($post_ID, $thisEnclosure);
5441
5442
		$this->attach_uploads( $ID, $post_content );
5443
5444
		// Handle post formats if assigned, validation is handled earlier in this function.
5445
		if ( isset( $content_struct['wp_post_format'] ) )
5446
			set_post_format( $post_ID, $content_struct['wp_post_format'] );
5447
5448
		/**
5449
		 * Fires after a post has been successfully updated via the XML-RPC MovableType API.
5450
		 *
5451
		 * @since 3.4.0
5452
		 *
5453
		 * @param int   $post_ID ID of the updated post.
5454
		 * @param array $args    An array of arguments to update the post.
5455
		 */
5456
		do_action( 'xmlrpc_call_success_mw_editPost', $post_ID, $args );
5457
5458
		return true;
5459
	}
5460
5461
	/**
5462
	 * Retrieve post.
5463
	 *
5464
	 * @since 1.5.0
5465
	 *
5466
	 * @param array  $args {
5467
	 *     Method arguments. Note: arguments must be ordered as documented.
5468
	 *
5469
	 *     @type int    $blog_id (unused)
5470
	 *     @type int    $post_ID
5471
	 *     @type string $username
5472
	 *     @type string $password
5473
	 * }
5474
	 * @return array|IXR_Error
5475
	 */
5476
	public function mw_getPost( $args ) {
5477
		$this->escape( $args );
5478
5479
		$post_ID  = (int) $args[0];
5480
		$username = $args[1];
5481
		$password = $args[2];
5482
5483
		if ( !$user = $this->login($username, $password) )
5484
			return $this->error;
5485
5486
		$postdata = get_post($post_ID, ARRAY_A);
5487
		if ( ! $postdata )
5488
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
5489
5490
		if ( !current_user_can( 'edit_post', $post_ID ) )
5491
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
5492
5493
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5494
		do_action( 'xmlrpc_call', 'metaWeblog.getPost' );
5495
5496
		if ($postdata['post_date'] != '') {
5497
			$post_date = $this->_convert_date( $postdata['post_date'] );
5498
			$post_date_gmt = $this->_convert_date_gmt( $postdata['post_date_gmt'],  $postdata['post_date'] );
5499
			$post_modified = $this->_convert_date( $postdata['post_modified'] );
5500
			$post_modified_gmt = $this->_convert_date_gmt( $postdata['post_modified_gmt'], $postdata['post_modified'] );
5501
5502
			$categories = array();
5503
			$catids = wp_get_post_categories($post_ID);
5504
			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...
5505
				$categories[] = get_cat_name($catid);
5506
5507
			$tagnames = array();
5508
			$tags = wp_get_post_tags( $post_ID );
5509 View Code Duplication
			if ( !empty( $tags ) ) {
5510
				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...
5511
					$tagnames[] = $tag->name;
5512
				$tagnames = implode( ', ', $tagnames );
5513
			} else {
5514
				$tagnames = '';
5515
			}
5516
5517
			$post = get_extended($postdata['post_content']);
5518
			$link = get_permalink($postdata['ID']);
5519
5520
			// Get the author info.
5521
			$author = get_userdata($postdata['post_author']);
5522
5523
			$allow_comments = ('open' == $postdata['comment_status']) ? 1 : 0;
5524
			$allow_pings = ('open' == $postdata['ping_status']) ? 1 : 0;
5525
5526
			// Consider future posts as published
5527
			if ( $postdata['post_status'] === 'future' )
5528
				$postdata['post_status'] = 'publish';
5529
5530
			// Get post format
5531
			$post_format = get_post_format( $post_ID );
5532
			if ( empty( $post_format ) )
5533
				$post_format = 'standard';
5534
5535
			$sticky = false;
5536
			if ( is_sticky( $post_ID ) )
5537
				$sticky = true;
5538
5539
			$enclosure = array();
5540
			foreach ( (array) get_post_custom($post_ID) as $key => $val) {
5541
				if ($key == 'enclosure') {
5542
					foreach ( (array) $val as $enc ) {
5543
						$encdata = explode("\n", $enc);
5544
						$enclosure['url'] = trim(htmlspecialchars($encdata[0]));
5545
						$enclosure['length'] = (int) trim($encdata[1]);
5546
						$enclosure['type'] = trim($encdata[2]);
5547
						break 2;
5548
					}
5549
				}
5550
			}
5551
5552
			$resp = array(
5553
				'dateCreated' => $post_date,
5554
				'userid' => $postdata['post_author'],
5555
				'postid' => $postdata['ID'],
5556
				'description' => $post['main'],
5557
				'title' => $postdata['post_title'],
5558
				'link' => $link,
5559
				'permaLink' => $link,
5560
				// commented out because no other tool seems to use this
5561
				//	      'content' => $entry['post_content'],
5562
				'categories' => $categories,
5563
				'mt_excerpt' => $postdata['post_excerpt'],
5564
				'mt_text_more' => $post['extended'],
5565
				'wp_more_text' => $post['more_text'],
5566
				'mt_allow_comments' => $allow_comments,
5567
				'mt_allow_pings' => $allow_pings,
5568
				'mt_keywords' => $tagnames,
5569
				'wp_slug' => $postdata['post_name'],
5570
				'wp_password' => $postdata['post_password'],
5571
				'wp_author_id' => (string) $author->ID,
5572
				'wp_author_display_name' => $author->display_name,
5573
				'date_created_gmt' => $post_date_gmt,
5574
				'post_status' => $postdata['post_status'],
5575
				'custom_fields' => $this->get_custom_fields($post_ID),
5576
				'wp_post_format' => $post_format,
5577
				'sticky' => $sticky,
5578
				'date_modified' => $post_modified,
5579
				'date_modified_gmt' => $post_modified_gmt
5580
			);
5581
5582
			if ( !empty($enclosure) ) $resp['enclosure'] = $enclosure;
5583
5584
			$resp['wp_post_thumbnail'] = get_post_thumbnail_id( $postdata['ID'] );
5585
5586
			return $resp;
5587
		} else {
5588
			return new IXR_Error(404, __('Sorry, no such post.'));
5589
		}
5590
	}
5591
5592
	/**
5593
	 * Retrieve list of recent posts.
5594
	 *
5595
	 * @since 1.5.0
5596
	 *
5597
	 * @param array  $args {
5598
	 *     Method arguments. Note: arguments must be ordered as documented.
5599
	 *
5600
	 *     @type int    $blog_id (unused)
5601
	 *     @type string $username
5602
	 *     @type string $password
5603
	 *     @type int    $numberposts
5604
	 * }
5605
	 * @return array|IXR_Error
5606
	 */
5607
	public function mw_getRecentPosts( $args ) {
5608
		$this->escape( $args );
5609
5610
		$username = $args[1];
5611
		$password = $args[2];
5612 View Code Duplication
		if ( isset( $args[3] ) )
5613
			$query = array( 'numberposts' => absint( $args[3] ) );
5614
		else
5615
			$query = array();
5616
5617
		if ( !$user = $this->login($username, $password) )
5618
			return $this->error;
5619
5620
		if ( ! current_user_can( 'edit_posts' ) )
5621
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
5622
5623
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5624
		do_action( 'xmlrpc_call', 'metaWeblog.getRecentPosts' );
5625
5626
		$posts_list = wp_get_recent_posts( $query );
5627
5628
		if ( !$posts_list )
5629
			return array();
5630
5631
		$recent_posts = array();
5632
		foreach ($posts_list as $entry) {
5633
			if ( !current_user_can( 'edit_post', $entry['ID'] ) )
5634
				continue;
5635
5636
			$post_date = $this->_convert_date( $entry['post_date'] );
5637
			$post_date_gmt = $this->_convert_date_gmt( $entry['post_date_gmt'], $entry['post_date'] );
5638
			$post_modified = $this->_convert_date( $entry['post_modified'] );
5639
			$post_modified_gmt = $this->_convert_date_gmt( $entry['post_modified_gmt'], $entry['post_modified'] );
5640
5641
			$categories = array();
5642
			$catids = wp_get_post_categories($entry['ID']);
5643
			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...
5644
				$categories[] = get_cat_name($catid);
5645
5646
			$tagnames = array();
5647
			$tags = wp_get_post_tags( $entry['ID'] );
5648 View Code Duplication
			if ( !empty( $tags ) ) {
5649
				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...
5650
					$tagnames[] = $tag->name;
5651
				}
5652
				$tagnames = implode( ', ', $tagnames );
5653
			} else {
5654
				$tagnames = '';
5655
			}
5656
5657
			$post = get_extended($entry['post_content']);
5658
			$link = get_permalink($entry['ID']);
5659
5660
			// Get the post author info.
5661
			$author = get_userdata($entry['post_author']);
5662
5663
			$allow_comments = ('open' == $entry['comment_status']) ? 1 : 0;
5664
			$allow_pings = ('open' == $entry['ping_status']) ? 1 : 0;
5665
5666
			// Consider future posts as published
5667
			if ( $entry['post_status'] === 'future' )
5668
				$entry['post_status'] = 'publish';
5669
5670
			// Get post format
5671
			$post_format = get_post_format( $entry['ID'] );
5672
			if ( empty( $post_format ) )
5673
				$post_format = 'standard';
5674
5675
			$recent_posts[] = array(
5676
				'dateCreated' => $post_date,
5677
				'userid' => $entry['post_author'],
5678
				'postid' => (string) $entry['ID'],
5679
				'description' => $post['main'],
5680
				'title' => $entry['post_title'],
5681
				'link' => $link,
5682
				'permaLink' => $link,
5683
				// commented out because no other tool seems to use this
5684
				// 'content' => $entry['post_content'],
5685
				'categories' => $categories,
5686
				'mt_excerpt' => $entry['post_excerpt'],
5687
				'mt_text_more' => $post['extended'],
5688
				'wp_more_text' => $post['more_text'],
5689
				'mt_allow_comments' => $allow_comments,
5690
				'mt_allow_pings' => $allow_pings,
5691
				'mt_keywords' => $tagnames,
5692
				'wp_slug' => $entry['post_name'],
5693
				'wp_password' => $entry['post_password'],
5694
				'wp_author_id' => (string) $author->ID,
5695
				'wp_author_display_name' => $author->display_name,
5696
				'date_created_gmt' => $post_date_gmt,
5697
				'post_status' => $entry['post_status'],
5698
				'custom_fields' => $this->get_custom_fields($entry['ID']),
5699
				'wp_post_format' => $post_format,
5700
				'date_modified' => $post_modified,
5701
				'date_modified_gmt' => $post_modified_gmt,
5702
				'sticky' => ( $entry['post_type'] === 'post' && is_sticky( $entry['ID'] ) ),
5703
				'wp_post_thumbnail' => get_post_thumbnail_id( $entry['ID'] )
5704
			);
5705
		}
5706
5707
		return $recent_posts;
5708
	}
5709
5710
	/**
5711
	 * Retrieve the list of categories on a given blog.
5712
	 *
5713
	 * @since 1.5.0
5714
	 *
5715
	 * @param array  $args {
5716
	 *     Method arguments. Note: arguments must be ordered as documented.
5717
	 *
5718
	 *     @type int    $blog_id (unused)
5719
	 *     @type string $username
5720
	 *     @type string $password
5721
	 * }
5722
	 * @return array|IXR_Error
5723
	 */
5724
	public function mw_getCategories( $args ) {
5725
		$this->escape( $args );
5726
5727
		$username = $args[1];
5728
		$password = $args[2];
5729
5730
		if ( !$user = $this->login($username, $password) )
5731
			return $this->error;
5732
5733
		if ( !current_user_can( 'edit_posts' ) )
5734
			return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
5735
5736
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5737
		do_action( 'xmlrpc_call', 'metaWeblog.getCategories' );
5738
5739
		$categories_struct = array();
5740
5741
		if ( $cats = get_categories(array('get' => 'all')) ) {
5742
			foreach ( $cats as $cat ) {
5743
				$struct = array();
5744
				$struct['categoryId'] = $cat->term_id;
5745
				$struct['parentId'] = $cat->parent;
5746
				$struct['description'] = $cat->name;
5747
				$struct['categoryDescription'] = $cat->description;
5748
				$struct['categoryName'] = $cat->name;
5749
				$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...
5750
				$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...
5751
5752
				$categories_struct[] = $struct;
5753
			}
5754
		}
5755
5756
		return $categories_struct;
5757
	}
5758
5759
	/**
5760
	 * Uploads a file, following your settings.
5761
	 *
5762
	 * Adapted from a patch by Johann Richard.
5763
	 *
5764
	 * @link http://mycvs.org/archives/2004/06/30/file-upload-to-wordpress-in-ecto/
5765
	 *
5766
	 * @since 1.5.0
5767
	 *
5768
	 * @param array  $args {
5769
	 *     Method arguments. Note: arguments must be ordered as documented.
5770
	 *
5771
	 *     @type int    $blog_id (unused)
5772
	 *     @type string $username
5773
	 *     @type string $password
5774
	 *     @type array  $data
5775
	 * }
5776
	 * @return array|IXR_Error
5777
	 */
5778
	public function mw_newMediaObject( $args ) {
5779
		$username = $this->escape( $args[1] );
5780
		$password = $this->escape( $args[2] );
5781
		$data     = $args[3];
5782
5783
		$name = sanitize_file_name( $data['name'] );
5784
		$type = $data['type'];
5785
		$bits = $data['bits'];
5786
5787
		if ( !$user = $this->login($username, $password) )
5788
			return $this->error;
5789
5790
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5791
		do_action( 'xmlrpc_call', 'metaWeblog.newMediaObject' );
5792
5793
		if ( !current_user_can('upload_files') ) {
5794
			$this->error = new IXR_Error( 401, __( 'Sorry, you are not allowed to upload files.' ) );
5795
			return $this->error;
5796
		}
5797
5798
		if ( is_multisite() && upload_is_user_over_quota( false ) ) {
5799
			$this->error = new IXR_Error( 401, __( 'Sorry, you have used your space allocation.' ) );
5800
			return $this->error;
5801
		}
5802
5803
		/**
5804
		 * Filters whether to preempt the XML-RPC media upload.
5805
		 *
5806
		 * Passing a truthy value will effectively short-circuit the media upload,
5807
		 * returning that value as a 500 error instead.
5808
		 *
5809
		 * @since 2.1.0
5810
		 *
5811
		 * @param bool $error Whether to pre-empt the media upload. Default false.
5812
		 */
5813
		if ( $upload_err = apply_filters( 'pre_upload_error', false ) ) {
5814
			return new IXR_Error( 500, $upload_err );
5815
		}
5816
5817
		$upload = wp_upload_bits($name, null, $bits);
5818
		if ( ! empty($upload['error']) ) {
5819
			$errorString = sprintf(__('Could not write file %1$s (%2$s)'), $name, $upload['error']);
5820
			return new IXR_Error(500, $errorString);
5821
		}
5822
		// Construct the attachment array
5823
		$post_id = 0;
5824
		if ( ! empty( $data['post_id'] ) ) {
5825
			$post_id = (int) $data['post_id'];
5826
5827
			if ( ! current_user_can( 'edit_post', $post_id ) )
5828
				return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
5829
		}
5830
		$attachment = array(
5831
			'post_title' => $name,
5832
			'post_content' => '',
5833
			'post_type' => 'attachment',
5834
			'post_parent' => $post_id,
5835
			'post_mime_type' => $type,
5836
			'guid' => $upload[ 'url' ]
5837
		);
5838
5839
		// Save the data
5840
		$id = wp_insert_attachment( $attachment, $upload[ 'file' ], $post_id );
5841
		wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $upload['file'] ) );
0 ignored issues
show
It seems like $id defined by wp_insert_attachment($at...load['file'], $post_id) on line 5840 can also be of type object<WP_Error>; however, wp_update_attachment_metadata() does only seem to accept integer, 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...
5842
5843
		/**
5844
		 * Fires after a new attachment has been added via the XML-RPC MovableType API.
5845
		 *
5846
		 * @since 3.4.0
5847
		 *
5848
		 * @param int   $id   ID of the new attachment.
5849
		 * @param array $args An array of arguments to add the attachment.
5850
		 */
5851
		do_action( 'xmlrpc_call_success_mw_newMediaObject', $id, $args );
5852
5853
		$struct = $this->_prepare_media_item( get_post( $id ) );
0 ignored issues
show
It seems like $id defined by wp_insert_attachment($at...load['file'], $post_id) on line 5840 can also be of type object<WP_Error>; however, get_post() does only seem to accept integer|object<WP_Post>|null, 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...
5854
5855
		// Deprecated values
5856
		$struct['id']   = $struct['attachment_id'];
5857
		$struct['file'] = $struct['title'];
5858
		$struct['url']  = $struct['link'];
5859
5860
		return $struct;
5861
	}
5862
5863
	/* MovableType API functions
5864
	 * specs on http://www.movabletype.org/docs/mtmanual_programmatic.html
5865
	 */
5866
5867
	/**
5868
	 * Retrieve the post titles of recent posts.
5869
	 *
5870
	 * @since 1.5.0
5871
	 *
5872
	 * @param array  $args {
5873
	 *     Method arguments. Note: arguments must be ordered as documented.
5874
	 *
5875
	 *     @type int    $blog_id (unused)
5876
	 *     @type string $username
5877
	 *     @type string $password
5878
	 *     @type int    $numberposts
5879
	 * }
5880
	 * @return array|IXR_Error
5881
	 */
5882
	public function mt_getRecentPostTitles( $args ) {
5883
		$this->escape( $args );
5884
5885
		$username = $args[1];
5886
		$password = $args[2];
5887 View Code Duplication
		if ( isset( $args[3] ) )
5888
			$query = array( 'numberposts' => absint( $args[3] ) );
5889
		else
5890
			$query = array();
5891
5892
		if ( !$user = $this->login($username, $password) )
5893
			return $this->error;
5894
5895
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5896
		do_action( 'xmlrpc_call', 'mt.getRecentPostTitles' );
5897
5898
		$posts_list = wp_get_recent_posts( $query );
5899
5900
		if ( !$posts_list ) {
5901
			$this->error = new IXR_Error(500, __('Either there are no posts, or something went wrong.'));
5902
			return $this->error;
5903
		}
5904
5905
		$recent_posts = array();
5906
5907
		foreach ($posts_list as $entry) {
5908
			if ( !current_user_can( 'edit_post', $entry['ID'] ) )
5909
				continue;
5910
5911
			$post_date = $this->_convert_date( $entry['post_date'] );
5912
			$post_date_gmt = $this->_convert_date_gmt( $entry['post_date_gmt'], $entry['post_date'] );
5913
5914
			$recent_posts[] = array(
5915
				'dateCreated' => $post_date,
5916
				'userid' => $entry['post_author'],
5917
				'postid' => (string) $entry['ID'],
5918
				'title' => $entry['post_title'],
5919
				'post_status' => $entry['post_status'],
5920
				'date_created_gmt' => $post_date_gmt
5921
			);
5922
		}
5923
5924
		return $recent_posts;
5925
	}
5926
5927
	/**
5928
	 * Retrieve list of all categories on blog.
5929
	 *
5930
	 * @since 1.5.0
5931
	 *
5932
	 * @param array  $args {
5933
	 *     Method arguments. Note: arguments must be ordered as documented.
5934
	 *
5935
	 *     @type int    $blog_id (unused)
5936
	 *     @type string $username
5937
	 *     @type string $password
5938
	 * }
5939
	 * @return array|IXR_Error
5940
	 */
5941
	public function mt_getCategoryList( $args ) {
5942
		$this->escape( $args );
5943
5944
		$username = $args[1];
5945
		$password = $args[2];
5946
5947
		if ( !$user = $this->login($username, $password) )
5948
			return $this->error;
5949
5950
		if ( !current_user_can( 'edit_posts' ) )
5951
			return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
5952
5953
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
5954
		do_action( 'xmlrpc_call', 'mt.getCategoryList' );
5955
5956
		$categories_struct = array();
5957
5958
		if ( $cats = get_categories(array('hide_empty' => 0, 'hierarchical' => 0)) ) {
5959
			foreach ( $cats as $cat ) {
5960
				$struct = array();
5961
				$struct['categoryId'] = $cat->term_id;
5962
				$struct['categoryName'] = $cat->name;
5963
5964
				$categories_struct[] = $struct;
5965
			}
5966
		}
5967
5968
		return $categories_struct;
5969
	}
5970
5971
	/**
5972
	 * Retrieve post categories.
5973
	 *
5974
	 * @since 1.5.0
5975
	 *
5976
	 * @param array  $args {
5977
	 *     Method arguments. Note: arguments must be ordered as documented.
5978
	 *
5979
	 *     @type int    $post_ID
5980
	 *     @type string $username
5981
	 *     @type string $password
5982
	 * }
5983
	 * @return array|IXR_Error
5984
	 */
5985
	public function mt_getPostCategories( $args ) {
5986
		$this->escape( $args );
5987
5988
		$post_ID  = (int) $args[0];
5989
		$username = $args[1];
5990
		$password = $args[2];
5991
5992
		if ( !$user = $this->login($username, $password) )
5993
			return $this->error;
5994
5995
		if ( ! get_post( $post_ID ) )
5996
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
5997
5998
		if ( !current_user_can( 'edit_post', $post_ID ) )
5999
			return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
6000
6001
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6002
		do_action( 'xmlrpc_call', 'mt.getPostCategories' );
6003
6004
		$categories = array();
6005
		$catids = wp_get_post_categories(intval($post_ID));
6006
		// first listed category will be the primary category
6007
		$isPrimary = true;
6008
		foreach ( $catids as $catid ) {
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...
6009
			$categories[] = array(
6010
				'categoryName' => get_cat_name($catid),
6011
				'categoryId' => (string) $catid,
6012
				'isPrimary' => $isPrimary
6013
			);
6014
			$isPrimary = false;
6015
		}
6016
6017
		return $categories;
6018
	}
6019
6020
	/**
6021
	 * Sets categories for a post.
6022
	 *
6023
	 * @since 1.5.0
6024
	 *
6025
	 * @param array  $args {
6026
	 *     Method arguments. Note: arguments must be ordered as documented.
6027
	 *
6028
	 *     @type int    $post_ID
6029
	 *     @type string $username
6030
	 *     @type string $password
6031
	 *     @type array  $categories
6032
	 * }
6033
	 * @return true|IXR_Error True on success.
6034
	 */
6035
	public function mt_setPostCategories( $args ) {
6036
		$this->escape( $args );
6037
6038
		$post_ID    = (int) $args[0];
6039
		$username   = $args[1];
6040
		$password   = $args[2];
6041
		$categories = $args[3];
6042
6043
		if ( !$user = $this->login($username, $password) )
6044
			return $this->error;
6045
6046
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6047
		do_action( 'xmlrpc_call', 'mt.setPostCategories' );
6048
6049
		if ( ! get_post( $post_ID ) )
6050
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
6051
6052
		if ( !current_user_can('edit_post', $post_ID) )
6053
			return new IXR_Error(401, __('Sorry, you are not allowed to edit this post.'));
6054
6055
		$catids = array();
6056
		foreach ( $categories as $cat ) {
6057
			$catids[] = $cat['categoryId'];
6058
		}
6059
6060
		wp_set_post_categories($post_ID, $catids);
6061
6062
		return true;
6063
	}
6064
6065
	/**
6066
	 * Retrieve an array of methods supported by this server.
6067
	 *
6068
	 * @since 1.5.0
6069
	 *
6070
	 * @return array
6071
	 */
6072
	public function mt_supportedMethods() {
6073
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6074
		do_action( 'xmlrpc_call', 'mt.supportedMethods' );
6075
6076
		return array_keys( $this->methods );
6077
	}
6078
6079
	/**
6080
	 * Retrieve an empty array because we don't support per-post text filters.
6081
	 *
6082
	 * @since 1.5.0
6083
	 */
6084
	public function mt_supportedTextFilters() {
6085
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6086
		do_action( 'xmlrpc_call', 'mt.supportedTextFilters' );
6087
6088
		/**
6089
		 * Filters the MoveableType text filters list for XML-RPC.
6090
		 *
6091
		 * @since 2.2.0
6092
		 *
6093
		 * @param array $filters An array of text filters.
6094
		 */
6095
		return apply_filters( 'xmlrpc_text_filters', array() );
6096
	}
6097
6098
	/**
6099
	 * Retrieve trackbacks sent to a given post.
6100
	 *
6101
	 * @since 1.5.0
6102
	 *
6103
	 * @param int $post_ID
6104
	 * @return array|IXR_Error
6105
	 */
6106
	public function mt_getTrackbackPings( $post_ID ) {
6107
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6108
		do_action( 'xmlrpc_call', 'mt.getTrackbackPings' );
6109
6110
		$actual_post = get_post($post_ID, ARRAY_A);
6111
6112
		if ( !$actual_post )
6113
			return new IXR_Error(404, __('Sorry, no such post.'));
6114
6115
		$comments = $this->db->get_results( $this->db->prepare("SELECT comment_author_url, comment_content, comment_author_IP, comment_type FROM {$this->db->comments} WHERE comment_post_ID = %d", $post_ID) );
6116
6117
		if ( !$comments )
6118
			return array();
6119
6120
		$trackback_pings = array();
6121
		foreach ( $comments as $comment ) {
6122
			if ( 'trackback' == $comment->comment_type ) {
6123
				$content = $comment->comment_content;
6124
				$title = substr($content, 8, (strpos($content, '</strong>') - 8));
6125
				$trackback_pings[] = array(
6126
					'pingTitle' => $title,
6127
					'pingURL'   => $comment->comment_author_url,
6128
					'pingIP'    => $comment->comment_author_IP
6129
				);
6130
			}
6131
		}
6132
6133
		return $trackback_pings;
6134
	}
6135
6136
	/**
6137
	 * Sets a post's publish status to 'publish'.
6138
	 *
6139
	 * @since 1.5.0
6140
	 *
6141
	 * @param array  $args {
6142
	 *     Method arguments. Note: arguments must be ordered as documented.
6143
	 *
6144
	 *     @type int    $post_ID
6145
	 *     @type string $username
6146
	 *     @type string $password
6147
	 * }
6148
	 * @return int|IXR_Error
6149
	 */
6150
	public function mt_publishPost( $args ) {
6151
		$this->escape( $args );
6152
6153
		$post_ID  = (int) $args[0];
6154
		$username = $args[1];
6155
		$password = $args[2];
6156
6157
		if ( !$user = $this->login($username, $password) )
6158
			return $this->error;
6159
6160
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6161
		do_action( 'xmlrpc_call', 'mt.publishPost' );
6162
6163
		$postdata = get_post($post_ID, ARRAY_A);
6164
		if ( ! $postdata )
6165
			return new IXR_Error( 404, __( 'Invalid post ID.' ) );
6166
6167
		if ( !current_user_can('publish_posts') || !current_user_can('edit_post', $post_ID) )
6168
			return new IXR_Error(401, __('Sorry, you are not allowed to publish this post.'));
6169
6170
		$postdata['post_status'] = 'publish';
6171
6172
		// retain old cats
6173
		$cats = wp_get_post_categories($post_ID);
6174
		$postdata['post_category'] = $cats;
6175
		$this->escape($postdata);
6176
6177
		return wp_update_post( $postdata );
0 ignored issues
show
It seems like $postdata defined by get_post($post_ID, ARRAY_A) on line 6163 can also be of type string; however, wp_update_post() does only seem to accept array|object, 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...
6178
	}
6179
6180
	/* PingBack functions
6181
	 * specs on www.hixie.ch/specs/pingback/pingback
6182
	 */
6183
6184
	/**
6185
	 * Retrieves a pingback and registers it.
6186
	 *
6187
	 * @since 1.5.0
6188
	 *
6189
	 * @global string $wp_version
6190
	 *
6191
	 * @param array  $args {
6192
	 *     Method arguments. Note: arguments must be ordered as documented.
6193
	 *
6194
	 *     @type string $pagelinkedfrom
6195
	 *     @type string $pagelinkedto
6196
	 * }
6197
	 * @return string|IXR_Error
6198
	 */
6199
	public function pingback_ping( $args ) {
6200
		global $wp_version;
6201
6202
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6203
		do_action( 'xmlrpc_call', 'pingback.ping' );
6204
6205
		$this->escape( $args );
6206
6207
		$pagelinkedfrom = str_replace( '&amp;', '&', $args[0] );
6208
		$pagelinkedto = str_replace( '&amp;', '&', $args[1] );
6209
		$pagelinkedto = str_replace( '&', '&amp;', $pagelinkedto );
6210
6211
		/**
6212
		 * Filters the pingback source URI.
6213
		 *
6214
		 * @since 3.6.0
6215
		 *
6216
		 * @param string $pagelinkedfrom URI of the page linked from.
6217
		 * @param string $pagelinkedto   URI of the page linked to.
6218
		 */
6219
		$pagelinkedfrom = apply_filters( 'pingback_ping_source_uri', $pagelinkedfrom, $pagelinkedto );
6220
6221
		if ( ! $pagelinkedfrom )
6222
			return $this->pingback_error( 0, __( 'A valid URL was not provided.' ) );
6223
6224
		// Check if the page linked to is in our site
6225
		$pos1 = strpos($pagelinkedto, str_replace(array('http://www.','http://','https://www.','https://'), '', get_option('home')));
6226
		if ( !$pos1 )
6227
			return $this->pingback_error( 0, __( 'Is there no link to us?' ) );
6228
6229
		// let's find which post is linked to
6230
		// FIXME: does url_to_postid() cover all these cases already?
6231
		//        if so, then let's use it and drop the old code.
6232
		$urltest = parse_url($pagelinkedto);
6233
		if ( $post_ID = url_to_postid($pagelinkedto) ) {
0 ignored issues
show
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
6234
			// $way
6235
		} elseif ( isset( $urltest['path'] ) && preg_match('#p/[0-9]{1,}#', $urltest['path'], $match) ) {
6236
			// the path defines the post_ID (archives/p/XXXX)
6237
			$blah = explode('/', $match[0]);
6238
			$post_ID = (int) $blah[1];
6239
		} elseif ( isset( $urltest['query'] ) && preg_match('#p=[0-9]{1,}#', $urltest['query'], $match) ) {
6240
			// the querystring defines the post_ID (?p=XXXX)
6241
			$blah = explode('=', $match[0]);
6242
			$post_ID = (int) $blah[1];
6243
		} elseif ( isset($urltest['fragment']) ) {
6244
			// an #anchor is there, it's either...
6245
			if ( intval($urltest['fragment']) ) {
6246
				// ...an integer #XXXX (simplest case)
6247
				$post_ID = (int) $urltest['fragment'];
6248
			} elseif ( preg_match('/post-[0-9]+/',$urltest['fragment']) ) {
6249
				// ...a post id in the form 'post-###'
6250
				$post_ID = preg_replace('/[^0-9]+/', '', $urltest['fragment']);
6251
			} elseif ( is_string($urltest['fragment']) ) {
6252
				// ...or a string #title, a little more complicated
6253
				$title = preg_replace('/[^a-z0-9]/i', '.', $urltest['fragment']);
6254
				$sql = $this->db->prepare("SELECT ID FROM {$this->db->posts} WHERE post_title RLIKE %s", $title );
6255
				if (! ($post_ID = $this->db->get_var($sql)) ) {
6256
					// returning unknown error '0' is better than die()ing
6257
			  		return $this->pingback_error( 0, '' );
6258
				}
6259
			}
6260
		} else {
6261
			// TODO: Attempt to extract a post ID from the given URL
6262
	  		return $this->pingback_error( 33, __('The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.' ) );
6263
		}
6264
		$post_ID = (int) $post_ID;
6265
6266
		$post = get_post($post_ID);
6267
6268
		if ( !$post ) // Post_ID not found
6269
	  		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.' ) );
6270
6271
		if ( $post_ID == url_to_postid($pagelinkedfrom) )
6272
			return $this->pingback_error( 0, __( 'The source URL and the target URL cannot both point to the same resource.' ) );
6273
6274
		// Check if pings are on
6275
		if ( !pings_open($post) )
0 ignored issues
show
It seems like $post defined by get_post($post_ID) on line 6266 can also be of type array; however, pings_open() does only seem to accept integer|object<WP_Post>|null, 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...
6276
	  		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.' ) );
6277
6278
		// Let's check that the remote site didn't already pingback this entry
6279
		if ( $this->db->get_results( $this->db->prepare("SELECT * FROM {$this->db->comments} WHERE comment_post_ID = %d AND comment_author_url = %s", $post_ID, $pagelinkedfrom) ) )
6280
			return $this->pingback_error( 48, __( 'The pingback has already been registered.' ) );
6281
6282
		// very stupid, but gives time to the 'from' server to publish !
6283
		sleep(1);
6284
6285
		$remote_ip = preg_replace( '/[^0-9a-fA-F:., ]/', '', $_SERVER['REMOTE_ADDR'] );
6286
6287
		/** This filter is documented in wp-includes/class-http.php */
6288
		$user_agent = apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ) );
6289
6290
		// Let's check the remote site
6291
		$http_api_args = array(
6292
			'timeout' => 10,
6293
			'redirection' => 0,
6294
			'limit_response_size' => 153600, // 150 KB
6295
			'user-agent' => "$user_agent; verifying pingback from $remote_ip",
6296
			'headers' => array(
6297
				'X-Pingback-Forwarded-For' => $remote_ip,
6298
			),
6299
		);
6300
6301
		$request = wp_safe_remote_get( $pagelinkedfrom, $http_api_args );
6302
		$remote_source = $remote_source_original = wp_remote_retrieve_body( $request );
0 ignored issues
show
It seems like $request defined by wp_safe_remote_get($page...edfrom, $http_api_args) on line 6301 can also be of type object<WP_Error>; however, wp_remote_retrieve_body() does only seem to accept array, 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...
6303
6304
		if ( ! $remote_source ) {
6305
			return $this->pingback_error( 16, __( 'The source URL does not exist.' ) );
6306
		}
6307
6308
		/**
6309
		 * Filters the pingback remote source.
6310
		 *
6311
		 * @since 2.5.0
6312
		 *
6313
		 * @param string $remote_source Response source for the page linked from.
6314
		 * @param string $pagelinkedto  URL of the page linked to.
6315
		 */
6316
		$remote_source = apply_filters( 'pre_remote_source', $remote_source, $pagelinkedto );
6317
6318
		// Work around bug in strip_tags():
6319
		$remote_source = str_replace( '<!DOC', '<DOC', $remote_source );
6320
		$remote_source = preg_replace( '/[\r\n\t ]+/', ' ', $remote_source ); // normalize spaces
6321
		$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 );
6322
6323
		preg_match( '|<title>([^<]*?)</title>|is', $remote_source, $matchtitle );
6324
		$title = $matchtitle[1];
6325
		if ( empty( $title ) )
6326
			return $this->pingback_error( 32, __('We cannot find a title on that page.' ) );
6327
6328
		$remote_source = strip_tags( $remote_source, '<a>' ); // just keep the tag we need
6329
6330
		$p = explode( "\n\n", $remote_source );
6331
6332
		$preg_target = preg_quote($pagelinkedto, '|');
6333
6334
		foreach ( $p as $para ) {
6335
			if ( strpos($para, $pagelinkedto) !== false ) { // it exists, but is it a link?
6336
				preg_match("|<a[^>]+?".$preg_target."[^>]*>([^>]+?)</a>|", $para, $context);
6337
6338
				// If the URL isn't in a link context, keep looking
6339
				if ( empty($context) )
6340
					continue;
6341
6342
				// We're going to use this fake tag to mark the context in a bit
6343
				// the marker is needed in case the link text appears more than once in the paragraph
6344
				$excerpt = preg_replace('|\</?wpcontext\>|', '', $para);
6345
6346
				// prevent really long link text
6347
				if ( strlen($context[1]) > 100 )
6348
					$context[1] = substr($context[1], 0, 100) . '&#8230;';
6349
6350
				$marker = '<wpcontext>'.$context[1].'</wpcontext>';    // set up our marker
6351
				$excerpt= str_replace($context[0], $marker, $excerpt); // swap out the link for our marker
6352
				$excerpt = strip_tags($excerpt, '<wpcontext>');        // strip all tags but our context marker
6353
				$excerpt = trim($excerpt);
6354
				$preg_marker = preg_quote($marker, '|');
6355
				$excerpt = preg_replace("|.*?\s(.{0,100}$preg_marker.{0,100})\s.*|s", '$1', $excerpt);
6356
				$excerpt = strip_tags($excerpt); // YES, again, to remove the marker wrapper
6357
				break;
6358
			}
6359
		}
6360
6361
		if ( empty($context) ) // Link to target not found
6362
			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.' ) );
6363
6364
		$pagelinkedfrom = str_replace('&', '&amp;', $pagelinkedfrom);
6365
6366
		$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...
6367
		$pagelinkedfrom = $this->escape( $pagelinkedfrom );
6368
6369
		$comment_post_ID = (int) $post_ID;
6370
		$comment_author = $title;
6371
		$comment_author_email = '';
6372
		$this->escape($comment_author);
6373
		$comment_author_url = $pagelinkedfrom;
6374
		$comment_content = $context;
6375
		$this->escape($comment_content);
6376
		$comment_type = 'pingback';
6377
6378
		$commentdata = compact(
6379
			'comment_post_ID', 'comment_author', 'comment_author_url', 'comment_author_email',
6380
			'comment_content', 'comment_type', 'remote_source', 'remote_source_original'
6381
		);
6382
6383
		$comment_ID = wp_new_comment($commentdata);
6384
6385
		/**
6386
		 * Fires after a post pingback has been sent.
6387
		 *
6388
		 * @since 0.71
6389
		 *
6390
		 * @param int $comment_ID Comment ID.
6391
		 */
6392
		do_action( 'pingback_post', $comment_ID );
6393
6394
		return sprintf(__('Pingback from %1$s to %2$s registered. Keep the web talking! :-)'), $pagelinkedfrom, $pagelinkedto);
6395
	}
6396
6397
	/**
6398
	 * Retrieve array of URLs that pingbacked the given URL.
6399
	 *
6400
	 * Specs on http://www.aquarionics.com/misc/archives/blogite/0198.html
6401
	 *
6402
	 * @since 1.5.0
6403
	 *
6404
	 * @param string $url
6405
	 * @return array|IXR_Error
6406
	 */
6407
	public function pingback_extensions_getPingbacks( $url ) {
6408
		/** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
6409
		do_action( 'xmlrpc_call', 'pingback.extensions.getPingbacks' );
6410
6411
		$url = $this->escape( $url );
6412
6413
		$post_ID = url_to_postid($url);
0 ignored issues
show
It seems like $url defined by $this->escape($url) on line 6411 can also be of type array or null; however, url_to_postid() 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...
6414
		if ( !$post_ID ) {
6415
			// We aren't sure that the resource is available and/or pingback enabled
6416
	  		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.' ) );
6417
		}
6418
6419
		$actual_post = get_post($post_ID, ARRAY_A);
6420
6421
		if ( !$actual_post ) {
6422
			// No such post = resource not found
6423
	  		return $this->pingback_error( 32, __('The specified target URL does not exist.' ) );
6424
		}
6425
6426
		$comments = $this->db->get_results( $this->db->prepare("SELECT comment_author_url, comment_content, comment_author_IP, comment_type FROM {$this->db->comments} WHERE comment_post_ID = %d", $post_ID) );
6427
6428
		if ( !$comments )
6429
			return array();
6430
6431
		$pingbacks = array();
6432
		foreach ( $comments as $comment ) {
6433
			if ( 'pingback' == $comment->comment_type )
6434
				$pingbacks[] = $comment->comment_author_url;
6435
		}
6436
6437
		return $pingbacks;
6438
	}
6439
6440
	/**
6441
	 * Sends a pingback error based on the given error code and message.
6442
	 *
6443
	 * @since 3.6.0
6444
	 *
6445
	 * @param int    $code    Error code.
6446
	 * @param string $message Error message.
6447
	 * @return IXR_Error Error object.
6448
	 */
6449
	protected function pingback_error( $code, $message ) {
6450
		/**
6451
		 * Filters the XML-RPC pingback error return.
6452
		 *
6453
		 * @since 3.5.1
6454
		 *
6455
		 * @param IXR_Error $error An IXR_Error object containing the error code and message.
6456
		 */
6457
		return apply_filters( 'xmlrpc_pingback_error', new IXR_Error( $code, $message ) );
6458
	}
6459
}
6460