Completed
Push — fix/publicize-twitter-tweaks ( d59431...446e42 )
by Jeremy
80:07 queued 72:39
created

accepts_site_based_authentication()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
use Automattic\Jetpack\Connection\Client;
4
5
require_once dirname( __FILE__ ) . '/json-api-config.php';
6
require_once dirname( __FILE__ ) . '/sal/class.json-api-links.php';
7
require_once dirname( __FILE__ ) . '/sal/class.json-api-metadata.php';
8
require_once dirname( __FILE__ ) . '/sal/class.json-api-date.php';
9
10
// Endpoint
11
abstract class WPCOM_JSON_API_Endpoint {
12
	// The API Object
13
	public $api;
14
15
	// The link-generating utility class
16
	public $links;
17
18
	public $pass_wpcom_user_details = false;
19
20
	// One liner.
21
	public $description;
22
23
	// Object Grouping For Documentation (Users, Posts, Comments)
24
	public $group;
25
26
	// Stats extra value to bump
27
	public $stat;
28
29
	// HTTP Method
30
	public $method = 'GET';
31
32
	// Minimum version of the api for which to serve this endpoint
33
	public $min_version = '0';
34
35
	// Maximum version of the api for which to serve this endpoint
36
	public $max_version = WPCOM_JSON_API__CURRENT_VERSION;
37
38
	// Path at which to serve this endpoint: sprintf() format.
39
	public $path = '';
40
41
	// Identifiers to fill sprintf() formatted $path
42
	public $path_labels = array();
43
44
	// Accepted query parameters
45
	public $query = array(
46
		// Parameter name
47
		'context'       => array(
48
			// Default value => description
49
			'display' => 'Formats the output as HTML for display.  Shortcodes are parsed, paragraph tags are added, etc..',
50
			// Other possible values => description
51
			'edit'    => 'Formats the output for editing.  Shortcodes are left unparsed, significant whitespace is kept, etc..',
52
		),
53
		'http_envelope' => array(
54
			'false' => '',
55
			'true'  => 'Some environments (like in-browser JavaScript or Flash) block or divert responses with a non-200 HTTP status code.  Setting this parameter will force the HTTP status code to always be 200.  The JSON response is wrapped in an "envelope" containing the "real" HTTP status code and headers.',
56
		),
57
		'pretty'        => array(
58
			'false' => '',
59
			'true'  => 'Output pretty JSON',
60
		),
61
		'meta'          => "(string) Optional. Loads data from the endpoints found in the 'meta' part of the response. Comma-separated list. Example: meta=site,likes",
62
		'fields'        => '(string) Optional. Returns specified fields only. Comma-separated list. Example: fields=ID,title',
63
		// Parameter name => description (default value is empty)
64
		'callback'      => '(string) An optional JSONP callback function.',
65
	);
66
67
	// Response format
68
	public $response_format = array();
69
70
	// Request format
71
	public $request_format = array();
72
73
	// Is this endpoint still in testing phase?  If so, not available to the public.
74
	public $in_testing = false;
75
76
	// Is this endpoint still allowed if the site in question is flagged?
77
	public $allowed_if_flagged = false;
78
79
	// Is this endpoint allowed if the site is red flagged?
80
	public $allowed_if_red_flagged = false;
81
82
	// Is this endpoint allowed if the site is deleted?
83
	public $allowed_if_deleted = false;
84
85
	/**
86
	 * @var string Version of the API
87
	 */
88
	public $version = '';
89
90
	/**
91
	 * @var string Example request to make
92
	 */
93
	public $example_request = '';
94
95
	/**
96
	 * @var string Example request data (for POST methods)
97
	 */
98
	public $example_request_data = '';
99
100
	/**
101
	 * @var string Example response from $example_request
102
	 */
103
	public $example_response = '';
104
105
	/**
106
	 * @var bool Set to true if the endpoint implements its own filtering instead of the standard `fields` query method
107
	 */
108
	public $custom_fields_filtering = false;
109
110
	/**
111
	 * @var bool Set to true if the endpoint accepts all cross origin requests. You probably should not set this flag.
112
	 */
113
	public $allow_cross_origin_request = false;
114
115
	/**
116
	 * @var bool Set to true if the endpoint can recieve unauthorized POST requests.
117
	 */
118
	public $allow_unauthorized_request = false;
119
120
	/**
121
	 * @var bool Set to true if the endpoint should accept site based (not user based) authentication.
122
	 */
123
	public $allow_jetpack_site_auth = false;
124
125
	/**
126
	 * @var bool Set to true if the endpoint should accept auth from an upload token.
127
	 */
128
	public $allow_upload_token_auth = false;
129
130
	/**
131
	 * @var bool Set to true if the endpoint should require auth from a Rewind auth token.
132
	 */
133
	public $require_rewind_auth = false;
134
135
	function __construct( $args ) {
136
		$defaults = array(
137
			'in_testing'                 => false,
138
			'allowed_if_flagged'         => false,
139
			'allowed_if_red_flagged'     => false,
140
			'allowed_if_deleted'         => false,
141
			'description'                => '',
142
			'group'                      => '',
143
			'method'                     => 'GET',
144
			'path'                       => '/',
145
			'min_version'                => '0',
146
			'max_version'                => WPCOM_JSON_API__CURRENT_VERSION,
147
			'force'                      => '',
148
			'deprecated'                 => false,
149
			'new_version'                => WPCOM_JSON_API__CURRENT_VERSION,
150
			'jp_disabled'                => false,
151
			'path_labels'                => array(),
152
			'request_format'             => array(),
153
			'response_format'            => array(),
154
			'query_parameters'           => array(),
155
			'version'                    => 'v1',
156
			'example_request'            => '',
157
			'example_request_data'       => '',
158
			'example_response'           => '',
159
			'required_scope'             => '',
160
			'pass_wpcom_user_details'    => false,
161
			'custom_fields_filtering'    => false,
162
			'allow_cross_origin_request' => false,
163
			'allow_unauthorized_request' => false,
164
			'allow_jetpack_site_auth'    => false,
165
			'allow_upload_token_auth'    => false,
166
		);
167
168
		$args = wp_parse_args( $args, $defaults );
0 ignored issues
show
Documentation introduced by
$defaults is of type array<string,false|strin...d_token_auth":"false"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
169
170
		$this->in_testing = $args['in_testing'];
171
172
		$this->allowed_if_flagged     = $args['allowed_if_flagged'];
173
		$this->allowed_if_red_flagged = $args['allowed_if_red_flagged'];
174
		$this->allowed_if_deleted     = $args['allowed_if_deleted'];
175
176
		$this->description = $args['description'];
177
		$this->group       = $args['group'];
178
		$this->stat        = $args['stat'];
179
		$this->force       = $args['force'];
0 ignored issues
show
Bug introduced by
The property force does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
180
		$this->jp_disabled = $args['jp_disabled'];
0 ignored issues
show
Bug introduced by
The property jp_disabled does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
181
182
		$this->method      = $args['method'];
183
		$this->path        = $args['path'];
184
		$this->path_labels = $args['path_labels'];
185
		$this->min_version = $args['min_version'];
186
		$this->max_version = $args['max_version'];
187
		$this->deprecated  = $args['deprecated'];
0 ignored issues
show
Bug introduced by
The property deprecated does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
188
		$this->new_version = $args['new_version'];
0 ignored issues
show
Bug introduced by
The property new_version does not seem to exist. Did you mean version?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
189
190
		// Ensure max version is not less than min version
191
		if ( version_compare( $this->min_version, $this->max_version, '>' ) ) {
192
			$this->max_version = $this->min_version;
193
		}
194
195
		$this->pass_wpcom_user_details = $args['pass_wpcom_user_details'];
196
		$this->custom_fields_filtering = (bool) $args['custom_fields_filtering'];
197
198
		$this->allow_cross_origin_request = (bool) $args['allow_cross_origin_request'];
199
		$this->allow_unauthorized_request = (bool) $args['allow_unauthorized_request'];
200
		$this->allow_jetpack_site_auth    = (bool) $args['allow_jetpack_site_auth'];
201
		$this->allow_upload_token_auth    = (bool) $args['allow_upload_token_auth'];
202
		$this->require_rewind_auth        = isset( $args['require_rewind_auth'] ) ? (bool) $args['require_rewind_auth'] : false;
203
204
		$this->version = $args['version'];
205
206
		$this->required_scope = $args['required_scope'];
0 ignored issues
show
Bug introduced by
The property required_scope does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
207
208 View Code Duplication
		if ( $this->request_format ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->request_format of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
209
			$this->request_format = array_filter( array_merge( $this->request_format, $args['request_format'] ) );
210
		} else {
211
			$this->request_format = $args['request_format'];
212
		}
213
214 View Code Duplication
		if ( $this->response_format ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->response_format of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
215
			$this->response_format = array_filter( array_merge( $this->response_format, $args['response_format'] ) );
216
		} else {
217
			$this->response_format = $args['response_format'];
218
		}
219
220
		if ( false === $args['query_parameters'] ) {
221
			$this->query = array();
222
		} elseif ( is_array( $args['query_parameters'] ) ) {
223
			$this->query = array_filter( array_merge( $this->query, $args['query_parameters'] ) );
224
		}
225
226
		$this->api   = WPCOM_JSON_API::init(); // Auto-add to WPCOM_JSON_API
227
		$this->links = WPCOM_JSON_API_Links::getInstance();
228
229
		/** Example Request/Response */
230
231
		// Examples for endpoint documentation request
232
		$this->example_request      = $args['example_request'];
233
		$this->example_request_data = $args['example_request_data'];
234
		$this->example_response     = $args['example_response'];
235
236
		$this->api->add( $this );
237
	}
238
239
	// Get all query args.  Prefill with defaults
240
	function query_args( $return_default_values = true, $cast_and_filter = true ) {
241
		$args = array_intersect_key( $this->api->query, $this->query );
242
243
		if ( ! $cast_and_filter ) {
244
			return $args;
245
		}
246
247
		return $this->cast_and_filter( $args, $this->query, $return_default_values );
248
	}
249
250
	// Get POST body data
251
	function input( $return_default_values = true, $cast_and_filter = true ) {
252
		$input        = trim( $this->api->post_body );
253
		$content_type = $this->api->content_type;
254
		if ( $content_type ) {
255
			list ( $content_type ) = explode( ';', $content_type );
256
		}
257
		$content_type = trim( $content_type );
258
		switch ( $content_type ) {
259
			case 'application/json':
260
			case 'application/x-javascript':
261
			case 'text/javascript':
262
			case 'text/x-javascript':
263
			case 'text/x-json':
264
			case 'text/json':
265
				$return = json_decode( $input, true );
266
267
				if ( function_exists( 'json_last_error' ) ) {
268
					if ( JSON_ERROR_NONE !== json_last_error() ) { // phpcs:ignore PHPCompatibility
269
						return null;
270
					}
271
				} else {
272
					if ( is_null( $return ) && json_encode( null ) !== $input ) {
273
						return null;
274
					}
275
				}
276
277
				break;
278
			case 'multipart/form-data':
279
				$return = array_merge( stripslashes_deep( $_POST ), $_FILES );
280
				break;
281
			case 'application/x-www-form-urlencoded':
282
				// attempt JSON first, since probably a curl command
283
				$return = json_decode( $input, true );
284
285
				if ( is_null( $return ) ) {
286
					wp_parse_str( $input, $return );
287
				}
288
289
				break;
290
			default:
291
				wp_parse_str( $input, $return );
0 ignored issues
show
Bug introduced by
The variable $return seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
292
				break;
293
		}
294
295
		if ( isset( $this->api->query['force'] )
296
			&& 'secure' === $this->api->query['force']
297
			&& isset( $return['secure_key'] ) ) {
298
			$this->api->post_body      = $this->get_secure_body( $return['secure_key'] );
299
			$this->api->query['force'] = false;
300
			return $this->input( $return_default_values, $cast_and_filter );
301
		}
302
303
		if ( $cast_and_filter ) {
304
			$return = $this->cast_and_filter( $return, $this->request_format, $return_default_values );
0 ignored issues
show
Bug introduced by
The variable $return 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...
305
		}
306
		return $return;
307
	}
308
309
310
	protected function get_secure_body( $secure_key ) {
311
		$response = Client::wpcom_json_api_request_as_blog(
312
			sprintf( '/sites/%d/secure-request', Jetpack_Options::get_option( 'id' ) ),
313
			'1.1',
314
			array( 'method' => 'POST' ),
315
			array( 'secure_key' => $secure_key )
0 ignored issues
show
Documentation introduced by
array('secure_key' => $secure_key) is of type array<string,?,{"secure_key":"?"}>, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
316
		);
317
		if ( 200 !== $response['response']['code'] ) {
318
			return null;
319
		}
320
		return json_decode( $response['body'], true );
321
	}
322
323
	function cast_and_filter( $data, $documentation, $return_default_values = false, $for_output = false ) {
324
		$return_as_object = false;
325
		if ( is_object( $data ) ) {
326
			// @todo this should probably be a deep copy if $data can ever have nested objects
327
			$data             = (array) $data;
328
			$return_as_object = true;
329
		} elseif ( ! is_array( $data ) ) {
330
			return $data;
331
		}
332
333
		$boolean_arg = array( 'false', 'true' );
334
		$naeloob_arg = array( 'true', 'false' );
335
336
		$return = array();
337
338
		foreach ( $documentation as $key => $description ) {
339
			if ( is_array( $description ) ) {
340
				// String or boolean array keys only
341
				$whitelist = array_keys( $description );
342
343
				if ( $whitelist === $boolean_arg || $whitelist === $naeloob_arg ) {
344
					// Truthiness
345
					if ( isset( $data[ $key ] ) ) {
346
						$return[ $key ] = (bool) WPCOM_JSON_API::is_truthy( $data[ $key ] );
347
					} elseif ( $return_default_values ) {
348
						$return[ $key ] = $whitelist === $naeloob_arg; // Default to true for naeloob_arg and false for boolean_arg.
349
					}
350
				} elseif ( isset( $data[ $key ] ) && isset( $description[ $data[ $key ] ] ) ) {
351
					// String Key
352
					$return[ $key ] = (string) $data[ $key ];
353
				} elseif ( $return_default_values ) {
354
					// Default value
355
					$return[ $key ] = (string) current( $whitelist );
356
				}
357
358
				continue;
359
			}
360
361
			$types = $this->parse_types( $description );
362
			$type  = array_shift( $types );
363
364
			// Explicit default - string and int only for now.  Always set these reguardless of $return_default_values
365
			if ( isset( $type['default'] ) ) {
366
				if ( ! isset( $data[ $key ] ) ) {
367
					$data[ $key ] = $type['default'];
368
				}
369
			}
370
371
			if ( ! isset( $data[ $key ] ) ) {
372
				continue;
373
			}
374
375
			$this->cast_and_filter_item( $return, $type, $key, $data[ $key ], $types, $for_output );
376
		}
377
378
		if ( $return_as_object ) {
379
			return (object) $return;
380
		}
381
382
		return $return;
383
	}
384
385
	/**
386
	 * Casts $value according to $type.
387
	 * Handles fallbacks for certain values of $type when $value is not that $type
388
	 * Currently, only handles fallback between string <-> array (two way), from string -> false (one way), and from object -> false (one way),
389
	 * and string -> object (one way)
390
	 *
391
	 * Handles "child types" - array:URL, object:category
392
	 * array:URL means an array of URLs
393
	 * object:category means a hash of categories
394
	 *
395
	 * Handles object typing - object>post means an object of type post
396
	 */
397
	function cast_and_filter_item( &$return, $type, $key, $value, $types = array(), $for_output = false ) {
398
		if ( is_string( $type ) ) {
399
			$type = compact( 'type' );
400
		}
401
402
		switch ( $type['type'] ) {
403
			case 'false':
404
				$return[ $key ] = false;
405
				break;
406
			case 'url':
407
				if ( is_object( $value ) && isset( $value->url ) && false !== strpos( $value->url, 'https://videos.files.wordpress.com/' ) ) {
408
					$value = $value->url;
409
				}
410
				// Check for string since esc_url_raw() expects one.
411
				if ( ! is_string( $value ) ) {
412
					break;
413
				}
414
				$return[ $key ] = (string) esc_url_raw( $value );
415
				break;
416
			case 'string':
417
				// Fallback string -> array, or for string -> object
418
				if ( is_array( $value ) || is_object( $value ) ) {
419 View Code Duplication
					if ( ! empty( $types[0] ) ) {
420
						$next_type = array_shift( $types );
421
						return $this->cast_and_filter_item( $return, $next_type, $key, $value, $types, $for_output );
422
					}
423
				}
424
425
				// Fallback string -> false
426 View Code Duplication
				if ( ! is_string( $value ) ) {
427
					if ( ! empty( $types[0] ) && 'false' === $types[0]['type'] ) {
428
						$next_type = array_shift( $types );
429
						return $this->cast_and_filter_item( $return, $next_type, $key, $value, $types, $for_output );
430
					}
431
				}
432
				$return[ $key ] = (string) $value;
433
				break;
434
			case 'html':
435
				$return[ $key ] = (string) $value;
436
				break;
437
			case 'safehtml':
438
				$return[ $key ] = wp_kses( (string) $value, wp_kses_allowed_html() );
439
				break;
440
			case 'zip':
441
			case 'media':
442
				if ( is_array( $value ) ) {
443
					if ( isset( $value['name'] ) && is_array( $value['name'] ) ) {
444
						// It's a $_FILES array
445
						// Reformat into array of $_FILES items
446
						$files = array();
447
448
						foreach ( $value['name'] as $k => $v ) {
449
							$files[ $k ] = array();
450
							foreach ( array_keys( $value ) as $file_key ) {
451
								$files[ $k ][ $file_key ] = $value[ $file_key ][ $k ];
452
							}
453
						}
454
455
						$return[ $key ] = $files;
456
						break;
457
					}
458
				} else {
459
					// no break - treat as 'array'
460
				}
461
				// nobreak
462
			case 'array':
463
				// Fallback array -> string
464 View Code Duplication
				if ( is_string( $value ) ) {
465
					if ( ! empty( $types[0] ) ) {
466
						$next_type = array_shift( $types );
467
						return $this->cast_and_filter_item( $return, $next_type, $key, $value, $types, $for_output );
468
					}
469
				}
470
471 View Code Duplication
				if ( isset( $type['children'] ) ) {
472
					$children = array();
473
					foreach ( (array) $value as $k => $child ) {
474
						$this->cast_and_filter_item( $children, $type['children'], $k, $child, array(), $for_output );
475
					}
476
					$return[ $key ] = (array) $children;
477
					break;
478
				}
479
480
				$return[ $key ] = (array) $value;
481
				break;
482
			case 'iso 8601 datetime':
483
			case 'datetime':
484
				// (string)s
485
				$dates = $this->parse_date( (string) $value );
486
				if ( $for_output ) {
487
					$return[ $key ] = $this->format_date( $dates[1], $dates[0] );
488
				} else {
489
					list( $return[ $key ], $return[ "{$key}_gmt" ] ) = $dates;
490
				}
491
				break;
492
			case 'float':
493
				$return[ $key ] = (float) $value;
494
				break;
495
			case 'int':
496
			case 'integer':
497
				$return[ $key ] = (int) $value;
498
				break;
499
			case 'bool':
500
			case 'boolean':
501
				$return[ $key ] = (bool) WPCOM_JSON_API::is_truthy( $value );
502
				break;
503
			case 'object':
504
				// Fallback object -> false
505 View Code Duplication
				if ( is_scalar( $value ) || is_null( $value ) ) {
506
					if ( ! empty( $types[0] ) && 'false' === $types[0]['type'] ) {
507
						return $this->cast_and_filter_item( $return, 'false', $key, $value, $types, $for_output );
508
					}
509
				}
510
511 View Code Duplication
				if ( isset( $type['children'] ) ) {
512
					$children = array();
513
					foreach ( (array) $value as $k => $child ) {
514
						$this->cast_and_filter_item( $children, $type['children'], $k, $child, array(), $for_output );
515
					}
516
					$return[ $key ] = (object) $children;
517
					break;
518
				}
519
520
				if ( isset( $type['subtype'] ) ) {
521
					return $this->cast_and_filter_item( $return, $type['subtype'], $key, $value, $types, $for_output );
522
				}
523
524
				$return[ $key ] = (object) $value;
525
				break;
526
			case 'post':
527
				$return[ $key ] = (object) $this->cast_and_filter( $value, $this->post_object_format, false, $for_output );
528
				break;
529
			case 'comment':
530
				$return[ $key ] = (object) $this->cast_and_filter( $value, $this->comment_object_format, false, $for_output );
531
				break;
532
			case 'tag':
533
			case 'category':
534
				$docs = array(
535
					'ID'          => '(int)',
536
					'name'        => '(string)',
537
					'slug'        => '(string)',
538
					'description' => '(HTML)',
539
					'post_count'  => '(int)',
540
					'feed_url'    => '(string)',
541
					'meta'        => '(object)',
542
				);
543
				if ( 'category' === $type['type'] ) {
544
					$docs['parent'] = '(int)';
545
				}
546
				$return[ $key ] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
547
				break;
548
			case 'post_reference':
549 View Code Duplication
			case 'comment_reference':
550
				$docs           = array(
551
					'ID'    => '(int)',
552
					'type'  => '(string)',
553
					'title' => '(string)',
554
					'link'  => '(URL)',
555
				);
556
				$return[ $key ] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
557
				break;
558 View Code Duplication
			case 'geo':
559
				$docs           = array(
560
					'latitude'  => '(float)',
561
					'longitude' => '(float)',
562
					'address'   => '(string)',
563
				);
564
				$return[ $key ] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
565
				break;
566
			case 'author':
567
				$docs           = array(
568
					'ID'             => '(int)',
569
					'user_login'     => '(string)',
570
					'login'          => '(string)',
571
					'email'          => '(string|false)',
572
					'name'           => '(string)',
573
					'first_name'     => '(string)',
574
					'last_name'      => '(string)',
575
					'nice_name'      => '(string)',
576
					'URL'            => '(URL)',
577
					'avatar_URL'     => '(URL)',
578
					'profile_URL'    => '(URL)',
579
					'is_super_admin' => '(bool)',
580
					'roles'          => '(array:string)',
581
					'ip_address'     => '(string|false)',
582
				);
583
				$return[ $key ] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
584
				break;
585 View Code Duplication
			case 'role':
586
				$docs           = array(
587
					'name'         => '(string)',
588
					'display_name' => '(string)',
589
					'capabilities' => '(object:boolean)',
590
				);
591
				$return[ $key ] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
592
				break;
593
			case 'attachment':
594
				$docs           = array(
595
					'ID'        => '(int)',
596
					'URL'       => '(URL)',
597
					'guid'      => '(string)',
598
					'mime_type' => '(string)',
599
					'width'     => '(int)',
600
					'height'    => '(int)',
601
					'duration'  => '(int)',
602
				);
603
				$return[ $key ] = (object) $this->cast_and_filter(
604
					$value,
605
					/**
606
					* Filter the documentation returned for a post attachment.
607
					*
608
					* @module json-api
609
					*
610
					* @since 1.9.0
611
					*
612
					* @param array $docs Array of documentation about a post attachment.
613
					*/
614
					apply_filters( 'wpcom_json_api_attachment_cast_and_filter', $docs ),
615
					false,
616
					$for_output
617
				);
618
				break;
619
			case 'metadata':
620
				$docs           = array(
621
					'id'             => '(int)',
622
					'key'            => '(string)',
623
					'value'          => '(string|false|float|int|array|object)',
624
					'previous_value' => '(string)',
625
					'operation'      => '(string)',
626
				);
627
				$return[ $key ] = (object) $this->cast_and_filter(
628
					$value,
629
					/** This filter is documented in class.json-api-endpoints.php */
630
					apply_filters( 'wpcom_json_api_attachment_cast_and_filter', $docs ),
631
					false,
632
					$for_output
633
				);
634
				break;
635
			case 'plugin':
636
				$docs           = array(
637
					'id'           => '(safehtml) The plugin\'s ID',
638
					'slug'         => '(safehtml) The plugin\'s Slug',
639
					'active'       => '(boolean)  The plugin status.',
640
					'update'       => '(object)   The plugin update info.',
641
					'name'         => '(safehtml) The name of the plugin.',
642
					'plugin_url'   => '(url)      Link to the plugin\'s web site.',
643
					'version'      => '(safehtml) The plugin version number.',
644
					'description'  => '(safehtml) Description of what the plugin does and/or notes from the author',
645
					'author'       => '(safehtml) The plugin author\'s name',
646
					'author_url'   => '(url)      The plugin author web site address',
647
					'network'      => '(boolean)  Whether the plugin can only be activated network wide.',
648
					'autoupdate'   => '(boolean)  Whether the plugin is auto updated',
649
					'log'          => '(array:safehtml) An array of update log strings.',
650
					'action_links' => '(array) An array of action links that the plugin uses.',
651
				);
652
				$return[ $key ] = (object) $this->cast_and_filter(
653
					$value,
654
					/**
655
					* Filter the documentation returned for a plugin.
656
					*
657
					* @module json-api
658
					*
659
					* @since 3.1.0
660
					*
661
					* @param array $docs Array of documentation about a plugin.
662
					*/
663
					apply_filters( 'wpcom_json_api_plugin_cast_and_filter', $docs ),
664
					false,
665
					$for_output
666
				);
667
				break;
668
			case 'plugin_v1_2':
669
				$docs           = class_exists( 'Jetpack_JSON_API_Get_Plugins_v1_2_Endpoint' )
670
				? Jetpack_JSON_API_Get_Plugins_v1_2_Endpoint::$_response_format
671
				: Jetpack_JSON_API_Plugins_Endpoint::$_response_format_v1_2;
0 ignored issues
show
Bug introduced by
The property _response_format_v1_2 cannot be accessed from this context as it is declared private in class Jetpack_JSON_API_Plugins_Endpoint.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
672
				$return[ $key ] = (object) $this->cast_and_filter(
673
					$value,
674
					/**
675
					* Filter the documentation returned for a plugin.
676
					*
677
					* @module json-api
678
					*
679
					* @since 3.1.0
680
					*
681
					* @param array $docs Array of documentation about a plugin.
682
					*/
683
					apply_filters( 'wpcom_json_api_plugin_cast_and_filter', $docs ),
684
					false,
685
					$for_output
686
				);
687
				break;
688
			case 'file_mod_capabilities':
689
				$docs           = array(
690
					'reasons_modify_files_unavailable' => '(array) The reasons why files can\'t be modified',
691
					'reasons_autoupdate_unavailable'   => '(array) The reasons why autoupdates aren\'t allowed',
692
					'modify_files'                     => '(boolean) true if files can be modified',
693
					'autoupdate_files'                 => '(boolean) true if autoupdates are allowed',
694
				);
695
				$return[ $key ] = (array) $this->cast_and_filter( $value, $docs, false, $for_output );
696
				break;
697
			case 'jetpackmodule':
698
				$docs           = array(
699
					'id'          => '(string)   The module\'s ID',
700
					'active'      => '(boolean)  The module\'s status.',
701
					'name'        => '(string)   The module\'s name.',
702
					'description' => '(safehtml) The module\'s description.',
703
					'sort'        => '(int)      The module\'s display order.',
704
					'introduced'  => '(string)   The Jetpack version when the module was introduced.',
705
					'changed'     => '(string)   The Jetpack version when the module was changed.',
706
					'free'        => '(boolean)  The module\'s Free or Paid status.',
707
					'module_tags' => '(array)    The module\'s tags.',
708
					'override'    => '(string)   The module\'s override. Empty if no override, otherwise \'active\' or \'inactive\'',
709
				);
710
				$return[ $key ] = (object) $this->cast_and_filter(
711
					$value,
712
					/** This filter is documented in class.json-api-endpoints.php */
713
					apply_filters( 'wpcom_json_api_plugin_cast_and_filter', $docs ),
714
					false,
715
					$for_output
716
				);
717
				break;
718
			case 'sharing_button':
719
				$docs           = array(
720
					'ID'         => '(string)',
721
					'name'       => '(string)',
722
					'URL'        => '(string)',
723
					'icon'       => '(string)',
724
					'enabled'    => '(bool)',
725
					'visibility' => '(string)',
726
				);
727
				$return[ $key ] = (array) $this->cast_and_filter( $value, $docs, false, $for_output );
728
				break;
729
			case 'sharing_button_service':
730
				$docs           = array(
731
					'ID'               => '(string) The service identifier',
732
					'name'             => '(string) The service name',
733
					'class_name'       => '(string) Class name for custom style sharing button elements',
734
					'genericon'        => '(string) The Genericon unicode character for the custom style sharing button icon',
735
					'preview_smart'    => '(string) An HTML snippet of a rendered sharing button smart preview',
736
					'preview_smart_js' => '(string) An HTML snippet of the page-wide initialization scripts used for rendering the sharing button smart preview',
737
				);
738
				$return[ $key ] = (array) $this->cast_and_filter( $value, $docs, false, $for_output );
739
				break;
740
			case 'site_keyring':
741
				$docs           = array(
742
					'keyring_id'       => '(int) Keyring ID',
743
					'service'          => '(string) The service name',
744
					'external_user_id' => '(string) External user id for the service',
745
				);
746
				$return[ $key ] = (array) $this->cast_and_filter( $value, $docs, false, $for_output );
747
				break;
748
			case 'taxonomy':
749
				$docs           = array(
750
					'name'         => '(string) The taxonomy slug',
751
					'label'        => '(string) The taxonomy human-readable name',
752
					'labels'       => '(object) Mapping of labels for the taxonomy',
753
					'description'  => '(string) The taxonomy description',
754
					'hierarchical' => '(bool) Whether the taxonomy is hierarchical',
755
					'public'       => '(bool) Whether the taxonomy is public',
756
					'capabilities' => '(object) Mapping of current user capabilities for the taxonomy',
757
				);
758
				$return[ $key ] = (array) $this->cast_and_filter( $value, $docs, false, $for_output );
759
				break;
760
761
			default:
762
				$method_name = $type['type'] . '_docs';
763
				if ( method_exists( 'WPCOM_JSON_API_Jetpack_Overrides', $method_name ) ) {
764
					$docs = WPCOM_JSON_API_Jetpack_Overrides::$method_name();
765
				}
766
767
				if ( ! empty( $docs ) ) {
768
					$return[ $key ] = (object) $this->cast_and_filter(
769
						$value,
770
						/** This filter is documented in class.json-api-endpoints.php */
771
						apply_filters( 'wpcom_json_api_plugin_cast_and_filter', $docs ),
772
						false,
773
						$for_output
774
					);
775
				} else {
776
					trigger_error( "Unknown API casting type {$type['type']}", E_USER_WARNING );
777
				}
778
		}
779
	}
780
781
	function parse_types( $text ) {
782
		if ( ! preg_match( '#^\(([^)]+)\)#', ltrim( $text ), $matches ) ) {
783
			return 'none';
784
		}
785
786
		$types  = explode( '|', strtolower( $matches[1] ) );
787
		$return = array();
788
		foreach ( $types as $type ) {
789
			foreach ( array(
790
				':' => 'children',
791
				'>' => 'subtype',
792
				'=' => 'default',
793
			) as $operator => $meaning ) {
794
				if ( false !== strpos( $type, $operator ) ) {
795
					$item     = explode( $operator, $type, 2 );
796
					$return[] = array(
797
						'type'   => $item[0],
798
						$meaning => $item[1],
799
					);
800
					continue 2;
801
				}
802
			}
803
			$return[] = compact( 'type' );
804
		}
805
806
		return $return;
807
	}
808
809
	/**
810
	 * Checks if the endpoint is publicly displayable
811
	 */
812
	function is_publicly_documentable() {
813
		return '__do_not_document' !== $this->group && true !== $this->in_testing;
814
	}
815
816
	/**
817
	 * Auto generates documentation based on description, method, path, path_labels, and query parameters.
818
	 * Echoes HTML.
819
	 */
820
	function document( $show_description = true ) {
821
		global $wpdb;
822
		$original_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : 'unset';
823
		unset( $GLOBALS['post'] );
824
825
		$doc = $this->generate_documentation();
826
827
		if ( $show_description ) :
828
			?>
829
<caption>
830
	<h1><?php echo wp_kses_post( $doc['method'] ); ?> <?php echo wp_kses_post( $doc['path_labeled'] ); ?></h1>
831
	<p><?php echo wp_kses_post( $doc['description'] ); ?></p>
832
</caption>
833
834
<?php endif; ?>
835
836
		<?php if ( true === $this->deprecated ) { ?>
837
<p><strong>This endpoint is deprecated in favor of version <?php echo floatval( $this->new_version ); ?></strong></p>
0 ignored issues
show
Bug introduced by
The property new_version does not seem to exist. Did you mean version?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
838
<?php } ?>
839
840
<section class="resource-info">
841
	<h2 id="apidoc-resource-info">Resource Information</h2>
842
843
	<table class="api-doc api-doc-resource-parameters api-doc-resource">
844
845
	<thead>
846
		<tr>
847
			<th class="api-index-title" scope="column">&nbsp;</th>
848
			<th class="api-index-title" scope="column">&nbsp;</th>
849
		</tr>
850
	</thead>
851
	<tbody>
852
853
		<tr class="api-index-item">
854
			<th scope="row" class="parameter api-index-item-title">Method</th>
855
			<td class="type api-index-item-title"><?php echo wp_kses_post( $doc['method'] ); ?></td>
856
		</tr>
857
858
		<tr class="api-index-item">
859
			<th scope="row" class="parameter api-index-item-title">URL</th>
860
			<?php
861
			$version = WPCOM_JSON_API__CURRENT_VERSION;
862
			if ( ! empty( $this->max_version ) ) {
863
				$version = $this->max_version;
864
			}
865
			?>
866
			<td class="type api-index-item-title">https://public-api.wordpress.com/rest/v<?php echo floatval( $version ); ?><?php echo wp_kses_post( $doc['path_labeled'] ); ?></td>
867
		</tr>
868
869
		<tr class="api-index-item">
870
			<th scope="row" class="parameter api-index-item-title">Requires authentication?</th>
871
			<?php
872
			$requires_auth = $wpdb->get_row( $wpdb->prepare( 'SELECT requires_authentication FROM rest_api_documentation WHERE `version` = %s AND `path` = %s AND `method` = %s LIMIT 1', $version, untrailingslashit( $doc['path_labeled'] ), $doc['method'] ) );
873
			?>
874
			<td class="type api-index-item-title"><?php echo ( true === (bool) $requires_auth->requires_authentication ? 'Yes' : 'No' ); ?></td>
875
		</tr>
876
877
	</tbody>
878
	</table>
879
880
</section>
881
882
		<?php
883
884
		foreach ( array(
885
			'path'     => 'Method Parameters',
886
			'query'    => 'Query Parameters',
887
			'body'     => 'Request Parameters',
888
			'response' => 'Response Parameters',
889
		) as $doc_section_key => $label ) :
890
			$doc_section = 'response' === $doc_section_key ? $doc['response']['body'] : $doc['request'][ $doc_section_key ];
891
			if ( ! $doc_section ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $doc_section of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
892
				continue;
893
			}
894
895
			$param_label = strtolower( str_replace( ' ', '-', $label ) );
896
			?>
897
898
<section class="<?php echo $param_label; ?>">
899
900
<h2 id="apidoc-<?php echo esc_attr( $doc_section_key ); ?>"><?php echo wp_kses_post( $label ); ?></h2>
901
902
<table class="api-doc api-doc-<?php echo $param_label; ?>-parameters api-doc-<?php echo strtolower( str_replace( ' ', '-', $doc['group'] ) ); ?>">
903
904
<thead>
905
	<tr>
906
		<th class="api-index-title" scope="column">Parameter</th>
907
		<th class="api-index-title" scope="column">Type</th>
908
		<th class="api-index-title" scope="column">Description</th>
909
	</tr>
910
</thead>
911
<tbody>
912
913
			<?php foreach ( $doc_section as $key => $item ) : ?>
914
915
	<tr class="api-index-item">
916
		<th scope="row" class="parameter api-index-item-title"><?php echo wp_kses_post( $key ); ?></th>
917
		<td class="type api-index-item-title"><?php echo wp_kses_post( $item['type'] ); // @todo auto-link? ?></td>
918
		<td class="description api-index-item-body">
919
				<?php
920
921
				$this->generate_doc_description( $item['description'] );
922
923
				?>
924
		</td>
925
	</tr>
926
927
			<?php endforeach; ?>
928
</tbody>
929
</table>
930
</section>
931
<?php endforeach; ?>
932
933
		<?php
934
		if ( 'unset' !== $original_post ) {
935
			$GLOBALS['post'] = $original_post;
936
		}
937
	}
938
939
	function add_http_build_query_to_php_content_example( $matches ) {
940
		$trimmed_match = ltrim( $matches[0] );
941
		$pad           = substr( $matches[0], 0, -1 * strlen( $trimmed_match ) );
942
		$pad           = ltrim( $pad, ' ' );
943
		$return        = '  ' . str_replace( "\n", "\n  ", $matches[0] );
944
		return " http_build_query({$return}{$pad})";
945
	}
946
947
	/**
948
	 * Recursively generates the <dl>'s to document item descriptions.
949
	 * Echoes HTML.
950
	 */
951
	function generate_doc_description( $item ) {
952
		if ( is_array( $item ) ) :
953
			?>
954
955
		<dl>
956
			<?php	foreach ( $item as $description_key => $description_value ) : ?>
957
958
			<dt><?php echo wp_kses_post( $description_key . ':' ); ?></dt>
959
			<dd><?php $this->generate_doc_description( $description_value ); ?></dd>
960
961
			<?php	endforeach; ?>
962
963
		</dl>
964
965
			<?php
966
		else :
967
			echo wp_kses_post( $item );
968
		endif;
969
	}
970
971
	/**
972
	 * Auto generates documentation based on description, method, path, path_labels, and query parameters.
973
	 * Echoes HTML.
974
	 */
975
	function generate_documentation() {
976
		$format       = str_replace( '%d', '%s', $this->path );
977
		$path_labeled = $format;
978
		if ( ! empty( $this->path_labels ) ) {
979
			$path_labeled = vsprintf( $format, array_keys( $this->path_labels ) );
980
		}
981
		$boolean_arg = array( 'false', 'true' );
982
		$naeloob_arg = array( 'true', 'false' );
983
984
		$doc = array(
985
			'description'  => $this->description,
986
			'method'       => $this->method,
987
			'path_format'  => $this->path,
988
			'path_labeled' => $path_labeled,
989
			'group'        => $this->group,
990
			'request'      => array(
991
				'path'  => array(),
992
				'query' => array(),
993
				'body'  => array(),
994
			),
995
			'response'     => array(
996
				'body' => array(),
997
			),
998
		);
999
1000
		foreach ( array(
1001
			'path_labels'     => 'path',
1002
			'query'           => 'query',
1003
			'request_format'  => 'body',
1004
			'response_format' => 'body',
1005
		) as $_property => $doc_item ) {
1006
			foreach ( (array) $this->$_property as $key => $description ) {
1007
				if ( is_array( $description ) ) {
1008
					$description_keys = array_keys( $description );
1009
					if ( $boolean_arg === $description_keys || $naeloob_arg === $description_keys ) {
1010
						$type = '(bool)';
1011
					} else {
1012
						$type = '(string)';
1013
					}
1014
1015
					if ( 'response_format' !== $_property ) {
1016
						// hack - don't show "(default)" in response format
1017
						reset( $description );
1018
						$description_key                 = key( $description );
1019
						$description[ $description_key ] = "(default) {$description[$description_key]}";
1020
					}
1021
				} else {
1022
					$types   = $this->parse_types( $description );
1023
					$type    = array();
1024
					$default = '';
1025
1026
					if ( 'none' == $types ) {
1027
						$types           = array();
1028
						$types[]['type'] = 'none';
1029
					}
1030
1031
					foreach ( $types as $type_array ) {
0 ignored issues
show
Bug introduced by
The expression $types of type string|array 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...
1032
						$type[] = $type_array['type'];
1033
						if ( isset( $type_array['default'] ) ) {
1034
							$default = $type_array['default'];
1035
							if ( 'string' === $type_array['type'] ) {
1036
								$default = "'$default'";
1037
							}
1038
						}
1039
					}
1040
					$type                       = '(' . join( '|', $type ) . ')';
1041
					$noop                       = ''; // skip an index in list below
0 ignored issues
show
Unused Code introduced by
$noop is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1042
					list( $noop, $description ) = explode( ')', $description, 2 );
0 ignored issues
show
Unused Code introduced by
The assignment to $noop is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
1043
					$description                = trim( $description );
1044
					if ( $default ) {
1045
						$description .= " Default: $default.";
1046
					}
1047
				}
1048
1049
				$item = compact( 'type', 'description' );
1050
1051
				if ( 'response_format' === $_property ) {
1052
					$doc['response'][ $doc_item ][ $key ] = $item;
1053
				} else {
1054
					$doc['request'][ $doc_item ][ $key ] = $item;
1055
				}
1056
			}
1057
		}
1058
1059
		return $doc;
1060
	}
1061
1062
	function user_can_view_post( $post_id ) {
1063
		$post = get_post( $post_id );
1064
		if ( ! $post || is_wp_error( $post ) ) {
1065
			return false;
1066
		}
1067
1068 View Code Duplication
		if ( 'inherit' === $post->post_status ) {
1069
			$parent_post     = get_post( $post->post_parent );
1070
			$post_status_obj = get_post_status_object( $parent_post->post_status );
1071
		} else {
1072
			$post_status_obj = get_post_status_object( $post->post_status );
1073
		}
1074
1075
		if ( ! $post_status_obj->public ) {
1076
			if ( is_user_logged_in() ) {
1077
				if ( $post_status_obj->protected ) {
1078
					if ( ! current_user_can( 'edit_post', $post->ID ) ) {
1079
						return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unauthorized'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1080
					}
1081
				} elseif ( $post_status_obj->private ) {
1082
					if ( ! current_user_can( 'read_post', $post->ID ) ) {
1083
						return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unauthorized'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1084
					}
1085
				} elseif ( in_array( $post->post_status, array( 'inherit', 'trash' ) ) ) {
1086
					if ( ! current_user_can( 'edit_post', $post->ID ) ) {
1087
						return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unauthorized'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1088
					}
1089
				} elseif ( 'auto-draft' === $post->post_status ) {
1090
					// allow auto-drafts
1091
				} else {
1092
					return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unauthorized'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1093
				}
1094
			} else {
1095
				return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unauthorized'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1096
			}
1097
		}
1098
1099 View Code Duplication
		if (
1100
			-1 == get_option( 'blog_public' ) &&
1101
			/**
1102
			 * Filter access to a specific post.
1103
			 *
1104
			 * @module json-api
1105
			 *
1106
			 * @since 3.4.0
1107
			 *
1108
			 * @param bool current_user_can( 'read_post', $post->ID ) Can the current user access the post.
1109
			 * @param WP_Post $post Post data.
1110
			 */
1111
			! apply_filters(
1112
				'wpcom_json_api_user_can_view_post',
1113
				current_user_can( 'read_post', $post->ID ),
1114
				$post
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1115
			)
1116
		) {
1117
			return new WP_Error(
1118
				'unauthorized',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unauthorized'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1119
				'User cannot view post',
1120
				array(
1121
					'status_code' => 403,
1122
					'error'       => 'private_blog',
1123
				)
1124
			);
1125
		}
1126
1127 View Code Duplication
		if ( strlen( $post->post_password ) && ! current_user_can( 'edit_post', $post->ID ) ) {
1128
			return new WP_Error(
1129
				'unauthorized',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unauthorized'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1130
				'User cannot view password protected post',
1131
				array(
1132
					'status_code' => 403,
1133
					'error'       => 'password_protected',
1134
				)
1135
			);
1136
		}
1137
1138
		return true;
1139
	}
1140
1141
	/**
1142
	 * Returns author object.
1143
	 *
1144
	 * @param object $author user ID, user row, WP_User object, comment row, post row
1145
	 * @param bool   $show_email_and_ip output the author's email address and IP address?
1146
	 *
1147
	 * @return object
1148
	 */
1149
	function get_author( $author, $show_email_and_ip = false ) {
1150
		$ip_address = isset( $author->comment_author_IP ) ? $author->comment_author_IP : '';
1151
1152
		if ( isset( $author->comment_author_email ) ) {
1153
			$ID          = 0;
1154
			$login       = '';
1155
			$email       = $author->comment_author_email;
1156
			$name        = $author->comment_author;
1157
			$first_name  = '';
1158
			$last_name   = '';
1159
			$URL         = $author->comment_author_url;
1160
			$avatar_URL  = $this->api->get_avatar_url( $author );
1161
			$profile_URL = 'https://en.gravatar.com/' . md5( strtolower( trim( $email ) ) );
1162
			$nice        = '';
1163
			$site_id     = -1;
1164
1165
			// Comment author URLs and Emails are sent through wp_kses() on save, which replaces "&" with "&amp;"
1166
			// "&" is the only email/URL character altered by wp_kses()
1167
			foreach ( array( 'email', 'URL' ) as $field ) {
1168
				$$field = str_replace( '&amp;', '&', $$field );
1169
			}
1170
		} else {
1171
			if ( isset( $author->user_id ) && $author->user_id ) {
1172
				$author = $author->user_id;
1173
			} elseif ( isset( $author->user_email ) ) {
1174
				$author = $author->ID;
1175
			} elseif ( isset( $author->post_author ) ) {
1176
				// then $author is a Post Object.
1177
				if ( 0 == $author->post_author ) {
1178
					return null;
1179
				}
1180
				/**
1181
				 * Filter whether the current site is a Jetpack site.
1182
				 *
1183
				 * @module json-api
1184
				 *
1185
				 * @since 3.3.0
1186
				 *
1187
				 * @param bool false Is the current site a Jetpack site. Default to false.
1188
				 * @param int get_current_blog_id() Blog ID.
1189
				 */
1190
				$is_jetpack = true === apply_filters( 'is_jetpack_site', false, get_current_blog_id() );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with get_current_blog_id().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1191
				$post_id    = $author->ID;
1192
				if ( $is_jetpack && ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) {
1193
					$ID         = get_post_meta( $post_id, '_jetpack_post_author_external_id', true );
1194
					$email      = get_post_meta( $post_id, '_jetpack_author_email', true );
1195
					$login      = '';
1196
					$name       = get_post_meta( $post_id, '_jetpack_author', true );
1197
					$first_name = '';
1198
					$last_name  = '';
1199
					$URL        = '';
1200
					$nice       = '';
1201
				} else {
1202
					$author = $author->post_author;
1203
				}
1204
			}
1205
1206
			if ( ! isset( $ID ) ) {
1207
				$user = get_user_by( 'id', $author );
1208
				if ( ! $user || is_wp_error( $user ) ) {
1209
					trigger_error( 'Unknown user', E_USER_WARNING );
1210
1211
					return null;
1212
				}
1213
				$ID         = $user->ID;
1214
				$email      = $user->user_email;
1215
				$login      = $user->user_login;
1216
				$name       = $user->display_name;
1217
				$first_name = $user->first_name;
1218
				$last_name  = $user->last_name;
1219
				$URL        = $user->user_url;
1220
				$nice       = $user->user_nicename;
1221
			}
1222
			if ( defined( 'IS_WPCOM' ) && IS_WPCOM && ! $is_jetpack ) {
0 ignored issues
show
Bug introduced by
The variable $is_jetpack 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...
1223
				$active_blog = get_active_blog_for_user( $ID );
1224
				$site_id     = $active_blog->blog_id;
1225
				if ( $site_id > -1 ) {
1226
					$site_visible = (
1227
						-1 != $active_blog->public ||
1228
						is_private_blog_user( $site_id, get_current_user_id() )
1229
					);
1230
				}
1231
				$profile_URL = "https://en.gravatar.com/{$login}";
0 ignored issues
show
Bug introduced by
The variable $login 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...
1232
			} else {
1233
				$profile_URL = 'https://en.gravatar.com/' . md5( strtolower( trim( $email ) ) );
0 ignored issues
show
Bug introduced by
The variable $email 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...
1234
				$site_id     = -1;
1235
			}
1236
1237
			$avatar_URL = $this->api->get_avatar_url( $email );
1238
		}
1239
1240
		if ( $show_email_and_ip ) {
1241
			$email      = (string) $email;
1242
			$ip_address = (string) $ip_address;
1243
		} else {
1244
			$email      = false;
1245
			$ip_address = false;
1246
		}
1247
1248
		$author = array(
1249
			'ID'          => (int) $ID,
1250
			'login'       => (string) $login,
1251
			'email'       => $email, // (string|bool)
1252
			'name'        => (string) $name,
0 ignored issues
show
Bug introduced by
The variable $name 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...
1253
			'first_name'  => (string) $first_name,
0 ignored issues
show
Bug introduced by
The variable $first_name 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...
1254
			'last_name'   => (string) $last_name,
0 ignored issues
show
Bug introduced by
The variable $last_name 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...
1255
			'nice_name'   => (string) $nice,
0 ignored issues
show
Bug introduced by
The variable $nice 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...
1256
			'URL'         => (string) esc_url_raw( $URL ),
0 ignored issues
show
Bug introduced by
The variable $URL 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...
1257
			'avatar_URL'  => (string) esc_url_raw( $avatar_URL ),
1258
			'profile_URL' => (string) esc_url_raw( $profile_URL ),
1259
			'ip_address'  => $ip_address, // (string|bool)
1260
		);
1261
1262
		if ( $site_id > -1 ) {
1263
			$author['site_ID']      = (int) $site_id;
1264
			$author['site_visible'] = $site_visible;
0 ignored issues
show
Bug introduced by
The variable $site_visible 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...
1265
		}
1266
1267
		return (object) $author;
1268
	}
1269
1270
	function get_media_item( $media_id ) {
1271
		$media_item = get_post( $media_id );
1272
1273
		if ( ! $media_item || is_wp_error( $media_item ) ) {
1274
			return new WP_Error( 'unknown_media', 'Unknown Media', 404 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unknown_media'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1275
		}
1276
1277
		$response = array(
1278
			'id'          => strval( $media_item->ID ),
1279
			'date'        => (string) $this->format_date( $media_item->post_date_gmt, $media_item->post_date ),
1280
			'parent'      => $media_item->post_parent,
1281
			'link'        => wp_get_attachment_url( $media_item->ID ),
1282
			'title'       => $media_item->post_title,
1283
			'caption'     => $media_item->post_excerpt,
1284
			'description' => $media_item->post_content,
1285
			'metadata'    => wp_get_attachment_metadata( $media_item->ID ),
1286
		);
1287
1288
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM && is_array( $response['metadata'] ) && ! empty( $response['metadata']['file'] ) ) {
1289
			remove_filter( '_wp_relative_upload_path', 'wpcom_wp_relative_upload_path', 10 );
1290
			$response['metadata']['file'] = _wp_relative_upload_path( $response['metadata']['file'] );
1291
			add_filter( '_wp_relative_upload_path', 'wpcom_wp_relative_upload_path', 10, 2 );
1292
		}
1293
1294
		$response['meta'] = (object) array(
1295
			'links' => (object) array(
1296
				'self' => (string) $this->links->get_media_link( $this->api->get_blog_id_for_output(), $media_id ),
1297
				'help' => (string) $this->links->get_media_link( $this->api->get_blog_id_for_output(), $media_id, 'help' ),
1298
				'site' => (string) $this->links->get_site_link( $this->api->get_blog_id_for_output() ),
1299
			),
1300
		);
1301
1302
		return (object) $response;
1303
	}
1304
1305
	function get_media_item_v1_1( $media_id, $media_item = null, $file = null ) {
1306
1307
		if ( ! $media_item ) {
1308
			$media_item = get_post( $media_id );
1309
		}
1310
1311
		if ( ! $media_item || is_wp_error( $media_item ) ) {
1312
			return new WP_Error( 'unknown_media', 'Unknown Media', 404 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unknown_media'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1313
		}
1314
1315
		$attachment_file = get_attached_file( $media_item->ID );
1316
1317
		$file      = basename( $attachment_file ? $attachment_file : $file );
1318
		$file_info = pathinfo( $file );
1319
		$ext       = isset( $file_info['extension'] ) ? $file_info['extension'] : null;
1320
1321
		$response = array(
1322
			'ID'          => $media_item->ID,
1323
			'URL'         => wp_get_attachment_url( $media_item->ID ),
1324
			'guid'        => $media_item->guid,
1325
			'date'        => (string) $this->format_date( $media_item->post_date_gmt, $media_item->post_date ),
1326
			'post_ID'     => $media_item->post_parent,
1327
			'author_ID'   => (int) $media_item->post_author,
1328
			'file'        => $file,
1329
			'mime_type'   => $media_item->post_mime_type,
1330
			'extension'   => $ext,
1331
			'title'       => $media_item->post_title,
1332
			'caption'     => $media_item->post_excerpt,
1333
			'description' => $media_item->post_content,
1334
			'alt'         => get_post_meta( $media_item->ID, '_wp_attachment_image_alt', true ),
1335
			'icon'        => wp_mime_type_icon( $media_item->ID ),
1336
			'thumbnails'  => array(),
1337
		);
1338
1339
		if ( in_array( $ext, array( 'jpg', 'jpeg', 'png', 'gif' ) ) ) {
1340
			$metadata = wp_get_attachment_metadata( $media_item->ID );
1341
			if ( isset( $metadata['height'], $metadata['width'] ) ) {
1342
				$response['height'] = $metadata['height'];
1343
				$response['width']  = $metadata['width'];
1344
			}
1345
1346
			if ( isset( $metadata['sizes'] ) ) {
1347
				/**
1348
				 * Filter the thumbnail sizes available for each attachment ID.
1349
				 *
1350
				 * @module json-api
1351
				 *
1352
				 * @since 3.9.0
1353
				 *
1354
				 * @param array $metadata['sizes'] Array of thumbnail sizes available for a given attachment ID.
1355
				 * @param string $media_id Attachment ID.
1356
				 */
1357
				$sizes = apply_filters( 'rest_api_thumbnail_sizes', $metadata['sizes'], $media_item->ID );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $media_item->ID.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1358 View Code Duplication
				if ( is_array( $sizes ) ) {
1359
					foreach ( $sizes as $size => $size_details ) {
1360
						$response['thumbnails'][ $size ] = dirname( $response['URL'] ) . '/' . $size_details['file'];
1361
					}
1362
					/**
1363
					 * Filter the thumbnail URLs for attachment files.
1364
					 *
1365
					 * @module json-api
1366
					 *
1367
					 * @since 7.1.0
1368
					 *
1369
					 * @param array $metadata['sizes'] Array with thumbnail sizes as keys and URLs as values.
1370
					 */
1371
					$response['thumbnails'] = apply_filters( 'rest_api_thumbnail_size_urls', $response['thumbnails'] );
1372
				}
1373
			}
1374
1375
			if ( isset( $metadata['image_meta'] ) ) {
1376
				$response['exif'] = $metadata['image_meta'];
1377
			}
1378
		}
1379
1380
		if ( in_array( $ext, array( 'mp3', 'm4a', 'wav', 'ogg' ) ) ) {
1381
			$metadata           = wp_get_attachment_metadata( $media_item->ID );
1382
			$response['length'] = $metadata['length'];
1383
			$response['exif']   = $metadata;
1384
		}
1385
1386
		$is_video = false;
1387
1388
		if (
1389
			in_array( $ext, array( 'ogv', 'mp4', 'mov', 'wmv', 'avi', 'mpg', '3gp', '3g2', 'm4v' ) )
1390
			||
1391
			$response['mime_type'] === 'video/videopress'
1392
		) {
1393
			$is_video = true;
1394
		}
1395
1396
		if ( $is_video ) {
1397
			$metadata = wp_get_attachment_metadata( $media_item->ID );
1398
1399
			if ( isset( $metadata['height'], $metadata['width'] ) ) {
1400
				$response['height'] = $metadata['height'];
1401
				$response['width']  = $metadata['width'];
1402
			}
1403
1404
			if ( isset( $metadata['length'] ) ) {
1405
				$response['length'] = $metadata['length'];
1406
			}
1407
1408
			// add VideoPress info
1409
			if ( function_exists( 'video_get_info_by_blogpostid' ) ) {
1410
				$info = video_get_info_by_blogpostid( $this->api->get_blog_id_for_output(), $media_item->ID );
1411
1412
				// If we failed to get VideoPress info, but it exists in the meta data (for some reason)
1413
				// then let's use that.
1414
				if ( false === $info && isset( $metadata['videopress'] ) ) {
1415
					$info = (object) $metadata['videopress'];
1416
				}
1417
1418
				// Thumbnails
1419 View Code Duplication
				if ( function_exists( 'video_format_done' ) && function_exists( 'video_image_url_by_guid' ) ) {
1420
					$response['thumbnails'] = array(
1421
						'fmt_hd'  => '',
1422
						'fmt_dvd' => '',
1423
						'fmt_std' => '',
1424
					);
1425
					foreach ( $response['thumbnails'] as $size => $thumbnail_url ) {
1426
						if ( video_format_done( $info, $size ) ) {
1427
							$response['thumbnails'][ $size ] = video_image_url_by_guid( $info->guid, $size );
1428
						} else {
1429
							unset( $response['thumbnails'][ $size ] );
1430
						}
1431
					}
1432
				}
1433
1434
				// If we didn't get VideoPress information (for some reason) then let's
1435
				// not try and include it in the response.
1436
				if ( isset( $info->guid ) ) {
1437
					$response['videopress_guid']            = $info->guid;
1438
					$response['videopress_processing_done'] = true;
1439
					if ( '0000-00-00 00:00:00' === $info->finish_date_gmt ) {
1440
						$response['videopress_processing_done'] = false;
1441
					}
1442
				}
1443
			}
1444
		}
1445
1446
		$response['thumbnails'] = (object) $response['thumbnails'];
1447
1448
		$response['meta'] = (object) array(
1449
			'links' => (object) array(
1450
				'self' => (string) $this->links->get_media_link( $this->api->get_blog_id_for_output(), $media_item->ID ),
1451
				'help' => (string) $this->links->get_media_link( $this->api->get_blog_id_for_output(), $media_item->ID, 'help' ),
1452
				'site' => (string) $this->links->get_site_link( $this->api->get_blog_id_for_output() ),
1453
			),
1454
		);
1455
1456
		// add VideoPress link to the meta
1457
		if ( isset( $response['videopress_guid'] ) ) {
1458 View Code Duplication
			if ( function_exists( 'video_get_info_by_blogpostid' ) ) {
1459
				$response['meta']->links->videopress = (string) $this->links->get_link( '/videos/%s', $response['videopress_guid'], '' );
1460
			}
1461
		}
1462
1463 View Code Duplication
		if ( $media_item->post_parent > 0 ) {
1464
			$response['meta']->links->parent = (string) $this->links->get_post_link( $this->api->get_blog_id_for_output(), $media_item->post_parent );
1465
		}
1466
1467
		return (object) $response;
1468
	}
1469
1470
	function get_taxonomy( $taxonomy_id, $taxonomy_type, $context ) {
1471
1472
		$taxonomy = get_term_by( 'slug', $taxonomy_id, $taxonomy_type );
1473
		// keep updating this function
1474
		if ( ! $taxonomy || is_wp_error( $taxonomy ) ) {
1475
			return new WP_Error( 'unknown_taxonomy', 'Unknown taxonomy', 404 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unknown_taxonomy'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1476
		}
1477
1478
		return $this->format_taxonomy( $taxonomy, $taxonomy_type, $context );
1479
	}
1480
1481
	function format_taxonomy( $taxonomy, $taxonomy_type, $context ) {
1482
		// Permissions
1483
		switch ( $context ) {
1484
			case 'edit':
1485
				$tax = get_taxonomy( $taxonomy_type );
1486
				if ( ! current_user_can( $tax->cap->edit_terms ) ) {
1487
					return new WP_Error( 'unauthorized', 'User cannot edit taxonomy', 403 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unauthorized'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1488
				}
1489
				break;
1490
			case 'display':
1491 View Code Duplication
				if ( -1 == get_option( 'blog_public' ) && ! current_user_can( 'read' ) ) {
1492
					return new WP_Error( 'unauthorized', 'User cannot view taxonomy', 403 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unauthorized'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1493
				}
1494
				break;
1495
			default:
1496
				return new WP_Error( 'invalid_context', 'Invalid API CONTEXT', 400 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_context'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1497
		}
1498
1499
		$response                = array();
1500
		$response['ID']          = (int) $taxonomy->term_id;
1501
		$response['name']        = (string) $taxonomy->name;
1502
		$response['slug']        = (string) $taxonomy->slug;
1503
		$response['description'] = (string) $taxonomy->description;
1504
		$response['post_count']  = (int) $taxonomy->count;
1505
		$response['feed_url']    = get_term_feed_link( $taxonomy->term_id, $taxonomy_type );
1506
1507
		if ( is_taxonomy_hierarchical( $taxonomy_type ) ) {
1508
			$response['parent'] = (int) $taxonomy->parent;
1509
		}
1510
1511
		$response['meta'] = (object) array(
1512
			'links' => (object) array(
1513
				'self' => (string) $this->links->get_taxonomy_link( $this->api->get_blog_id_for_output(), $taxonomy->slug, $taxonomy_type ),
1514
				'help' => (string) $this->links->get_taxonomy_link( $this->api->get_blog_id_for_output(), $taxonomy->slug, $taxonomy_type, 'help' ),
1515
				'site' => (string) $this->links->get_site_link( $this->api->get_blog_id_for_output() ),
1516
			),
1517
		);
1518
1519
		return (object) $response;
1520
	}
1521
1522
	/**
1523
	 * Returns ISO 8601 formatted datetime: 2011-12-08T01:15:36-08:00
1524
	 *
1525
	 * @param $date_gmt (string) GMT datetime string.
1526
	 * @param $date (string) Optional.  Used to calculate the offset from GMT.
1527
	 *
1528
	 * @return string
1529
	 */
1530
	function format_date( $date_gmt, $date = null ) {
1531
		return WPCOM_JSON_API_Date::format_date( $date_gmt, $date );
1532
	}
1533
1534
	/**
1535
	 * Parses a date string and returns the local and GMT representations
1536
	 * of that date & time in 'YYYY-MM-DD HH:MM:SS' format without
1537
	 * timezones or offsets. If the parsed datetime was not localized to a
1538
	 * particular timezone or offset we will assume it was given in GMT
1539
	 * relative to now and will convert it to local time using either the
1540
	 * timezone set in the options table for the blog or the GMT offset.
1541
	 *
1542
	 * @param datetime string $date_string Date to parse.
1543
	 *
1544
	 * @return array( $local_time_string, $gmt_time_string )
0 ignored issues
show
Documentation introduced by
The doc-type array( could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1545
	 */
1546
	public function parse_date( $date_string ) {
1547
		$date_string_info = date_parse( $date_string );
1548
		if ( is_array( $date_string_info ) && 0 === $date_string_info['error_count'] ) {
1549
			// Check if it's already localized. Can't just check is_localtime because date_parse('oppossum') returns true; WTF, PHP.
1550
			if ( isset( $date_string_info['zone'] ) && true === $date_string_info['is_localtime'] ) {
1551
				$dt_utc   = new DateTime( $date_string );
1552
				$dt_local = clone $dt_utc;
1553
				$dt_utc->setTimezone( new DateTimeZone( 'UTC' ) );
1554
				return array(
1555
					(string) $dt_local->format( 'Y-m-d H:i:s' ),
1556
					(string) $dt_utc->format( 'Y-m-d H:i:s' ),
1557
				);
1558
			}
1559
1560
			// It's parseable but no TZ info so assume UTC.
1561
			$dt_utc   = new DateTime( $date_string, new DateTimeZone( 'UTC' ) );
1562
			$dt_local = clone $dt_utc;
1563
		} else {
1564
			// Could not parse time, use now in UTC.
1565
			$dt_utc   = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
1566
			$dt_local = clone $dt_utc;
1567
		}
1568
1569
		$dt_local->setTimezone( wp_timezone() );
1570
1571
		return array(
1572
			(string) $dt_local->format( 'Y-m-d H:i:s' ),
1573
			(string) $dt_utc->format( 'Y-m-d H:i:s' ),
1574
		);
1575
	}
1576
1577
	// Load the functions.php file for the current theme to get its post formats, CPTs, etc.
1578
	function load_theme_functions() {
1579
		// bail if we've done this already (can happen when calling /batch endpoint)
1580
		if ( defined( 'REST_API_THEME_FUNCTIONS_LOADED' ) ) {
1581
			return;
1582
		}
1583
1584
		// VIP context loading is handled elsewhere, so bail to prevent
1585
		// duplicate loading. See `switch_to_blog_and_validate_user()`
1586
		if ( function_exists( 'wpcom_is_vip' ) && wpcom_is_vip() ) {
1587
			return;
1588
		}
1589
1590
		define( 'REST_API_THEME_FUNCTIONS_LOADED', true );
1591
1592
		// the theme info we care about is found either within functions.php or one of the jetpack files.
1593
		$function_files = array( '/functions.php', '/inc/jetpack.compat.php', '/inc/jetpack.php', '/includes/jetpack.compat.php' );
1594
1595
		$copy_dirs = array( get_template_directory() );
1596
1597
		// Is this a child theme? Load the child theme's functions file.
1598
		if ( get_stylesheet_directory() !== get_template_directory() && wpcom_is_child_theme() ) {
1599
			foreach ( $function_files as $function_file ) {
1600
				if ( file_exists( get_stylesheet_directory() . $function_file ) ) {
1601
					require_once get_stylesheet_directory() . $function_file;
1602
				}
1603
			}
1604
			$copy_dirs[] = get_stylesheet_directory();
1605
		}
1606
1607
		foreach ( $function_files as $function_file ) {
1608
			if ( file_exists( get_template_directory() . $function_file ) ) {
1609
				require_once get_template_directory() . $function_file;
1610
			}
1611
		}
1612
1613
		// add inc/wpcom.php and/or includes/wpcom.php
1614
		wpcom_load_theme_compat_file();
1615
1616
		// Enable including additional directories or files in actions to be copied
1617
		$copy_dirs = apply_filters( 'restapi_theme_action_copy_dirs', $copy_dirs );
1618
1619
		// since the stuff we care about (CPTS, post formats, are usually on setup or init hooks, we want to load those)
1620
		$this->copy_hooks( 'after_setup_theme', 'restapi_theme_after_setup_theme', $copy_dirs );
1621
1622
		/**
1623
		 * Fires functions hooked onto `after_setup_theme` by the theme for the purpose of the REST API.
1624
		 *
1625
		 * The REST API does not load the theme when processing requests.
1626
		 * To enable theme-based functionality, the API will load the '/functions.php',
1627
		 * '/inc/jetpack.compat.php', '/inc/jetpack.php', '/includes/jetpack.compat.php files
1628
		 * of the theme (parent and child) and copy functions hooked onto 'after_setup_theme' within those files.
1629
		 *
1630
		 * @module json-api
1631
		 *
1632
		 * @since 3.2.0
1633
		 */
1634
		do_action( 'restapi_theme_after_setup_theme' );
1635
		$this->copy_hooks( 'init', 'restapi_theme_init', $copy_dirs );
1636
1637
		/**
1638
		 * Fires functions hooked onto `init` by the theme for the purpose of the REST API.
1639
		 *
1640
		 * The REST API does not load the theme when processing requests.
1641
		 * To enable theme-based functionality, the API will load the '/functions.php',
1642
		 * '/inc/jetpack.compat.php', '/inc/jetpack.php', '/includes/jetpack.compat.php files
1643
		 * of the theme (parent and child) and copy functions hooked onto 'init' within those files.
1644
		 *
1645
		 * @module json-api
1646
		 *
1647
		 * @since 3.2.0
1648
		 */
1649
		do_action( 'restapi_theme_init' );
1650
	}
1651
1652
	function copy_hooks( $from_hook, $to_hook, $base_paths ) {
1653
		global $wp_filter;
1654
		foreach ( $wp_filter as $hook => $actions ) {
1655
1656
			if ( $from_hook != $hook ) {
1657
				continue;
1658
			}
1659
			if ( ! has_action( $hook ) ) {
1660
				continue;
1661
			}
1662
1663
			foreach ( $actions as $priority => $callbacks ) {
1664
				foreach ( $callbacks as $callback_key => $callback_data ) {
1665
					$callback = $callback_data['function'];
1666
1667
					// use reflection api to determine filename where function is defined
1668
					$reflection = $this->get_reflection( $callback );
1669
1670
					if ( false !== $reflection ) {
1671
						$file_name = $reflection->getFileName();
1672
						foreach ( $base_paths as $base_path ) {
1673
1674
							// only copy hooks with functions which are part of the specified files
1675
							if ( 0 === strpos( $file_name, $base_path ) ) {
1676
								add_action(
1677
									$to_hook,
1678
									$callback_data['function'],
1679
									$priority,
1680
									$callback_data['accepted_args']
1681
								);
1682
							}
1683
						}
1684
					}
1685
				}
1686
			}
1687
		}
1688
	}
1689
1690
	function get_reflection( $callback ) {
1691
		if ( is_array( $callback ) ) {
1692
			list( $class, $method ) = $callback;
1693
			return new ReflectionMethod( $class, $method );
1694
		}
1695
1696
		if ( is_string( $callback ) && strpos( $callback, '::' ) !== false ) {
1697
			list( $class, $method ) = explode( '::', $callback );
1698
			return new ReflectionMethod( $class, $method );
1699
		}
1700
1701
		if ( method_exists( $callback, "__invoke" ) ) {
1702
			return new ReflectionMethod( $callback, "__invoke" );
1703
		}
1704
1705
		if ( is_string( $callback ) && strpos( $callback, '::' ) == false && function_exists( $callback ) ) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing strpos($callback, '::') of type integer to the boolean false. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
1706
			return new ReflectionFunction( $callback );
1707
		}
1708
1709
		return false;
1710
	}
1711
1712
	/**
1713
	 * Check whether a user can view or edit a post type
1714
	 *
1715
	 * @param string $post_type              post type to check
1716
	 * @param string $context                'display' or 'edit'
1717
	 * @return bool
1718
	 */
1719 View Code Duplication
	function current_user_can_access_post_type( $post_type, $context = 'display' ) {
1720
		$post_type_object = get_post_type_object( $post_type );
1721
		if ( ! $post_type_object ) {
1722
			return false;
1723
		}
1724
1725
		switch ( $context ) {
1726
			case 'edit':
1727
				return current_user_can( $post_type_object->cap->edit_posts );
1728
			case 'display':
1729
				return $post_type_object->public || current_user_can( $post_type_object->cap->read_private_posts );
1730
			default:
1731
				return false;
1732
		}
1733
	}
1734
1735 View Code Duplication
	function is_post_type_allowed( $post_type ) {
1736
		// if the post type is empty, that's fine, WordPress will default to post
1737
		if ( empty( $post_type ) ) {
1738
			return true;
1739
		}
1740
1741
		// allow special 'any' type
1742
		if ( 'any' == $post_type ) {
1743
			return true;
1744
		}
1745
1746
		// check for allowed types
1747
		if ( in_array( $post_type, $this->_get_whitelisted_post_types() ) ) {
1748
			return true;
1749
		}
1750
1751
		if ( $post_type_object = get_post_type_object( $post_type ) ) {
1752
			if ( ! empty( $post_type_object->show_in_rest ) ) {
1753
				return $post_type_object->show_in_rest;
1754
			}
1755
			if ( ! empty( $post_type_object->publicly_queryable ) ) {
1756
				return $post_type_object->publicly_queryable;
1757
			}
1758
		}
1759
1760
		return ! empty( $post_type_object->public );
1761
	}
1762
1763
	/**
1764
	 * Gets the whitelisted post types that JP should allow access to.
1765
	 *
1766
	 * @return array Whitelisted post types.
1767
	 */
1768 View Code Duplication
	protected function _get_whitelisted_post_types() {
1769
		$allowed_types = array( 'post', 'page', 'revision' );
1770
1771
		/**
1772
		 * Filter the post types Jetpack has access to, and can synchronize with WordPress.com.
1773
		 *
1774
		 * @module json-api
1775
		 *
1776
		 * @since 2.2.3
1777
		 *
1778
		 * @param array $allowed_types Array of whitelisted post types. Default to `array( 'post', 'page', 'revision' )`.
1779
		 */
1780
		$allowed_types = apply_filters( 'rest_api_allowed_post_types', $allowed_types );
1781
1782
		return array_unique( $allowed_types );
1783
	}
1784
1785
	function handle_media_creation_v1_1( $media_files, $media_urls, $media_attrs = array(), $force_parent_id = false ) {
1786
1787
		add_filter( 'upload_mimes', array( $this, 'allow_video_uploads' ) );
1788
1789
		$media_ids             = $errors = array();
1790
		$user_can_upload_files = current_user_can( 'upload_files' ) || $this->api->is_authorized_with_upload_token();
0 ignored issues
show
Bug introduced by
The method is_authorized_with_upload_token() does not seem to exist on object<WPCOM_JSON_API>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1791
		$media_attrs           = array_values( $media_attrs ); // reset the keys
1792
		$i                     = 0;
1793
1794
		if ( ! empty( $media_files ) ) {
1795
			$this->api->trap_wp_die( 'upload_error' );
1796
			foreach ( $media_files as $media_item ) {
1797
				$_FILES['.api.media.item.'] = $media_item;
1798 View Code Duplication
				if ( ! $user_can_upload_files ) {
1799
					$media_id = new WP_Error( 'unauthorized', 'User cannot upload media.', 403 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unauthorized'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1800
				} else {
1801
					if ( $force_parent_id ) {
1802
						$parent_id = absint( $force_parent_id );
1803
					} elseif ( ! empty( $media_attrs[ $i ] ) && ! empty( $media_attrs[ $i ]['parent_id'] ) ) {
1804
						$parent_id = absint( $media_attrs[ $i ]['parent_id'] );
1805
					} else {
1806
						$parent_id = 0;
1807
					}
1808
					$media_id = media_handle_upload( '.api.media.item.', $parent_id );
1809
				}
1810
				if ( is_wp_error( $media_id ) ) {
1811
					$errors[ $i ]['file']    = $media_item['name'];
1812
					$errors[ $i ]['error']   = $media_id->get_error_code();
1813
					$errors[ $i ]['message'] = $media_id->get_error_message();
1814
				} else {
1815
					$media_ids[ $i ] = $media_id;
1816
				}
1817
1818
				$i++;
1819
			}
1820
			$this->api->trap_wp_die( null );
1821
			unset( $_FILES['.api.media.item.'] );
1822
		}
1823
1824
		if ( ! empty( $media_urls ) ) {
1825
			foreach ( $media_urls as $url ) {
1826 View Code Duplication
				if ( ! $user_can_upload_files ) {
1827
					$media_id = new WP_Error( 'unauthorized', 'User cannot upload media.', 403 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unauthorized'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1828
				} else {
1829
					if ( $force_parent_id ) {
1830
						$parent_id = absint( $force_parent_id );
1831
					} elseif ( ! empty( $media_attrs[ $i ] ) && ! empty( $media_attrs[ $i ]['parent_id'] ) ) {
1832
						$parent_id = absint( $media_attrs[ $i ]['parent_id'] );
1833
					} else {
1834
						$parent_id = 0;
1835
					}
1836
					$media_id = $this->handle_media_sideload( $url, $parent_id );
1837
				}
1838
				if ( is_wp_error( $media_id ) ) {
1839
					$errors[ $i ] = array(
1840
						'file'    => $url,
1841
						'error'   => $media_id->get_error_code(),
1842
						'message' => $media_id->get_error_message(),
1843
					);
1844
				} elseif ( ! empty( $media_id ) ) {
1845
					$media_ids[ $i ] = $media_id;
1846
				}
1847
1848
				$i++;
1849
			}
1850
		}
1851
1852
		if ( ! empty( $media_attrs ) ) {
1853
			foreach ( $media_ids as $index => $media_id ) {
1854
				if ( empty( $media_attrs[ $index ] ) ) {
1855
					continue;
1856
				}
1857
1858
				$attrs  = $media_attrs[ $index ];
1859
				$insert = array();
1860
1861
				// Attributes: Title, Caption, Description
1862
1863
				if ( isset( $attrs['title'] ) ) {
1864
					$insert['post_title'] = $attrs['title'];
1865
				}
1866
1867
				if ( isset( $attrs['caption'] ) ) {
1868
					$insert['post_excerpt'] = $attrs['caption'];
1869
				}
1870
1871
				if ( isset( $attrs['description'] ) ) {
1872
					$insert['post_content'] = $attrs['description'];
1873
				}
1874
1875
				if ( ! empty( $insert ) ) {
1876
					$insert['ID'] = $media_id;
1877
					wp_update_post( (object) $insert );
1878
				}
1879
1880
				// Attributes: Alt
1881
1882 View Code Duplication
				if ( isset( $attrs['alt'] ) ) {
1883
					$alt = wp_strip_all_tags( $attrs['alt'], true );
1884
					update_post_meta( $media_id, '_wp_attachment_image_alt', $alt );
1885
				}
1886
1887
				// Attributes: Artist, Album
1888
1889
				$id3_meta = array();
1890
1891 View Code Duplication
				foreach ( array( 'artist', 'album' ) as $key ) {
1892
					if ( isset( $attrs[ $key ] ) ) {
1893
						$id3_meta[ $key ] = wp_strip_all_tags( $attrs[ $key ], true );
1894
					}
1895
				}
1896
1897
				if ( ! empty( $id3_meta ) ) {
1898
					// Before updating metadata, ensure that the item is audio
1899
					$item = $this->get_media_item_v1_1( $media_id );
1900
					if ( 0 === strpos( $item->mime_type, 'audio/' ) ) {
1901
						wp_update_attachment_metadata( $media_id, $id3_meta );
1902
					}
1903
				}
1904
			}
1905
		}
1906
1907
		return array(
1908
			'media_ids' => $media_ids,
1909
			'errors'    => $errors,
1910
		);
1911
1912
	}
1913
1914
	function handle_media_sideload( $url, $parent_post_id = 0, $type = 'any' ) {
1915
		if ( ! function_exists( 'download_url' ) || ! function_exists( 'media_handle_sideload' ) ) {
1916
			return false;
1917
		}
1918
1919
		// if we didn't get a URL, let's bail
1920
		$parsed = wp_parse_url( $url );
1921
		if ( empty( $parsed ) ) {
1922
			return false;
1923
		}
1924
1925
		$tmp = download_url( $url );
1926
		if ( is_wp_error( $tmp ) ) {
1927
			return $tmp;
1928
		}
1929
1930
		// First check to see if we get a mime-type match by file, otherwise, check to
1931
		// see if WordPress supports this file as an image. If neither, then it is not supported.
1932 View Code Duplication
		if ( ! $this->is_file_supported_for_sideloading( $tmp ) || 'image' === $type && ! file_is_displayable_image( $tmp ) ) {
1933
			@unlink( $tmp );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1934
			return new WP_Error( 'invalid_input', 'Invalid file type.', 403 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_input'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1935
		}
1936
1937
		// emulate a $_FILES entry
1938
		$file_array = array(
1939
			'name'     => basename( wp_parse_url( $url, PHP_URL_PATH ) ),
0 ignored issues
show
Unused Code introduced by
The call to wp_parse_url() has too many arguments starting with PHP_URL_PATH.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1940
			'tmp_name' => $tmp,
1941
		);
1942
1943
		$id = media_handle_sideload( $file_array, $parent_post_id );
1944
		if ( file_exists( $tmp ) ) {
1945
			@unlink( $tmp );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1946
		}
1947
1948
		if ( is_wp_error( $id ) ) {
1949
			return $id;
1950
		}
1951
1952
		if ( ! $id || ! is_int( $id ) ) {
1953
			return false;
1954
		}
1955
1956
		return $id;
1957
	}
1958
1959
	/**
1960
	 * Checks that the mime type of the specified file is among those in a filterable list of mime types.
1961
	 *
1962
	 * @param string $file Path to file to get its mime type.
1963
	 *
1964
	 * @return bool
1965
	 */
1966
	protected function is_file_supported_for_sideloading( $file ) {
1967
		return jetpack_is_file_supported_for_sideloading( $file );
1968
	}
1969
1970
	function allow_video_uploads( $mimes ) {
1971
		// if we are on Jetpack, bail - Videos are already allowed
1972
		if ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) {
1973
			return $mimes;
1974
		}
1975
1976
		// extra check that this filter is only ever applied during REST API requests
1977
		if ( ! defined( 'REST_API_REQUEST' ) || ! REST_API_REQUEST ) {
1978
			return $mimes;
1979
		}
1980
1981
		// bail early if they already have the upgrade..
1982
		if ( get_option( 'video_upgrade' ) == '1' ) {
1983
			return $mimes;
1984
		}
1985
1986
		// lets whitelist to only specific clients right now
1987
		$clients_allowed_video_uploads = array();
1988
		/**
1989
		 * Filter the list of whitelisted video clients.
1990
		 *
1991
		 * @module json-api
1992
		 *
1993
		 * @since 3.2.0
1994
		 *
1995
		 * @param array $clients_allowed_video_uploads Array of whitelisted Video clients.
1996
		 */
1997
		$clients_allowed_video_uploads = apply_filters( 'rest_api_clients_allowed_video_uploads', $clients_allowed_video_uploads );
1998
		if ( ! in_array( $this->api->token_details['client_id'], $clients_allowed_video_uploads ) ) {
1999
			return $mimes;
2000
		}
2001
2002
		$mime_list = wp_get_mime_types();
2003
2004
		$video_exts = explode( ' ', get_site_option( 'video_upload_filetypes', false, false ) );
2005
		/**
2006
		 * Filter the video filetypes allowed on the site.
2007
		 *
2008
		 * @module json-api
2009
		 *
2010
		 * @since 3.2.0
2011
		 *
2012
		 * @param array $video_exts Array of video filetypes allowed on the site.
2013
		 */
2014
		$video_exts  = apply_filters( 'video_upload_filetypes', $video_exts );
2015
		$video_mimes = array();
2016
2017
		if ( ! empty( $video_exts ) ) {
2018
			foreach ( $video_exts as $ext ) {
2019
				foreach ( $mime_list as $ext_pattern => $mime ) {
2020
					if ( $ext != '' && strpos( $ext_pattern, $ext ) !== false ) {
2021
						$video_mimes[ $ext_pattern ] = $mime;
2022
					}
2023
				}
2024
			}
2025
2026
			$mimes = array_merge( $mimes, $video_mimes );
2027
		}
2028
2029
		return $mimes;
2030
	}
2031
2032
	function is_current_site_multi_user() {
2033
		$users = wp_cache_get( 'site_user_count', 'WPCOM_JSON_API_Endpoint' );
2034
		if ( false === $users ) {
2035
			$user_query = new WP_User_Query(
2036
				array(
2037
					'blog_id' => get_current_blog_id(),
2038
					'fields'  => 'ID',
2039
				)
2040
			);
2041
			$users      = (int) $user_query->get_total();
2042
			wp_cache_set( 'site_user_count', $users, 'WPCOM_JSON_API_Endpoint', DAY_IN_SECONDS );
2043
		}
2044
		return $users > 1;
2045
	}
2046
2047
	function allows_cross_origin_requests() {
2048
		return 'GET' == $this->method || $this->allow_cross_origin_request;
2049
	}
2050
2051
	function allows_unauthorized_requests( $origin, $complete_access_origins ) {
2052
		return 'GET' == $this->method || ( $this->allow_unauthorized_request && in_array( $origin, $complete_access_origins ) );
2053
	}
2054
2055
	/**
2056
	 * Whether this endpoint accepts site based authentication for the current request.
2057
	 *
2058
	 * @since 9.1.0
2059
	 *
2060
	 * @return bool true, if Jetpack blog token is used and `allow_jetpack_site_auth` is true,
2061
	 * false otherwise.
2062
	 */
2063
	public function accepts_site_based_authentication() {
2064
		return $this->allow_jetpack_site_auth &&
2065
			$this->api->is_jetpack_authorized_for_site();
2066
	}
2067
2068
	function get_platform() {
2069
		return wpcom_get_sal_platform( $this->api->token_details );
2070
	}
2071
2072
	/**
2073
	 * Allows the endpoint to perform logic to allow it to decide whether-or-not it should force a
2074
	 * response from the WPCOM API, or potentially go to the Jetpack blog.
2075
	 *
2076
	 * Override this method if you want to do something different.
2077
	 *
2078
	 * @param  int $blog_id
2079
	 * @return bool
2080
	 */
2081
	function force_wpcom_request( $blog_id ) {
2082
		return false;
2083
	}
2084
2085
	/**
2086
	 * Get an array of all valid AMP origins for a blog's siteurl.
2087
	 *
2088
	 * @param string $siteurl Origin url of the API request.
2089
	 * @return array
2090
	 */
2091
	public function get_amp_cache_origins( $siteurl ) {
2092
		$host = parse_url( $siteurl, PHP_URL_HOST );
2093
2094
		/*
2095
		 * From AMP docs:
2096
		 * "When possible, the Google AMP Cache will create a subdomain for each AMP document's domain by first converting it
2097
		 * from IDN (punycode) to UTF-8. The caches replaces every - (dash) with -- (2 dashes) and replace every . (dot) with
2098
		 * - (dash). For example, pub.com will map to pub-com.cdn.ampproject.org."
2099
		 */
2100
		if ( function_exists( 'idn_to_utf8' ) ) {
2101
			// The third parameter is set explicitly to prevent issues with newer PHP versions compiled with an old ICU version.
2102
			// phpcs:ignore PHPCompatibility.Constants.RemovedConstants.intl_idna_variant_2003Deprecated
2103
			$host = idn_to_utf8( $host, IDNA_DEFAULT, defined( 'INTL_IDNA_VARIANT_UTS46' ) ? INTL_IDNA_VARIANT_UTS46 : INTL_IDNA_VARIANT_2003 );
2104
		}
2105
		$subdomain = str_replace( array( '-', '.' ), array( '--', '-' ), $host );
2106
		return array(
2107
			$siteurl,
2108
			// Google AMP Cache (legacy).
2109
			'https://cdn.ampproject.org',
2110
			// Google AMP Cache subdomain.
2111
			sprintf( 'https://%s.cdn.ampproject.org', $subdomain ),
2112
			// Cloudflare AMP Cache.
2113
			sprintf( 'https://%s.amp.cloudflare.com', $subdomain ),
2114
			// Bing AMP Cache.
2115
			sprintf( 'https://%s.bing-amp.com', $subdomain ),
2116
		);
2117
	}
2118
2119
	/**
2120
	 * Return endpoint response
2121
	 *
2122
	 * @param string $path ... determined by ->$path.
2123
	 *
2124
	 * @return array|WP_Error
2125
	 *  falsy: HTTP 500, no response body
2126
	 *  WP_Error( $error_code, $error_message, $http_status_code ): HTTP $status_code, json_encode( array( 'error' => $error_code, 'message' => $error_message ) ) response body
2127
	 *  $data: HTTP 200, json_encode( $data ) response body
2128
	 */
2129
	abstract public function callback( $path = '' );
2130
2131
2132
}
2133
2134
require_once dirname( __FILE__ ) . '/json-endpoints.php';
2135