Completed
Push — content-options-blog-display-7... ( 930de1 )
by
unknown
12:25
created

WPCOM_JSON_API_Endpoint::format_taxonomy()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 38
Code Lines 27

Duplication

Lines 38
Ratio 100 %

Importance

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

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
154
155
		$this->allowed_if_flagged = $args['allowed_if_flagged'];
156
		$this->allowed_if_red_flagged = $args['allowed_if_red_flagged'];
157
158
		$this->description = $args['description'];
159
		$this->group       = $args['group'];
160
		$this->stat        = $args['stat'];
161
		$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...
162
		$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...
163
164
		$this->method      = $args['method'];
165
		$this->path        = $args['path'];
166
		$this->path_labels = $args['path_labels'];
167
		$this->min_version = $args['min_version'];
168
		$this->max_version = $args['max_version'];
169
		$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...
170
		$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...
171
172
		$this->pass_wpcom_user_details = $args['pass_wpcom_user_details'];
173
		$this->custom_fields_filtering = (bool) $args['custom_fields_filtering'];
174
175
		$this->allow_cross_origin_request = (bool) $args['allow_cross_origin_request'];
176
		$this->allow_unauthorized_request = (bool) $args['allow_unauthorized_request'];
177
		$this->allow_jetpack_site_auth    = (bool) $args['allow_jetpack_site_auth'];
178
179
		$this->version     = $args['version'];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 5 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
180
181
		$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...
182
183 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...
184
			$this->request_format = array_filter( array_merge( $this->request_format, $args['request_format'] ) );
185
		} else {
186
			$this->request_format = $args['request_format'];
187
		}
188
189 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...
190
			$this->response_format = array_filter( array_merge( $this->response_format, $args['response_format'] ) );
191
		} else {
192
			$this->response_format = $args['response_format'];
193
		}
194
195
		if ( false === $args['query_parameters'] ) {
196
			$this->query = array();
197
		} elseif ( is_array( $args['query_parameters'] ) ) {
198
			$this->query = array_filter( array_merge( $this->query, $args['query_parameters'] ) );
199
		}
200
201
		$this->api = WPCOM_JSON_API::init(); // Auto-add to WPCOM_JSON_API
202
		$this->links = WPCOM_JSON_API_Links::getInstance();
203
204
		/** Example Request/Response ******************************************/
205
206
		// Examples for endpoint documentation request
207
		$this->example_request      = $args['example_request'];
208
		$this->example_request_data = $args['example_request_data'];
209
		$this->example_response     = $args['example_response'];
210
211
		$this->api->add( $this );
212
	}
213
214
	// Get all query args.  Prefill with defaults
215
	function query_args( $return_default_values = true, $cast_and_filter = true ) {
216
		$args = array_intersect_key( $this->api->query, $this->query );
217
218
		if ( !$cast_and_filter ) {
219
			return $args;
220
		}
221
222
		return $this->cast_and_filter( $args, $this->query, $return_default_values );
223
	}
224
225
	// Get POST body data
226
	function input( $return_default_values = true, $cast_and_filter = true ) {
227
		$input = trim( $this->api->post_body );
228
		$content_type = $this->api->content_type;
229
		if ( $content_type ) {
230
			list ( $content_type ) = explode( ';', $content_type );
231
		}
232
		$content_type = trim( $content_type );
233
		switch ( $content_type ) {
234
		case 'application/json' :
235
		case 'application/x-javascript' :
236
		case 'text/javascript' :
237
		case 'text/x-javascript' :
238
		case 'text/x-json' :
239
		case 'text/json' :
240
			$return = json_decode( $input, true );
241
242
			if ( function_exists( 'json_last_error' ) ) {
243
				if ( JSON_ERROR_NONE !== json_last_error() ) {
244
					return null;
245
				}
246
			} else {
247
				if ( is_null( $return ) && json_encode( null ) !== $input ) {
248
					return null;
249
				}
250
			}
251
252
			break;
253
		case 'multipart/form-data' :
254
			$return = array_merge( stripslashes_deep( $_POST ), $_FILES );
255
			break;
256
		case 'application/x-www-form-urlencoded' :
257
			//attempt JSON first, since probably a curl command
258
			$return = json_decode( $input, true );
259
260
			if ( is_null( $return ) ) {
261
				wp_parse_str( $input, $return );
262
			}
263
264
			break;
265
		default :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a DEFAULT statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in the default statement.

switch ($expr) {
    default : //wrong
        doSomething();
        break;
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
266
			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...
267
			break;
268
		}
269
270
		if ( isset( $this->api->query['force'] ) 
271
		    && 'secure' === $this->api->query['force']
272
		    && isset( $return['secure_key'] ) ) {
273
			$this->api->post_body = $this->get_secure_body( $return['secure_key'] );
274
			$this->api->query['force'] = false;
275
			return $this->input( $return_default_values, $cast_and_filter );
276
		}
277
278
		if ( $cast_and_filter ) {
279
			$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...
280
		}
281
		return $return;
282
	}
283
284
285
	protected function get_secure_body( $secure_key ) {
286
		$response =  Jetpack_Client::wpcom_json_api_request_as_blog( 
287
			sprintf( '/sites/%d/secure-request', Jetpack_Options::get_option('id' ) ), 
288
			'1.1', 
289
			array( 'method' => 'POST' ), 
290
			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...
291
		);
292
		if ( 200 !== $response['response']['code'] ) {
293
			return null;
294
		}
295
		return json_decode( $response['body'], true );
296
	}
297
298
	function cast_and_filter( $data, $documentation, $return_default_values = false, $for_output = false ) {
299
		$return_as_object = false;
300
		if ( is_object( $data ) ) {
301
			// @todo this should probably be a deep copy if $data can ever have nested objects
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
302
			$data = (array) $data;
303
			$return_as_object = true;
304
		} elseif ( !is_array( $data ) ) {
305
			return $data;
306
		}
307
308
		$boolean_arg = array( 'false', 'true' );
309
		$naeloob_arg = array( 'true', 'false' );
310
311
		$return = array();
312
313
		foreach ( $documentation as $key => $description ) {
314
			if ( is_array( $description ) ) {
315
				// String or boolean array keys only
316
				$whitelist = array_keys( $description );
317
318
				if ( $whitelist === $boolean_arg || $whitelist === $naeloob_arg ) {
319
					// Truthiness
320
					if ( isset( $data[$key] ) ) {
321
						$return[$key] = (bool) WPCOM_JSON_API::is_truthy( $data[$key] );
322
					} elseif ( $return_default_values ) {
323
						$return[$key] = $whitelist === $naeloob_arg; // Default to true for naeloob_arg and false for boolean_arg.
324
					}
325
				} elseif ( isset( $data[$key] ) && isset( $description[$data[$key]] ) ) {
326
					// String Key
327
					$return[$key] = (string) $data[$key];
328
				} elseif ( $return_default_values ) {
329
					// Default value
330
					$return[$key] = (string) current( $whitelist );
331
				}
332
333
				continue;
334
			}
335
336
			$types = $this->parse_types( $description );
337
			$type = array_shift( $types );
338
339
			// Explicit default - string and int only for now.  Always set these reguardless of $return_default_values
340
			if ( isset( $type['default'] ) ) {
341
				if ( !isset( $data[$key] ) ) {
342
					$data[$key] = $type['default'];
343
				}
344
			}
345
346
			if ( !isset( $data[$key] ) ) {
347
				continue;
348
			}
349
350
			$this->cast_and_filter_item( $return, $type, $key, $data[$key], $types, $for_output );
351
		}
352
353
		if ( $return_as_object ) {
354
			return (object) $return;
355
		}
356
357
		return $return;
358
	}
359
360
	/**
361
	 * Casts $value according to $type.
362
	 * Handles fallbacks for certain values of $type when $value is not that $type
363
	 * Currently, only handles fallback between string <-> array (two way), from string -> false (one way), and from object -> false (one way),
364
	 * and string -> object (one way)
365
	 *
366
	 * Handles "child types" - array:URL, object:category
367
	 * array:URL means an array of URLs
368
	 * object:category means a hash of categories
369
	 *
370
	 * Handles object typing - object>post means an object of type post
371
	 */
372
	function cast_and_filter_item( &$return, $type, $key, $value, $types = array(), $for_output = false ) {
373
		if ( is_string( $type ) ) {
374
			$type = compact( 'type' );
375
		}
376
377
		switch ( $type['type'] ) {
378
		case 'false' :
379
			$return[$key] = false;
380
			break;
381
		case 'url' :
382
			$return[$key] = (string) esc_url_raw( $value );
383
			break;
384
		case 'string' :
385
			// Fallback string -> array, or for string -> object
386
			if ( is_array( $value ) || is_object( $value ) ) {
387 View Code Duplication
				if ( !empty( $types[0] ) ) {
388
					$next_type = array_shift( $types );
389
					return $this->cast_and_filter_item( $return, $next_type, $key, $value, $types, $for_output );
390
				}
391
			}
392
393
			// Fallback string -> false
394 View Code Duplication
			if ( !is_string( $value ) ) {
395
				if ( !empty( $types[0] ) && 'false' === $types[0]['type'] ) {
396
					$next_type = array_shift( $types );
397
					return $this->cast_and_filter_item( $return, $next_type, $key, $value, $types, $for_output );
398
				}
399
			}
400
			$return[$key] = (string) $value;
401
			break;
402
		case 'html' :
403
			$return[$key] = (string) $value;
404
			break;
405
		case 'safehtml' :
406
			$return[$key] = wp_kses( (string) $value, wp_kses_allowed_html() );
407
			break;
408
		case 'zip' :
409
		case 'media' :
410
			if ( is_array( $value ) ) {
411
				if ( isset( $value['name'] ) && is_array( $value['name'] ) ) {
412
					// It's a $_FILES array
413
					// Reformat into array of $_FILES items
414
					$files = array();
415
416
					foreach ( $value['name'] as $k => $v ) {
417
						$files[$k] = array();
418
						foreach ( array_keys( $value ) as $file_key ) {
419
							$files[$k][$file_key] = $value[$file_key][$k];
420
						}
421
					}
422
423
					$return[$key] = $files;
424
					break;
425
				}
426
			} else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

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

These else branches can be removed.

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

could be turned into

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

This is much more concise to read.

Loading history...
427
				// no break - treat as 'array'
428
			}
429
			// nobreak
430
		case 'array' :
431
			// Fallback array -> string
432 View Code Duplication
			if ( is_string( $value ) ) {
433
				if ( !empty( $types[0] ) ) {
434
					$next_type = array_shift( $types );
435
					return $this->cast_and_filter_item( $return, $next_type, $key, $value, $types, $for_output );
436
				}
437
			}
438
439 View Code Duplication
			if ( isset( $type['children'] ) ) {
440
				$children = array();
441
				foreach ( (array) $value as $k => $child ) {
442
					$this->cast_and_filter_item( $children, $type['children'], $k, $child, array(), $for_output );
443
				}
444
				$return[$key] = (array) $children;
445
				break;
446
			}
447
448
			$return[$key] = (array) $value;
449
			break;
450
		case 'iso 8601 datetime' :
451
		case 'datetime' :
452
			// (string)s
453
			$dates = $this->parse_date( (string) $value );
454
			if ( $for_output ) {
455
				$return[$key] = $this->format_date( $dates[1], $dates[0] );
456
			} else {
457
				list( $return[$key], $return["{$key}_gmt"] ) = $dates;
458
			}
459
			break;
460
		case 'float' :
461
			$return[$key] = (float) $value;
462
			break;
463
		case 'int' :
464
		case 'integer' :
465
			$return[$key] = (int) $value;
466
			break;
467
		case 'bool' :
468
		case 'boolean' :
469
			$return[$key] = (bool) WPCOM_JSON_API::is_truthy( $value );
470
			break;
471
		case 'object' :
472
			// Fallback object -> false
473 View Code Duplication
			if ( is_scalar( $value ) || is_null( $value ) ) {
474
				if ( !empty( $types[0] ) && 'false' === $types[0]['type'] ) {
475
					return $this->cast_and_filter_item( $return, 'false', $key, $value, $types, $for_output );
476
				}
477
			}
478
479 View Code Duplication
			if ( isset( $type['children'] ) ) {
480
				$children = array();
481
				foreach ( (array) $value as $k => $child ) {
482
					$this->cast_and_filter_item( $children, $type['children'], $k, $child, array(), $for_output );
483
				}
484
				$return[$key] = (object) $children;
485
				break;
486
			}
487
488
			if ( isset( $type['subtype'] ) ) {
489
				return $this->cast_and_filter_item( $return, $type['subtype'], $key, $value, $types, $for_output );
490
			}
491
492
			$return[$key] = (object) $value;
493
			break;
494
		case 'post' :
495
			$return[$key] = (object) $this->cast_and_filter( $value, $this->post_object_format, false, $for_output );
496
			break;
497
		case 'comment' :
498
			$return[$key] = (object) $this->cast_and_filter( $value, $this->comment_object_format, false, $for_output );
499
			break;
500
		case 'tag' :
501
		case 'category' :
502
			$docs = array(
503
				'ID'          => '(int)',
504
				'name'        => '(string)',
505
				'slug'        => '(string)',
506
				'description' => '(HTML)',
507
				'post_count'  => '(int)',
508
				'meta'        => '(object)',
509
			);
510
			if ( 'category' === $type['type'] ) {
511
				$docs['parent'] = '(int)';
512
			}
513
			$return[$key] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
514
			break;
515
		case 'post_reference' :
516 View Code Duplication
		case 'comment_reference' :
517
			$docs = array(
518
				'ID'    => '(int)',
519
				'type'  => '(string)',
520
				'title' => '(string)',
521
				'link'  => '(URL)',
522
			);
523
			$return[$key] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
524
			break;
525 View Code Duplication
		case 'geo' :
526
			$docs = array(
527
				'latitude'  => '(float)',
528
				'longitude' => '(float)',
529
				'address'   => '(string)',
530
			);
531
			$return[$key] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
532
			break;
533
		case 'author' :
534
			$docs = array(
535
				'ID'             => '(int)',
536
				'user_login'     => '(string)',
537
				'login'          => '(string)',
538
				'email'          => '(string|false)',
539
				'name'           => '(string)',
540
				'first_name'     => '(string)',
541
				'last_name'      => '(string)',
542
				'nice_name'      => '(string)',
543
				'URL'            => '(URL)',
544
				'avatar_URL'     => '(URL)',
545
				'profile_URL'    => '(URL)',
546
				'is_super_admin' => '(bool)',
547
				'roles'          => '(array:string)'
548
			);
549
			$return[$key] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
550
			break;
551 View Code Duplication
		case 'role' :
552
			$docs = array(
553
				'name'         => '(string)',
554
				'display_name' => '(string)',
555
				'capabilities' => '(object:boolean)',
556
			);
557
			$return[$key] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
558
			break;
559
		case 'attachment' :
560
			$docs = array(
561
				'ID'        => '(int)',
562
				'URL'       => '(URL)',
563
				'guid'      => '(string)',
564
				'mime_type' => '(string)',
565
				'width'     => '(int)',
566
				'height'    => '(int)',
567
				'duration'  => '(int)',
568
			);
569
			$return[$key] = (object) $this->cast_and_filter(
570
				$value,
571
				/**
572
				 * Filter the documentation returned for a post attachment.
573
				 *
574
				 * @module json-api
575
				 *
576
				 * @since 1.9.0
577
				 *
578
				 * @param array $docs Array of documentation about a post attachment.
579
				 */
580
				apply_filters( 'wpcom_json_api_attachment_cast_and_filter', $docs ),
581
				false,
582
				$for_output
583
			);
584
			break;
585
		case 'metadata' :
586
			$docs = array(
587
				'id'       => '(int)',
588
				'key'       => '(string)',
589
				'value'     => '(string|false|float|int|array|object)',
590
				'previous_value' => '(string)',
591
				'operation'  => '(string)',
592
			);
593
			$return[$key] = (object) $this->cast_and_filter(
594
				$value,
595
				/** This filter is documented in class.json-api-endpoints.php */
596
				apply_filters( 'wpcom_json_api_attachment_cast_and_filter', $docs ),
597
				false,
598
				$for_output
599
			);
600
			break;
601
		case 'plugin' :
602
			$docs = array(
603
				'id'          => '(safehtml) The plugin\'s ID',
604
				'slug'        => '(safehtml) The plugin\'s Slug',
605
				'active'      => '(boolean)  The plugin status.',
606
				'update'      => '(object)   The plugin update info.',
607
				'name'        => '(safehtml) The name of the plugin.',
608
				'plugin_url'  => '(url)      Link to the plugin\'s web site.',
609
				'version'     => '(safehtml) The plugin version number.',
610
				'description' => '(safehtml) Description of what the plugin does and/or notes from the author',
611
				'author'      => '(safehtml) The plugin author\'s name',
612
				'author_url'  => '(url)      The plugin author web site address',
613
				'network'     => '(boolean)  Whether the plugin can only be activated network wide.',
614
				'autoupdate'  => '(boolean)  Whether the plugin is auto updated',
615
				'log'         => '(array:safehtml) An array of update log strings.',
616
			);
617
			$return[$key] = (object) $this->cast_and_filter(
618
				$value,
619
				/**
620
				 * Filter the documentation returned for a plugin.
621
				 *
622
				 * @module json-api
623
				 *
624
				 * @since 3.1.0
625
				 *
626
				 * @param array $docs Array of documentation about a plugin.
627
				 */
628
				apply_filters( 'wpcom_json_api_plugin_cast_and_filter', $docs ),
629
				false,
630
				$for_output
631
			);
632
			break;
633
		case 'jetpackmodule' :
634
			$docs = array(
635
				'id'          => '(string)   The module\'s ID',
636
				'active'      => '(boolean)  The module\'s status.',
637
				'name'        => '(string)   The module\'s name.',
638
				'description' => '(safehtml) The module\'s description.',
639
				'sort'        => '(int)      The module\'s display order.',
640
				'introduced'  => '(string)   The Jetpack version when the module was introduced.',
641
				'changed'     => '(string)   The Jetpack version when the module was changed.',
642
				'free'        => '(boolean)  The module\'s Free or Paid status.',
643
				'module_tags' => '(array)    The module\'s tags.'
644
			);
645
			$return[$key] = (object) $this->cast_and_filter(
646
				$value,
647
				/** This filter is documented in class.json-api-endpoints.php */
648
				apply_filters( 'wpcom_json_api_plugin_cast_and_filter', $docs ),
649
				false,
650
				$for_output
651
			);
652
			break;
653
		case 'sharing_button' :
654
			$docs = array(
655
				'ID'         => '(string)',
656
				'name'       => '(string)',
657
				'URL'        => '(string)',
658
				'icon'       => '(string)',
659
				'enabled'    => '(bool)',
660
				'visibility' => '(string)',
661
			);
662
			$return[$key] = (array) $this->cast_and_filter( $value, $docs, false, $for_output );
663
			break;
664
		case 'sharing_button_service':
665
			$docs = array(
666
				'ID'               => '(string) The service identifier',
667
				'name'             => '(string) The service name',
668
				'class_name'       => '(string) Class name for custom style sharing button elements',
669
				'genericon'        => '(string) The Genericon unicode character for the custom style sharing button icon',
670
				'preview_smart'    => '(string) An HTML snippet of a rendered sharing button smart preview',
671
				'preview_smart_js' => '(string) An HTML snippet of the page-wide initialization scripts used for rendering the sharing button smart preview'
672
			);
673
			$return[$key] = (array) $this->cast_and_filter( $value, $docs, false, $for_output );
674
			break;
675
		case 'taxonomy':
676
			$docs = array(
677
				'name'         => '(string) The taxonomy slug',
678
				'label'        => '(string) The taxonomy human-readable name',
679
				'labels'       => '(object) Mapping of labels for the taxonomy',
680
				'description'  => '(string) The taxonomy description',
681
				'hierarchical' => '(bool) Whether the taxonomy is hierarchical',
682
				'public'       => '(bool) Whether the taxonomy is public',
683
				'capabilities' => '(object) Mapping of current user capabilities for the taxonomy',
684
			);
685
			$return[$key] = (array) $this->cast_and_filter( $value, $docs, false, $for_output );
686
			break;
687
688
		default :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a DEFAULT statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in the default statement.

switch ($expr) {
    default : //wrong
        doSomething();
        break;
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
689
			$method_name = $type['type'] . '_docs';
690
			if ( method_exists( WPCOM_JSON_API_Jetpack_Overrides, $method_name ) ) {
691
				$docs = WPCOM_JSON_API_Jetpack_Overrides::$method_name();
692
			}
693
694
			if ( ! empty( $docs ) ) {
695
				$return[$key] = (object) $this->cast_and_filter(
696
					$value,
697
					/** This filter is documented in class.json-api-endpoints.php */
698
					apply_filters( 'wpcom_json_api_plugin_cast_and_filter', $docs ),
699
					false,
700
					$for_output
701
				);
702
			} else {
703
				trigger_error( "Unknown API casting type {$type['type']}", E_USER_WARNING );
704
			}
705
		}
706
	}
707
708
	function parse_types( $text ) {
709
		if ( !preg_match( '#^\(([^)]+)\)#', ltrim( $text ), $matches ) ) {
710
			return 'none';
711
		}
712
713
		$types = explode( '|', strtolower( $matches[1] ) );
714
		$return = array();
715
		foreach ( $types as $type ) {
716
			foreach ( array( ':' => 'children', '>' => 'subtype', '=' => 'default' ) as $operator => $meaning ) {
717
				if ( false !== strpos( $type, $operator ) ) {
718
					$item = explode( $operator, $type, 2 );
719
					$return[] = array( 'type' => $item[0], $meaning => $item[1] );
720
					continue 2;
721
				}
722
			}
723
			$return[] = compact( 'type' );
724
		}
725
726
		return $return;
727
	}
728
729
	/**
730
	 * Checks if the endpoint is publicly displayable
731
	 */
732
	function is_publicly_documentable() {
733
		return '__do_not_document' !== $this->group && true !== $this->in_testing;
734
	}
735
736
	/**
737
	 * Auto generates documentation based on description, method, path, path_labels, and query parameters.
738
	 * Echoes HTML.
739
	 */
740
	function document( $show_description = true ) {
741
		global $wpdb;
742
		$original_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : 'unset';
743
		unset( $GLOBALS['post'] );
744
745
		$doc = $this->generate_documentation();
746
747
		if ( $show_description ) :
748
?>
749
<caption>
750
	<h1><?php echo wp_kses_post( $doc['method'] ); ?> <?php echo wp_kses_post( $doc['path_labeled'] ); ?></h1>
751
	<p><?php echo wp_kses_post( $doc['description'] ); ?></p>
752
</caption>
753
754
<?php endif; ?>
755
756
<?php if ( true === $this->deprecated ) { ?>
757
<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...
758
<?php } ?>
759
760
<section class="resource-info">
761
	<h2 id="apidoc-resource-info">Resource Information</h2>
762
763
	<table class="api-doc api-doc-resource-parameters api-doc-resource">
764
765
	<thead>
766
		<tr>
767
			<th class="api-index-title" scope="column">&nbsp;</th>
768
			<th class="api-index-title" scope="column">&nbsp;</th>
769
		</tr>
770
	</thead>
771
	<tbody>
772
773
		<tr class="api-index-item">
774
			<th scope="row" class="parameter api-index-item-title">Method</th>
775
			<td class="type api-index-item-title"><?php echo wp_kses_post( $doc['method'] ); ?></td>
776
		</tr>
777
778
		<tr class="api-index-item">
779
			<th scope="row" class="parameter api-index-item-title">URL</th>
780
			<?php
781
			$version = WPCOM_JSON_API__CURRENT_VERSION;
782
			if ( !empty( $this->max_version ) ) {
783
				$version = $this->max_version;
784
			}
785
			?>
786
			<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>
787
		</tr>
788
789
		<tr class="api-index-item">
790
			<th scope="row" class="parameter api-index-item-title">Requires authentication?</th>
791
			<?php
792
			$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'] ) );
793
			?>
794
			<td class="type api-index-item-title"><?php echo ( true === (bool) $requires_auth->requires_authentication ? 'Yes' : 'No' ); ?></td>
795
		</tr>
796
797
	</tbody>
798
	</table>
799
800
</section>
801
802
<?php
803
804
		foreach ( array(
805
			'path'     => 'Method Parameters',
806
			'query'    => 'Query Parameters',
807
			'body'     => 'Request Parameters',
808
			'response' => 'Response Parameters',
809
		) as $doc_section_key => $label ) :
810
			$doc_section = 'response' === $doc_section_key ? $doc['response']['body'] : $doc['request'][$doc_section_key];
811
			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...
812
				continue;
813
			}
814
815
			$param_label = strtolower( str_replace( ' ', '-', $label ) );
816
?>
817
818
<section class="<?php echo $param_label; ?>">
819
820
<h2 id="apidoc-<?php echo esc_attr( $doc_section_key ); ?>"><?php echo wp_kses_post( $label ); ?></h2>
821
822
<table class="api-doc api-doc-<?php echo $param_label; ?>-parameters api-doc-<?php echo strtolower( str_replace( ' ', '-', $doc['group'] ) ); ?>">
823
824
<thead>
825
	<tr>
826
		<th class="api-index-title" scope="column">Parameter</th>
827
		<th class="api-index-title" scope="column">Type</th>
828
		<th class="api-index-title" scope="column">Description</th>
829
	</tr>
830
</thead>
831
<tbody>
832
833
<?php foreach ( $doc_section as $key => $item ) : ?>
834
835
	<tr class="api-index-item">
836
		<th scope="row" class="parameter api-index-item-title"><?php echo wp_kses_post( $key ); ?></th>
837
		<td class="type api-index-item-title"><?php echo wp_kses_post( $item['type'] ); // @todo auto-link? ?></td>
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
838
		<td class="description api-index-item-body"><?php
839
840
		$this->generate_doc_description( $item['description'] );
841
842
		?></td>
843
	</tr>
844
845
<?php endforeach; ?>
846
</tbody>
847
</table>
848
</section>
849
<?php endforeach; ?>
850
851
<?php
852
		if ( 'unset' !== $original_post ) {
853
			$GLOBALS['post'] = $original_post;
854
		}
855
	}
856
857
	function add_http_build_query_to_php_content_example( $matches ) {
858
		$trimmed_match = ltrim( $matches[0] );
859
		$pad = substr( $matches[0], 0, -1 * strlen( $trimmed_match ) );
860
		$pad = ltrim( $pad, ' ' );
861
		$return = '  ' . str_replace( "\n", "\n  ", $matches[0] );
862
		return " http_build_query({$return}{$pad})";
863
	}
864
865
	/**
866
	 * Recursively generates the <dl>'s to document item descriptions.
867
	 * Echoes HTML.
868
	 */
869
	function generate_doc_description( $item ) {
870
		if ( is_array( $item ) ) : ?>
871
872
		<dl>
873
<?php			foreach ( $item as $description_key => $description_value ) : ?>
874
875
			<dt><?php echo wp_kses_post( $description_key . ':' ); ?></dt>
876
			<dd><?php $this->generate_doc_description( $description_value ); ?></dd>
877
878
<?php			endforeach; ?>
879
880
		</dl>
881
882
<?php
883
		else :
884
			echo wp_kses_post( $item );
885
		endif;
886
	}
887
888
	/**
889
	 * Auto generates documentation based on description, method, path, path_labels, and query parameters.
890
	 * Echoes HTML.
891
	 */
892
	function generate_documentation() {
893
		$format       = str_replace( '%d', '%s', $this->path );
894
		$path_labeled = $format;
895
		if ( ! empty( $this->path_labels ) ) {
896
			$path_labeled = vsprintf( $format, array_keys( $this->path_labels ) );
897
		}
898
		$boolean_arg  = array( 'false', 'true' );
899
		$naeloob_arg  = array( 'true', 'false' );
900
901
		$doc = array(
902
			'description'  => $this->description,
903
			'method'       => $this->method,
904
			'path_format'  => $this->path,
905
			'path_labeled' => $path_labeled,
906
			'group'        => $this->group,
907
			'request' => array(
908
				'path'  => array(),
909
				'query' => array(),
910
				'body'  => array(),
911
			),
912
			'response' => array(
913
				'body' => array(),
914
			)
915
		);
916
917
		foreach ( array( 'path_labels' => 'path', 'query' => 'query', 'request_format' => 'body', 'response_format' => 'body' ) as $_property => $doc_item ) {
918
			foreach ( (array) $this->$_property as $key => $description ) {
919
				if ( is_array( $description ) ) {
920
					$description_keys = array_keys( $description );
921
					if ( $boolean_arg === $description_keys || $naeloob_arg === $description_keys ) {
922
						$type = '(bool)';
923
					} else {
924
						$type = '(string)';
925
					}
926
927
					if ( 'response_format' !== $_property ) {
928
						// hack - don't show "(default)" in response format
929
						reset( $description );
930
						$description_key = key( $description );
931
						$description[$description_key] = "(default) {$description[$description_key]}";
932
					}
933
				} else {
934
					$types   = $this->parse_types( $description );
935
					$type    = array();
936
					$default = '';
937
938
					if ( 'none' == $types ) {
939
						$types = array();
940
						$types[]['type'] = 'none';
941
					}
942
943
					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...
944
						$type[] = $type_array['type'];
945
						if ( isset( $type_array['default'] ) ) {
946
							$default = $type_array['default'];
947
							if ( 'string' === $type_array['type'] ) {
948
								$default = "'$default'";
949
							}
950
						}
951
					}
952
					$type = '(' . join( '|', $type ) . ')';
953
					$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...
954
					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...
955
					$description = trim( $description );
956
					if ( $default ) {
957
						$description .= " Default: $default.";
958
					}
959
				}
960
961
				$item = compact( 'type', 'description' );
962
963
				if ( 'response_format' === $_property ) {
964
					$doc['response'][$doc_item][$key] = $item;
965
				} else {
966
					$doc['request'][$doc_item][$key] = $item;
967
				}
968
			}
969
		}
970
971
		return $doc;
972
	}
973
974
	function user_can_view_post( $post_id ) {
975
		$post = get_post( $post_id );
976
		if ( !$post || is_wp_error( $post ) ) {
977
			return false;
978
		}
979
980 View Code Duplication
		if ( 'inherit' === $post->post_status ) {
981
			$parent_post = get_post( $post->post_parent );
982
			$post_status_obj = get_post_status_object( $parent_post->post_status );
983
		} else {
984
			$post_status_obj = get_post_status_object( $post->post_status );
985
		}
986
987
		if ( !$post_status_obj->public ) {
988
			if ( is_user_logged_in() ) {
989
				if ( $post_status_obj->protected ) {
990
					if ( !current_user_can( 'edit_post', $post->ID ) ) {
991
						return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
992
					}
993
				} elseif ( $post_status_obj->private ) {
994
					if ( !current_user_can( 'read_post', $post->ID ) ) {
995
						return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
996
					}
997
				} elseif ( 'trash' === $post->post_status ) {
998
					if ( !current_user_can( 'edit_post', $post->ID ) ) {
999
						return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
1000
					}
1001
				} elseif ( 'auto-draft' === $post->post_status ) {
0 ignored issues
show
Unused Code introduced by
This elseif statement is empty, and could be removed.

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

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

Loading history...
1002
					//allow auto-drafts
1003
				} else {
1004
					return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
1005
				}
1006
			} else {
1007
				return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
1008
			}
1009
		}
1010
1011 View Code Duplication
		if (
1012
			-1 == get_option( 'blog_public' ) &&
1013
			/**
1014
			 * Filter access to a specific post.
1015
			 *
1016
			 * @module json-api
1017
			 *
1018
			 * @since 3.4.0
1019
			 *
1020
			 * @param bool current_user_can( 'read_post', $post->ID ) Can the current user access the post.
1021
			 * @param WP_Post $post Post data.
1022
			 */
1023
			! apply_filters(
1024
				'wpcom_json_api_user_can_view_post',
1025
				current_user_can( 'read_post', $post->ID ),
1026
				$post
1027
			)
1028
		) {
1029
			return new WP_Error( 'unauthorized', 'User cannot view post', array( 'status_code' => 403, 'error' => 'private_blog' ) );
1030
		}
1031
1032 View Code Duplication
		if ( strlen( $post->post_password ) && !current_user_can( 'edit_post', $post->ID ) ) {
1033
			return new WP_Error( 'unauthorized', 'User cannot view password protected post', array( 'status_code' => 403, 'error' => 'password_protected' ) );
1034
		}
1035
1036
		return true;
1037
	}
1038
1039
	/**
1040
	 * Returns author object.
1041
	 *
1042
	 * @param $author user ID, user row, WP_User object, comment row, post row
1043
	 * @param $show_email output the author's email address?
1044
	 *
1045
	 * @return (object)
1046
	 */
1047
	function get_author( $author, $show_email = false ) {
1048
		if ( isset( $author->comment_author_email ) && !$author->user_id ) {
1049
			$ID          = 0;
1050
			$login       = '';
1051
			$email       = $author->comment_author_email;
1052
			$name        = $author->comment_author;
1053
			$first_name  = '';
1054
			$last_name   = '';
1055
			$URL         = $author->comment_author_url;
1056
			$avatar_URL  = get_avatar_url( $author );
1057
			$profile_URL = 'https://en.gravatar.com/' . md5( strtolower( trim( $email ) ) );
1058
			$nice        = '';
1059
			$site_id     = -1;
1060
1061
			// Comment author URLs and Emails are sent through wp_kses() on save, which replaces "&" with "&amp;"
1062
			// "&" is the only email/URL character altered by wp_kses()
1063
			foreach ( array( 'email', 'URL' ) as $field ) {
1064
				$$field = str_replace( '&amp;', '&', $$field );
1065
			}
1066
		} else {
1067
			if ( isset( $author->user_id ) && $author->user_id ) {
1068
				$author = $author->user_id;
1069
			} elseif ( isset( $author->user_email ) ) {
1070
				$author = $author->ID;
1071
			} elseif ( isset( $author->post_author ) ) {
1072
				// then $author is a Post Object.
1073
				if ( 0 == $author->post_author )
1074
					return null;
1075
				/**
1076
				 * Filter whether the current site is a Jetpack site.
1077
				 *
1078
				 * @module json-api
1079
				 *
1080
				 * @since 3.3.0
1081
				 *
1082
				 * @param bool false Is the current site a Jetpack site. Default to false.
1083
				 * @param int get_current_blog_id() Blog ID.
1084
				 */
1085
				$is_jetpack = true === apply_filters( 'is_jetpack_site', false, get_current_blog_id() );
1086
				$post_id = $author->ID;
1087
				if ( $is_jetpack && ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) {
1088
					$ID         = get_post_meta( $post_id, '_jetpack_post_author_external_id', true );
1089
					$email      = get_post_meta( $post_id, '_jetpack_author_email', true );
1090
					$login      = '';
1091
					$name       = get_post_meta( $post_id, '_jetpack_author', true );
1092
					$first_name = '';
1093
					$last_name  = '';
1094
					$URL        = '';
1095
					$nice       = '';
1096
				} else {
1097
					$author = $author->post_author;
1098
				}
1099
			}
1100
1101
			if ( ! isset( $ID ) ) {
1102
				$user = get_user_by( 'id', $author );
1103
				if ( ! $user || is_wp_error( $user ) ) {
1104
					trigger_error( 'Unknown user', E_USER_WARNING );
1105
1106
					return null;
1107
				}
1108
				$ID         = $user->ID;
1109
				$email      = $user->user_email;
1110
				$login      = $user->user_login;
1111
				$name       = $user->display_name;
1112
				$first_name = $user->first_name;
1113
				$last_name  = $user->last_name;
1114
				$URL        = $user->user_url;
1115
				$nice       = $user->user_nicename;
1116
			}
1117 View Code Duplication
			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...
1118
				$active_blog = get_active_blog_for_user( $ID );
1119
				$site_id     = $active_blog->blog_id;
1120
				$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...
1121
			} else {
1122
				$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...
1123
				$site_id     = -1;
1124
			}
1125
1126
			$avatar_URL = get_avatar_url( $email );
1127
		}
1128
1129
		$email = $show_email ? (string) $email : false;
1130
1131
		$author = array(
1132
			'ID'          => (int) $ID,
1133
			'login'       => (string) $login,
1134
			'email'       => $email, // (string|bool)
1135
			'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...
1136
			'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...
1137
			'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...
1138
			'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...
1139
			'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...
1140
			'avatar_URL'  => (string) esc_url_raw( $avatar_URL ),
1141
			'profile_URL' => (string) esc_url_raw( $profile_URL ),
1142
		);
1143
1144
		if ($site_id > -1) {
1145
			$author['site_ID'] = (int) $site_id;
1146
		}
1147
1148
		return (object) $author;
1149
	}
1150
1151
	function get_media_item( $media_id ) {
1152
		$media_item = get_post( $media_id );
1153
1154
		if ( !$media_item || is_wp_error( $media_item ) )
1155
			return new WP_Error( 'unknown_media', 'Unknown Media', 404 );
1156
1157
		$response = array(
1158
			'id'    => strval( $media_item->ID ),
1159
			'date' =>  (string) $this->format_date( $media_item->post_date_gmt, $media_item->post_date ),
1160
			'parent'           => $media_item->post_parent,
1161
			'link'             => wp_get_attachment_url( $media_item->ID ),
1162
			'title'            => $media_item->post_title,
1163
			'caption'          => $media_item->post_excerpt,
1164
			'description'      => $media_item->post_content,
1165
			'metadata'         => wp_get_attachment_metadata( $media_item->ID ),
1166
		);
1167
1168
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM && is_array( $response['metadata'] ) && ! empty( $response['metadata']['file'] ) ) {
1169
			remove_filter( '_wp_relative_upload_path', 'wpcom_wp_relative_upload_path', 10 );
1170
			$response['metadata']['file'] = _wp_relative_upload_path( $response['metadata']['file'] );
1171
			add_filter( '_wp_relative_upload_path', 'wpcom_wp_relative_upload_path', 10, 2 );
1172
		}
1173
1174
		$response['meta'] = (object) array(
1175
			'links' => (object) array(
1176
				'self' => (string) $this->links->get_media_link( $this->api->get_blog_id_for_output(), $media_id ),
1177
				'help' => (string) $this->links->get_media_link( $this->api->get_blog_id_for_output(), $media_id, 'help' ),
1178
				'site' => (string) $this->links->get_site_link( $this->api->get_blog_id_for_output() ),
1179
			),
1180
		);
1181
1182
		return (object) $response;
1183
	}
1184
1185
	function get_media_item_v1_1( $media_id ) {
1186
		$media_item = get_post( $media_id );
1187
1188
		if ( ! $media_item || is_wp_error( $media_item ) )
1189
			return new WP_Error( 'unknown_media', 'Unknown Media', 404 );
1190
1191
		$file = basename( wp_get_attachment_url( $media_item->ID ) );
1192
		$file_info = pathinfo( $file );
1193
		$ext  = isset( $file_info['extension'] ) ? $file_info['extension'] : null;
1194
1195
		$response = array(
1196
			'ID'           => $media_item->ID,
1197
			'URL'          => wp_get_attachment_url( $media_item->ID ),
1198
			'guid'         => $media_item->guid,
1199
			'date'         => (string) $this->format_date( $media_item->post_date_gmt, $media_item->post_date ),
1200
			'post_ID'      => $media_item->post_parent,
1201
			'author_ID'    => (int) $media_item->post_author,
1202
			'file'         => $file,
1203
			'mime_type'    => $media_item->post_mime_type,
1204
			'extension'    => $ext,
1205
			'title'        => $media_item->post_title,
1206
			'caption'      => $media_item->post_excerpt,
1207
			'description'  => $media_item->post_content,
1208
			'alt'          => get_post_meta( $media_item->ID, '_wp_attachment_image_alt', true ),
1209
			'thumbnails'   => array()
1210
		);
1211
1212 View Code Duplication
		if ( in_array( $ext, array( 'jpg', 'jpeg', 'png', 'gif' ) ) ) {
1213
			$metadata = wp_get_attachment_metadata( $media_item->ID );
1214
			if ( isset( $metadata['height'], $metadata['width'] ) ) {
1215
				$response['height'] = $metadata['height'];
1216
				$response['width'] = $metadata['width'];
1217
			}
1218
1219
			if ( isset( $metadata['sizes'] ) ) {
1220
				/**
1221
				 * Filter the thumbnail sizes available for each attachment ID.
1222
				 *
1223
				 * @module json-api
1224
				 *
1225
				 * @since 3.9.0
1226
				 *
1227
				 * @param array $metadata['sizes'] Array of thumbnail sizes available for a given attachment ID.
1228
				 * @param string $media_id Attachment ID.
1229
				 */
1230
				$sizes = apply_filters( 'rest_api_thumbnail_sizes', $metadata['sizes'], $media_id );
1231
				if ( is_array( $sizes ) ) {
1232
					foreach ( $sizes as $size => $size_details ) {
1233
						$response['thumbnails'][ $size ] = dirname( $response['URL'] ) . '/' . $size_details['file'];
1234
					}
1235
				}
1236
			}
1237
1238
			if ( isset( $metadata['image_meta'] ) ) {
1239
				$response['exif'] = $metadata['image_meta'];
1240
			}
1241
		}
1242
1243 View Code Duplication
		if ( in_array( $ext, array( 'mp3', 'm4a', 'wav', 'ogg' ) ) ) {
1244
			$metadata = wp_get_attachment_metadata( $media_item->ID );
1245
			$response['length'] = $metadata['length'];
1246
			$response['exif']   = $metadata;
1247
		}
1248
1249 View Code Duplication
		if (
1250
		        in_array( $ext, array( 'ogv', 'mp4', 'mov', 'wmv', 'avi', 'mpg', '3gp', '3g2', 'm4v' ) )
1251
            ||
1252
                $response['mime_type'] === 'video/videopress'
1253
        ) {
1254
			$metadata = wp_get_attachment_metadata( $media_item->ID );
1255
			if ( isset( $metadata['height'], $metadata['width'] ) ) {
1256
				$response['height'] = $metadata['height'];
1257
				$response['width']  = $metadata['width'];
1258
			}
1259
1260
			if ( isset( $metadata['length'] ) ) {
1261
				$response['length'] = $metadata['length'];
1262
			}
1263
1264
			// add VideoPress info
1265
			if ( function_exists( 'video_get_info_by_blogpostid' ) ) {
1266
				$info = video_get_info_by_blogpostid( $this->api->get_blog_id_for_output(), $media_id );
1267
1268
				// Thumbnails
1269
				if ( function_exists( 'video_format_done' ) && function_exists( 'video_image_url_by_guid' ) ) {
1270
					$response['thumbnails'] = array( 'fmt_hd' => '', 'fmt_dvd' => '', 'fmt_std' => '' );
1271
					foreach ( $response['thumbnails'] as $size => $thumbnail_url ) {
1272
						if ( video_format_done( $info, $size ) ) {
0 ignored issues
show
Bug introduced by
It seems like $info defined by video_get_info_by_blogpo...or_output(), $media_id) on line 1266 can also be of type boolean; however, video_format_done() does only seem to accept object<stdClass>, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1273
							$response['thumbnails'][ $size ] = video_image_url_by_guid( $info->guid, $size );
1274
						} else {
1275
							unset( $response['thumbnails'][ $size ] );
1276
						}
1277
					}
1278
				}
1279
1280
				$response['videopress_guid'] = $info->guid;
1281
				$response['videopress_processing_done'] = true;
1282
				if ( '0000-00-00 00:00:00' == $info->finish_date_gmt ) {
1283
					$response['videopress_processing_done'] = false;
1284
				}
1285
			}
1286
		}
1287
1288
		$response['thumbnails'] = (object) $response['thumbnails'];
1289
1290
		$response['meta'] = (object) array(
1291
			'links' => (object) array(
1292
				'self' => (string) $this->links->get_media_link( $this->api->get_blog_id_for_output(), $media_id ),
1293
				'help' => (string) $this->links->get_media_link( $this->api->get_blog_id_for_output(), $media_id, 'help' ),
1294
				'site' => (string) $this->links->get_site_link( $this->api->get_blog_id_for_output() ),
1295
			),
1296
		);
1297
1298
		// add VideoPress link to the meta
1299 View Code Duplication
		if ( in_array( $ext, array( 'ogv', 'mp4', 'mov', 'wmv', 'avi', 'mpg', '3gp', '3g2', 'm4v' ) ) ) {
1300
			if ( function_exists( 'video_get_info_by_blogpostid' ) ) {
1301
				$response['meta']->links->videopress = (string) $this->links->get_link( '/videos/%s', $response['videopress_guid'], '' );
1302
			}
1303
		}
1304
1305 View Code Duplication
		if ( $media_item->post_parent > 0 ) {
1306
			$response['meta']->links->parent = (string) $this->links->get_post_link( $this->api->get_blog_id_for_output(), $media_item->post_parent );
1307
		}
1308
1309
		return (object) $response;
1310
	}
1311
1312
	function get_taxonomy( $taxonomy_id, $taxonomy_type, $context ) {
1313
1314
		$taxonomy = get_term_by( 'slug', $taxonomy_id, $taxonomy_type );
1315
		/// keep updating this function
1316
		if ( !$taxonomy || is_wp_error( $taxonomy ) ) {
1317
			return new WP_Error( 'unknown_taxonomy', 'Unknown taxonomy', 404 );
1318
		}
1319
1320
		return $this->format_taxonomy( $taxonomy, $taxonomy_type, $context );
1321
	}
1322
1323 View Code Duplication
	function format_taxonomy( $taxonomy, $taxonomy_type, $context ) {
1324
		// Permissions
1325
		switch ( $context ) {
1326
		case 'edit' :
1327
			$tax = get_taxonomy( $taxonomy_type );
1328
			if ( !current_user_can( $tax->cap->edit_terms ) )
1329
				return new WP_Error( 'unauthorized', 'User cannot edit taxonomy', 403 );
1330
			break;
1331
		case 'display' :
1332
			if ( -1 == get_option( 'blog_public' ) && ! current_user_can( 'read' ) ) {
1333
				return new WP_Error( 'unauthorized', 'User cannot view taxonomy', 403 );
1334
			}
1335
			break;
1336
		default :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a DEFAULT statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in the default statement.

switch ($expr) {
    default : //wrong
        doSomething();
        break;
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1337
			return new WP_Error( 'invalid_context', 'Invalid API CONTEXT', 400 );
1338
		}
1339
1340
		$response                = array();
1341
		$response['ID']          = (int) $taxonomy->term_id;
1342
		$response['name']        = (string) $taxonomy->name;
1343
		$response['slug']        = (string) $taxonomy->slug;
1344
		$response['description'] = (string) $taxonomy->description;
1345
		$response['post_count']  = (int) $taxonomy->count;
1346
1347
		if ( is_taxonomy_hierarchical( $taxonomy_type ) ) {
1348
			$response['parent'] = (int) $taxonomy->parent;
1349
		}
1350
1351
		$response['meta'] = (object) array(
1352
			'links' => (object) array(
1353
				'self' => (string) $this->links->get_taxonomy_link( $this->api->get_blog_id_for_output(), $taxonomy->slug, $taxonomy_type ),
1354
				'help' => (string) $this->links->get_taxonomy_link( $this->api->get_blog_id_for_output(), $taxonomy->slug, $taxonomy_type, 'help' ),
1355
				'site' => (string) $this->links->get_site_link( $this->api->get_blog_id_for_output() ),
1356
			),
1357
		);
1358
1359
		return (object) $response;
1360
	}
1361
1362
	/**
1363
	 * Returns ISO 8601 formatted datetime: 2011-12-08T01:15:36-08:00
1364
	 *
1365
	 * @param $date_gmt (string) GMT datetime string.
1366
	 * @param $date (string) Optional.  Used to calculate the offset from GMT.
1367
	 *
1368
	 * @return string
1369
	 */
1370
	function format_date( $date_gmt, $date = null ) {
1371
		return WPCOM_JSON_API_Date::format_date( $date_gmt, $date );
1372
	}
1373
1374
	/**
1375
	 * Parses a date string and returns the local and GMT representations
1376
	 * of that date & time in 'YYYY-MM-DD HH:MM:SS' format without
1377
	 * timezones or offsets. If the parsed datetime was not localized to a
1378
	 * particular timezone or offset we will assume it was given in GMT
1379
	 * relative to now and will convert it to local time using either the
1380
	 * timezone set in the options table for the blog or the GMT offset.
1381
	 *
1382
	 * @param datetime string
1383
	 *
1384
	 * @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...
1385
	 */
1386
	function parse_date( $date_string ) {
1387
		$date_string_info = date_parse( $date_string );
1388
		if ( is_array( $date_string_info ) && 0 === $date_string_info['error_count'] ) {
1389
			// Check if it's already localized. Can't just check is_localtime because date_parse('oppossum') returns true; WTF, PHP.
1390
			if ( isset( $date_string_info['zone'] ) && true === $date_string_info['is_localtime'] ) {
1391
				$dt_local = clone $dt_utc = new DateTime( $date_string );
1392
				$dt_utc->setTimezone( new DateTimeZone( 'UTC' ) );
1393
				return array(
1394
					(string) $dt_local->format( 'Y-m-d H:i:s' ),
1395
					(string) $dt_utc->format( 'Y-m-d H:i:s' ),
1396
				);
1397
			}
1398
1399
			// It's parseable but no TZ info so assume UTC
1400
			$dt_local = clone $dt_utc = new DateTime( $date_string, new DateTimeZone( 'UTC' ) );
1401
		} else {
1402
			// Could not parse time, use now in UTC
1403
			$dt_local = clone $dt_utc = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
1404
		}
1405
1406
		// First try to use timezone as it's daylight savings aware.
1407
		$timezone_string = get_option( 'timezone_string' );
1408
		if ( $timezone_string ) {
1409
			$tz = timezone_open( $timezone_string );
1410
			if ( $tz ) {
1411
				$dt_local->setTimezone( $tz );
1412
				return array(
1413
					(string) $dt_local->format( 'Y-m-d H:i:s' ),
1414
					(string) $dt_utc->format( 'Y-m-d H:i:s' ),
1415
				);
1416
			}
1417
		}
1418
1419
		// Fallback to GMT offset (in hours)
1420
		// NOTE: TZ of $dt_local is still UTC, we simply modified the timestamp with an offset.
1421
		$gmt_offset_seconds = intval( get_option( 'gmt_offset' ) * 3600 );
1422
		$dt_local->modify("+{$gmt_offset_seconds} seconds");
1423
		return array(
1424
			(string) $dt_local->format( 'Y-m-d H:i:s' ),
1425
			(string) $dt_utc->format( 'Y-m-d H:i:s' ),
1426
		);
1427
	}
1428
1429
	// Load the functions.php file for the current theme to get its post formats, CPTs, etc.
1430
	function load_theme_functions() {
1431
		// bail if we've done this already (can happen when calling /batch endpoint)
1432
		if ( defined( 'REST_API_THEME_FUNCTIONS_LOADED' ) )
1433
			return;
1434
1435
		// VIP context loading is handled elsewhere, so bail to prevent
1436
		// duplicate loading. See `switch_to_blog_and_validate_user()`
1437
		if ( function_exists( 'wpcom_is_vip' ) && wpcom_is_vip() ) {
1438
			return;
1439
		}
1440
1441
		define( 'REST_API_THEME_FUNCTIONS_LOADED', true );
1442
1443
		// the theme info we care about is found either within functions.php or one of the jetpack files.
1444
		$function_files = array( '/functions.php', '/inc/jetpack.compat.php', '/inc/jetpack.php', '/includes/jetpack.compat.php' );
1445
1446
		$copy_dirs = array( get_template_directory() );
1447
1448
		// Is this a child theme? Load the child theme's functions file.
1449
		if ( get_stylesheet_directory() !== get_template_directory() && wpcom_is_child_theme() ) {
1450
			foreach ( $function_files as $function_file ) {
1451
				if ( file_exists( get_stylesheet_directory() . $function_file ) ) {
1452
					require_once(  get_stylesheet_directory() . $function_file );
1453
				}
1454
			}
1455
			$copy_dirs[] = get_stylesheet_directory();
1456
		}
1457
1458
		foreach ( $function_files as $function_file ) {
1459
			if ( file_exists( get_template_directory() . $function_file ) ) {
1460
				require_once(  get_template_directory() . $function_file );
1461
			}
1462
		}
1463
1464
		// add inc/wpcom.php and/or includes/wpcom.php
1465
		wpcom_load_theme_compat_file();
1466
1467
		// since the stuff we care about (CPTS, post formats, are usually on setup or init hooks, we want to load those)
1468
		$this->copy_hooks( 'after_setup_theme', 'restapi_theme_after_setup_theme', $copy_dirs );
1469
1470
		/**
1471
		 * Fires functions hooked onto `after_setup_theme` by the theme for the purpose of the REST API.
1472
		 *
1473
		 * The REST API does not load the theme when processing requests.
1474
		 * To enable theme-based functionality, the API will load the '/functions.php',
1475
		 * '/inc/jetpack.compat.php', '/inc/jetpack.php', '/includes/jetpack.compat.php files
1476
		 * of the theme (parent and child) and copy functions hooked onto 'after_setup_theme' within those files.
1477
		 *
1478
		 * @module json-api
1479
		 *
1480
		 * @since 3.2.0
1481
		 */
1482
		do_action( 'restapi_theme_after_setup_theme' );
1483
		$this->copy_hooks( 'init', 'restapi_theme_init', $copy_dirs );
1484
1485
		/**
1486
		 * Fires functions hooked onto `init` by the theme for the purpose of the REST API.
1487
		 *
1488
		 * The REST API does not load the theme when processing requests.
1489
		 * To enable theme-based functionality, the API will load the '/functions.php',
1490
		 * '/inc/jetpack.compat.php', '/inc/jetpack.php', '/includes/jetpack.compat.php files
1491
		 * of the theme (parent and child) and copy functions hooked onto 'init' within those files.
1492
		 *
1493
		 * @module json-api
1494
		 *
1495
		 * @since 3.2.0
1496
		 */
1497
		do_action( 'restapi_theme_init' );
1498
	}
1499
1500
	function copy_hooks( $from_hook, $to_hook, $base_paths ) {
1501
		global $wp_filter;
1502
		foreach ( $wp_filter as $hook => $actions ) {
1503
1504
			if ( $from_hook != $hook ) {
1505
				continue;
1506
			}
1507
1508
			foreach ( (array) $actions as $priority => $callbacks ) {
1509
				foreach( $callbacks as $callback_key => $callback_data ) {
1510
					$callback = $callback_data['function'];
1511
1512
					// use reflection api to determine filename where function is defined
1513
					$reflection = $this->get_reflection( $callback );
1514
1515
					if ( false !== $reflection ) {
1516
						$file_name = $reflection->getFileName();
1517
						foreach( $base_paths as $base_path ) {
1518
1519
							// only copy hooks with functions which are part of the specified files
1520
							if ( 0 === strpos( $file_name, $base_path ) ) {
1521
								add_action(
1522
									$to_hook,
1523
									$callback_data['function'],
1524
									$priority,
1525
									$callback_data['accepted_args']
1526
								);
1527
							}
1528
						}
1529
					}
1530
				}
1531
			}
1532
		}
1533
	}
1534
1535
	function get_reflection( $callback ) {
1536
		if ( is_array( $callback ) ) {
1537
			list( $class, $method ) = $callback;
1538
			return new ReflectionMethod( $class, $method );
1539
		}
1540
1541
		if ( is_string( $callback ) && strpos( $callback, "::" ) !== false ) {
1542
			list( $class, $method ) = explode( "::", $callback );
1543
			return new ReflectionMethod( $class, $method );
1544
		}
1545
1546
		if ( version_compare( PHP_VERSION, "5.3.0", ">=" ) && method_exists( $callback, "__invoke" ) ) {
1547
			return new ReflectionMethod( $callback, "__invoke" );
1548
		}
1549
1550
		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...
1551
			return new ReflectionFunction( $callback );
1552
		}
1553
1554
		return false;
1555
	}
1556
1557
	/**
1558
	* Check whether a user can view or edit a post type
1559
	* @param string $post_type              post type to check
1560
	* @param string $context                'display' or 'edit'
1561
	* @return bool
1562
	*/
1563
	function current_user_can_access_post_type( $post_type, $context='display' ) {
1564
		$post_type_object = get_post_type_object( $post_type );
1565
		if ( ! $post_type_object ) {
1566
			return false;
1567
		}
1568
1569
		switch( $context ) {
1570
			case 'edit':
1571
				return current_user_can( $post_type_object->cap->edit_posts );
1572
			case 'display':
1573
				return $post_type_object->public || current_user_can( $post_type_object->cap->read_private_posts );
1574
			default:
1575
				return false;
1576
		}
1577
	}
1578
1579
	function is_post_type_allowed( $post_type ) {
1580
		// if the post type is empty, that's fine, WordPress will default to post
1581
		if ( empty( $post_type ) ) {
1582
			return true;
1583
		}
1584
1585
		// allow special 'any' type
1586
		if ( 'any' == $post_type ) {
1587
			return true;
1588
		}
1589
1590
		// check for allowed types
1591
		if ( in_array( $post_type, $this->_get_whitelisted_post_types() ) ) {
1592
			return true;
1593
		}
1594
1595
		if ( $post_type_object = get_post_type_object( $post_type ) ) {
1596
			if ( ! empty( $post_type_object->show_in_rest ) ) {
1597
				return $post_type_object->show_in_rest;
1598
			}
1599
			if ( ! empty( $post_type_object->publicly_queryable ) ) {
1600
				return $post_type_object->publicly_queryable;
1601
			}
1602
		}
1603
1604
		return ! empty( $post_type_object->public );
1605
	}
1606
1607
	/**
1608
	 * Gets the whitelisted post types that JP should allow access to.
1609
	 *
1610
	 * @return array Whitelisted post types.
1611
	 */
1612 View Code Duplication
	protected function _get_whitelisted_post_types() {
1613
		$allowed_types = array( 'post', 'page', 'revision' );
1614
1615
		/**
1616
		 * Filter the post types Jetpack has access to, and can synchronize with WordPress.com.
1617
		 *
1618
		 * @module json-api
1619
		 *
1620
		 * @since 2.2.3
1621
		 *
1622
		 * @param array $allowed_types Array of whitelisted post types. Default to `array( 'post', 'page', 'revision' )`.
1623
		 */
1624
		$allowed_types = apply_filters( 'rest_api_allowed_post_types', $allowed_types );
1625
1626
		return array_unique( $allowed_types );
1627
	}
1628
1629
	function handle_media_creation_v1_1( $media_files, $media_urls, $media_attrs = array(), $force_parent_id = false ) {
1630
1631
		add_filter( 'upload_mimes', array( $this, 'allow_video_uploads' ) );
1632
1633
		$media_ids = $errors = array();
1634
		$user_can_upload_files = current_user_can( 'upload_files' );
1635
		$media_attrs = array_values( $media_attrs ); // reset the keys
1636
		$i = 0;
1637
1638
		if ( ! empty( $media_files ) ) {
1639
			$this->api->trap_wp_die( 'upload_error' );
1640
			foreach ( $media_files as $media_item ) {
1641
				$_FILES['.api.media.item.'] = $media_item;
1642 View Code Duplication
				if ( ! $user_can_upload_files ) {
1643
					$media_id = new WP_Error( 'unauthorized', 'User cannot upload media.', 403 );
1644
				} else {
1645
					if ( $force_parent_id ) {
1646
						$parent_id = absint( $force_parent_id );
1647
					} elseif ( ! empty( $media_attrs[$i] ) && ! empty( $media_attrs[$i]['parent_id'] ) ) {
1648
						$parent_id = absint( $media_attrs[$i]['parent_id'] );
1649
					} else {
1650
						$parent_id = 0;
1651
					}
1652
					$media_id = media_handle_upload( '.api.media.item.', $parent_id );
1653
				}
1654
				if ( is_wp_error( $media_id ) ) {
1655
					$errors[$i]['file']   = $media_item['name'];
1656
					$errors[$i]['error']   = $media_id->get_error_code();
1657
					$errors[$i]['message'] = $media_id->get_error_message();
1658
				} else {
1659
					$media_ids[$i] = $media_id;
1660
				}
1661
1662
				$i++;
1663
			}
1664
			$this->api->trap_wp_die( null );
1665
			unset( $_FILES['.api.media.item.'] );
1666
		}
1667
1668
		if ( ! empty( $media_urls ) ) {
1669
			foreach ( $media_urls as $url ) {
1670 View Code Duplication
				if ( ! $user_can_upload_files ) {
1671
					$media_id = new WP_Error( 'unauthorized', 'User cannot upload media.', 403 );
1672
				} else {
1673
					if ( $force_parent_id ) {
1674
						$parent_id = absint( $force_parent_id );
1675
					} else if ( ! empty( $media_attrs[$i] ) && ! empty( $media_attrs[$i]['parent_id'] ) ) {
1676
						$parent_id = absint( $media_attrs[$i]['parent_id'] );
1677
					} else {
1678
						$parent_id = 0;
1679
					}
1680
					$media_id = $this->handle_media_sideload( $url, $parent_id );
1681
				}
1682
				if ( is_wp_error( $media_id ) ) {
1683
					$errors[$i] = array(
1684
						'file'    => $url,
1685
						'error'   => $media_id->get_error_code(),
1686
						'message' => $media_id->get_error_message(),
1687
					);
1688
				} elseif ( ! empty( $media_id ) ) {
1689
					$media_ids[$i] = $media_id;
1690
				}
1691
1692
				$i++;
1693
			}
1694
		}
1695
1696
		if ( ! empty( $media_attrs ) ) {
1697
			foreach ( $media_ids as $index => $media_id ) {
1698
				if ( empty( $media_attrs[$index] ) )
1699
					continue;
1700
1701
				$attrs = $media_attrs[$index];
1702
				$insert = array();
1703
1704
				// Attributes: Title, Caption, Description
1705
1706
				if ( isset( $attrs['title'] ) ) {
1707
					$insert['post_title'] = $attrs['title'];
1708
				}
1709
1710
				if ( isset( $attrs['caption'] ) ) {
1711
					$insert['post_excerpt'] = $attrs['caption'];
1712
				}
1713
1714
				if ( isset( $attrs['description'] ) ) {
1715
					$insert['post_content'] = $attrs['description'];
1716
				}
1717
1718
				if ( ! empty( $insert ) ) {
1719
					$insert['ID'] = $media_id;
1720
					wp_update_post( (object) $insert );
1721
				}
1722
1723
				// Attributes: Alt
1724
1725 View Code Duplication
				if ( isset( $attrs['alt'] ) ) {
1726
					$alt = wp_strip_all_tags( $attrs['alt'], true );
1727
					update_post_meta( $media_id, '_wp_attachment_image_alt', $alt );
1728
				}
1729
1730
				// Attributes: Artist, Album
1731
1732
				$id3_meta = array();
1733
1734 View Code Duplication
				foreach ( array( 'artist', 'album' ) as $key ) {
1735
					if ( isset( $attrs[ $key ] ) ) {
1736
						$id3_meta[ $key ] = wp_strip_all_tags( $attrs[ $key ], true );
1737
					}
1738
				}
1739
1740 View Code Duplication
				if ( ! empty( $id3_meta ) ) {
1741
					// Before updating metadata, ensure that the item is audio
1742
					$item = $this->get_media_item_v1_1( $media_id );
1743
					if ( 0 === strpos( $item->mime_type, 'audio/' ) ) {
1744
						wp_update_attachment_metadata( $media_id, $id3_meta );
1745
					}
1746
				}
1747
			}
1748
		}
1749
1750
		return array( 'media_ids' => $media_ids, 'errors' => $errors );
1751
1752
	}
1753
1754
	function handle_media_sideload( $url, $parent_post_id = 0, $type = 'any' ) {
1755
		if ( ! function_exists( 'download_url' ) || ! function_exists( 'media_handle_sideload' ) )
1756
			return false;
1757
1758
		// if we didn't get a URL, let's bail
1759
		$parsed = @parse_url( $url );
1760
		if ( empty( $parsed ) )
1761
			return false;
1762
1763
		$tmp = download_url( $url );
1764
		if ( is_wp_error( $tmp ) ) {
1765
			return $tmp;
1766
		}
1767
1768
		// First check to see if we get a mime-type match by file, otherwise, check to
1769
		// see if WordPress supports this file as an image. If neither, then it is not supported.
1770
		if ( ! $this->is_file_supported_for_sideloading( $tmp ) && 'image' === $type && ! file_is_displayable_image( $tmp ) ) {
1771
			@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...
1772
			return false;
1773
		}
1774
1775
		// emulate a $_FILES entry
1776
		$file_array = array(
1777
			'name' => basename( parse_url( $url, PHP_URL_PATH ) ),
1778
			'tmp_name' => $tmp,
1779
		);
1780
1781
		$id = media_handle_sideload( $file_array, $parent_post_id );
1782
		@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...
1783
1784
		if ( is_wp_error( $id ) ) {
1785
			return $id;
1786
		}
1787
1788
		if ( ! $id || ! is_int( $id ) ) {
1789
			return false;
1790
		}
1791
1792
		return $id;
1793
	}
1794
1795
	/**
1796
	 * Checks that the mime type of the specified file is among those in a filterable list of mime types.
1797
	 *
1798
	 * @param string $file Path to file to get its mime type.
1799
	 *
1800
	 * @return bool
1801
	 */
1802 View Code Duplication
	protected function is_file_supported_for_sideloading( $file ) {
1803
		if ( class_exists( 'finfo' ) ) { // php 5.3+
1804
			$finfo = new finfo( FILEINFO_MIME );
1805
			$mime = explode( '; ', $finfo->file( $file ) );
1806
			$type = $mime[0];
1807
1808
		} elseif ( function_exists( 'mime_content_type' ) ) { // PHP 5.2
1809
			$type = mime_content_type( $file );
1810
1811
		} else {
1812
			return false;
1813
		}
1814
1815
		/**
1816
		 * Filter the list of supported mime types for media sideloading.
1817
		 *
1818
		 * @since 4.0.0
1819
		 *
1820
		 * @module json-api
1821
		 *
1822
		 * @param array $supported_mime_types Array of the supported mime types for media sideloading.
1823
		 */
1824
		$supported_mime_types = apply_filters( 'jetpack_supported_media_sideload_types', array(
1825
			'image/png',
1826
			'image/jpeg',
1827
			'image/gif',
1828
			'image/bmp',
1829
			'video/quicktime',
1830
			'video/mp4',
1831
			'video/mpeg',
1832
			'video/ogg',
1833
			'video/3gpp',
1834
			'video/3gpp2',
1835
			'video/h261',
1836
			'video/h262',
1837
			'video/h264',
1838
			'video/x-msvideo',
1839
			'video/x-ms-wmv',
1840
			'video/x-ms-asf',
1841
		) );
1842
1843
		// If the type returned was not an array as expected, then we know we don't have a match.
1844
		if ( ! is_array( $supported_mime_types ) ) {
1845
			return false;
1846
		}
1847
1848
		return in_array( $type, $supported_mime_types );
1849
	}
1850
1851
	function allow_video_uploads( $mimes ) {
1852
		// if we are on Jetpack, bail - Videos are already allowed
1853
		if ( ! defined( 'IS_WPCOM' ) || !IS_WPCOM ) {
1854
			return $mimes;
1855
		}
1856
1857
		// extra check that this filter is only ever applied during REST API requests
1858
		if ( ! defined( 'REST_API_REQUEST' ) || ! REST_API_REQUEST ) {
1859
			return $mimes;
1860
		}
1861
1862
		// bail early if they already have the upgrade..
1863
		if ( get_option( 'video_upgrade' ) == '1' ) {
1864
			return $mimes;
1865
		}
1866
1867
		// lets whitelist to only specific clients right now
1868
		$clients_allowed_video_uploads = array();
1869
		/**
1870
		 * Filter the list of whitelisted video clients.
1871
		 *
1872
		 * @module json-api
1873
		 *
1874
		 * @since 3.2.0
1875
		 *
1876
		 * @param array $clients_allowed_video_uploads Array of whitelisted Video clients.
1877
		 */
1878
		$clients_allowed_video_uploads = apply_filters( 'rest_api_clients_allowed_video_uploads', $clients_allowed_video_uploads );
1879
		if ( !in_array( $this->api->token_details['client_id'], $clients_allowed_video_uploads ) ) {
1880
			return $mimes;
1881
		}
1882
1883
		$mime_list = wp_get_mime_types();
1884
1885
		$video_exts = explode( ' ', get_site_option( 'video_upload_filetypes', false, false ) );
1886
		/**
1887
		 * Filter the video filetypes allowed on the site.
1888
		 *
1889
		 * @module json-api
1890
		 *
1891
		 * @since 3.2.0
1892
		 *
1893
		 * @param array $video_exts Array of video filetypes allowed on the site.
1894
		 */
1895
		$video_exts = apply_filters( 'video_upload_filetypes', $video_exts );
1896
		$video_mimes = array();
1897
1898
		if ( !empty( $video_exts ) ) {
1899
			foreach ( $video_exts as $ext ) {
1900
				foreach ( $mime_list as $ext_pattern => $mime ) {
1901
					if ( $ext != '' && strpos( $ext_pattern, $ext ) !== false )
1902
						$video_mimes[$ext_pattern] = $mime;
1903
				}
1904
			}
1905
1906
			$mimes = array_merge( $mimes, $video_mimes );
1907
		}
1908
1909
		return $mimes;
1910
	}
1911
1912
	function is_current_site_multi_user() {
1913
		$users = wp_cache_get( 'site_user_count', 'WPCOM_JSON_API_Endpoint' );
1914
		if ( false === $users ) {
1915
			$user_query = new WP_User_Query( array(
1916
				'blog_id' => get_current_blog_id(),
1917
				'fields'  => 'ID',
1918
			) );
1919
			$users = (int) $user_query->get_total();
1920
			wp_cache_set( 'site_user_count', $users, 'WPCOM_JSON_API_Endpoint', DAY_IN_SECONDS );
1921
		}
1922
		return $users > 1;
1923
	}
1924
1925
	function allows_cross_origin_requests() {
1926
		return 'GET' == $this->method || $this->allow_cross_origin_request;
1927
	}
1928
1929
	function allows_unauthorized_requests( $origin, $complete_access_origins  ) {
1930
		return 'GET' == $this->method || ( $this->allow_unauthorized_request && in_array( $origin, $complete_access_origins ) );
1931
	}
1932
1933
	function get_platform() {
1934
		return wpcom_get_sal_platform( $this->api->token_details );
1935
	}
1936
1937
	/**
1938
	 * Return endpoint response
1939
	 *
1940
	 * @param ... determined by ->$path
1941
	 *
1942
	 * @return
1943
	 * 	falsy: HTTP 500, no response body
1944
	 *	WP_Error( $error_code, $error_message, $http_status_code ): HTTP $status_code, json_encode( array( 'error' => $error_code, 'message' => $error_message ) ) response body
1945
	 *	$data: HTTP 200, json_encode( $data ) response body
1946
	 */
1947
	abstract function callback( $path = '' );
1948
1949
1950
}
1951
1952
require_once( dirname( __FILE__ ) . '/json-endpoints.php' );
1953