Completed
Pull Request — feature/sync-json-endpoints (#7169)
by
unknown
11:30
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
	/**
121
	 * @var bool Set to true if the endpoint should accept auth from an upload token.
122
	 */
123
	public $allow_upload_token_auth = false;
124
125
	function __construct( $args ) {
126
		$defaults = array(
127
			'in_testing'           => false,
128
			'allowed_if_flagged'   => false,
129
			'allowed_if_red_flagged' => false,
130
			'description'          => '',
131
			'group'	               => '',
132
			'method'               => 'GET',
133
			'path'                 => '/',
134
			'min_version'          => '0',
135
			'max_version'          => WPCOM_JSON_API__CURRENT_VERSION,
136
			'force'	               => '',
137
			'deprecated'           => false,
138
			'new_version'          => WPCOM_JSON_API__CURRENT_VERSION,
139
			'jp_disabled'          => false,
140
			'path_labels'          => array(),
141
			'request_format'       => array(),
142
			'response_format'      => array(),
143
			'query_parameters'     => array(),
144
			'version'              => 'v1',
145
			'example_request'      => '',
146
			'example_request_data' => '',
147
			'example_response'     => '',
148
			'required_scope'       => '',
149
			'pass_wpcom_user_details' => false,
150
			'custom_fields_filtering' => false,
151
			'allow_cross_origin_request' => false,
152
			'allow_unauthorized_request' => false,
153
			'allow_jetpack_site_auth'    => false,
154
			'allow_upload_token_auth'    => false,
155
		);
156
157
		$args = wp_parse_args( $args, $defaults );
158
159
		$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...
160
161
		$this->allowed_if_flagged = $args['allowed_if_flagged'];
162
		$this->allowed_if_red_flagged = $args['allowed_if_red_flagged'];
163
164
		$this->description = $args['description'];
165
		$this->group       = $args['group'];
166
		$this->stat        = $args['stat'];
167
		$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...
168
		$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...
169
170
		$this->method      = $args['method'];
171
		$this->path        = $args['path'];
172
		$this->path_labels = $args['path_labels'];
173
		$this->min_version = $args['min_version'];
174
		$this->max_version = $args['max_version'];
175
		$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...
176
		$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...
177
178
		// Ensure max version is not less than min version
179
		if ( version_compare( $this->min_version, $this->max_version, '>' ) ) {
180
			$this->max_version = $this->min_version;
181
		}
182
183
		$this->pass_wpcom_user_details = $args['pass_wpcom_user_details'];
184
		$this->custom_fields_filtering = (bool) $args['custom_fields_filtering'];
185
186
		$this->allow_cross_origin_request = (bool) $args['allow_cross_origin_request'];
187
		$this->allow_unauthorized_request = (bool) $args['allow_unauthorized_request'];
188
		$this->allow_jetpack_site_auth    = (bool) $args['allow_jetpack_site_auth'];
189
		$this->allow_upload_token_auth    = (bool) $args['allow_upload_token_auth'];
190
191
		$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...
192
193
		$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...
194
195 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...
196
			$this->request_format = array_filter( array_merge( $this->request_format, $args['request_format'] ) );
197
		} else {
198
			$this->request_format = $args['request_format'];
199
		}
200
201 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...
202
			$this->response_format = array_filter( array_merge( $this->response_format, $args['response_format'] ) );
203
		} else {
204
			$this->response_format = $args['response_format'];
205
		}
206
207
		if ( false === $args['query_parameters'] ) {
208
			$this->query = array();
209
		} elseif ( is_array( $args['query_parameters'] ) ) {
210
			$this->query = array_filter( array_merge( $this->query, $args['query_parameters'] ) );
211
		}
212
213
		$this->api = WPCOM_JSON_API::init(); // Auto-add to WPCOM_JSON_API
214
		$this->links = WPCOM_JSON_API_Links::getInstance();
215
216
		/** Example Request/Response ******************************************/
217
218
		// Examples for endpoint documentation request
219
		$this->example_request      = $args['example_request'];
220
		$this->example_request_data = $args['example_request_data'];
221
		$this->example_response     = $args['example_response'];
222
223
		$this->api->add( $this );
224
	}
225
226
	// Get all query args.  Prefill with defaults
227
	function query_args( $return_default_values = true, $cast_and_filter = true ) {
228
		$args = array_intersect_key( $this->api->query, $this->query );
229
230
		if ( !$cast_and_filter ) {
231
			return $args;
232
		}
233
234
		return $this->cast_and_filter( $args, $this->query, $return_default_values );
235
	}
236
237
	// Get POST body data
238
	function input( $return_default_values = true, $cast_and_filter = true ) {
239
		$input = trim( $this->api->post_body );
240
		$content_type = $this->api->content_type;
241
		if ( $content_type ) {
242
			list ( $content_type ) = explode( ';', $content_type );
243
		}
244
		$content_type = trim( $content_type );
245
		switch ( $content_type ) {
246
		case 'application/json' :
247
		case 'application/x-javascript' :
248
		case 'text/javascript' :
249
		case 'text/x-javascript' :
250
		case 'text/x-json' :
251
		case 'text/json' :
252
			$return = json_decode( $input, true );
253
254
			if ( function_exists( 'json_last_error' ) ) {
255
				if ( JSON_ERROR_NONE !== json_last_error() ) {
256
					return null;
257
				}
258
			} else {
259
				if ( is_null( $return ) && json_encode( null ) !== $input ) {
260
					return null;
261
				}
262
			}
263
264
			break;
265
		case 'multipart/form-data' :
266
			$return = array_merge( stripslashes_deep( $_POST ), $_FILES );
267
			break;
268
		case 'application/x-www-form-urlencoded' :
269
			//attempt JSON first, since probably a curl command
270
			$return = json_decode( $input, true );
271
272
			if ( is_null( $return ) ) {
273
				wp_parse_str( $input, $return );
274
			}
275
276
			break;
277
		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...
278
			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...
279
			break;
280
		}
281
282
		if ( !$cast_and_filter ) {
283
			return $return;
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...
284
		}
285
286
		return $this->cast_and_filter( $return, $this->request_format, $return_default_values );
287
	}
288
289
	function cast_and_filter( $data, $documentation, $return_default_values = false, $for_output = false ) {
290
		$return_as_object = false;
291
		if ( is_object( $data ) ) {
292
			// @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...
293
			$data = (array) $data;
294
			$return_as_object = true;
295
		} elseif ( !is_array( $data ) ) {
296
			return $data;
297
		}
298
299
		$boolean_arg = array( 'false', 'true' );
300
		$naeloob_arg = array( 'true', 'false' );
301
302
		$return = array();
303
304
		foreach ( $documentation as $key => $description ) {
305
			if ( is_array( $description ) ) {
306
				// String or boolean array keys only
307
				$whitelist = array_keys( $description );
308
309
				if ( $whitelist === $boolean_arg || $whitelist === $naeloob_arg ) {
310
					// Truthiness
311
					if ( isset( $data[$key] ) ) {
312
						$return[$key] = (bool) WPCOM_JSON_API::is_truthy( $data[$key] );
313
					} elseif ( $return_default_values ) {
314
						$return[$key] = $whitelist === $naeloob_arg; // Default to true for naeloob_arg and false for boolean_arg.
315
					}
316
				} elseif ( isset( $data[$key] ) && isset( $description[$data[$key]] ) ) {
317
					// String Key
318
					$return[$key] = (string) $data[$key];
319
				} elseif ( $return_default_values ) {
320
					// Default value
321
					$return[$key] = (string) current( $whitelist );
322
				}
323
324
				continue;
325
			}
326
327
			$types = $this->parse_types( $description );
328
			$type = array_shift( $types );
329
330
			// Explicit default - string and int only for now.  Always set these reguardless of $return_default_values
331
			if ( isset( $type['default'] ) ) {
332
				if ( !isset( $data[$key] ) ) {
333
					$data[$key] = $type['default'];
334
				}
335
			}
336
337
			if ( !isset( $data[$key] ) ) {
338
				continue;
339
			}
340
341
			$this->cast_and_filter_item( $return, $type, $key, $data[$key], $types, $for_output );
342
		}
343
344
		if ( $return_as_object ) {
345
			return (object) $return;
346
		}
347
348
		return $return;
349
	}
350
351
	/**
352
	 * Casts $value according to $type.
353
	 * Handles fallbacks for certain values of $type when $value is not that $type
354
	 * Currently, only handles fallback between string <-> array (two way), from string -> false (one way), and from object -> false (one way),
355
	 * and string -> object (one way)
356
	 *
357
	 * Handles "child types" - array:URL, object:category
358
	 * array:URL means an array of URLs
359
	 * object:category means a hash of categories
360
	 *
361
	 * Handles object typing - object>post means an object of type post
362
	 */
363
	function cast_and_filter_item( &$return, $type, $key, $value, $types = array(), $for_output = false ) {
364
		if ( is_string( $type ) ) {
365
			$type = compact( 'type' );
366
		}
367
368
		switch ( $type['type'] ) {
369
		case 'false' :
370
			$return[$key] = false;
371
			break;
372
		case 'url' :
373
			$return[$key] = (string) esc_url_raw( $value );
374
			break;
375
		case 'string' :
376
			// Fallback string -> array, or for string -> object
377
			if ( is_array( $value ) || is_object( $value ) ) {
378 View Code Duplication
				if ( !empty( $types[0] ) ) {
379
					$next_type = array_shift( $types );
380
					return $this->cast_and_filter_item( $return, $next_type, $key, $value, $types, $for_output );
381
				}
382
			}
383
384
			// Fallback string -> false
385 View Code Duplication
			if ( !is_string( $value ) ) {
386
				if ( !empty( $types[0] ) && 'false' === $types[0]['type'] ) {
387
					$next_type = array_shift( $types );
388
					return $this->cast_and_filter_item( $return, $next_type, $key, $value, $types, $for_output );
389
				}
390
			}
391
			$return[$key] = (string) $value;
392
			break;
393
		case 'html' :
394
			$return[$key] = (string) $value;
395
			break;
396
		case 'safehtml' :
397
			$return[$key] = wp_kses( (string) $value, wp_kses_allowed_html() );
398
			break;
399
		case 'zip' :
400
		case 'media' :
401
			if ( is_array( $value ) ) {
402
				if ( isset( $value['name'] ) && is_array( $value['name'] ) ) {
403
					// It's a $_FILES array
404
					// Reformat into array of $_FILES items
405
					$files = array();
406
407
					foreach ( $value['name'] as $k => $v ) {
408
						$files[$k] = array();
409
						foreach ( array_keys( $value ) as $file_key ) {
410
							$files[$k][$file_key] = $value[$file_key][$k];
411
						}
412
					}
413
414
					$return[$key] = $files;
415
					break;
416
				}
417
			} 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...
418
				// no break - treat as 'array'
419
			}
420
			// nobreak
421
		case 'array' :
422
			// Fallback array -> string
423 View Code Duplication
			if ( is_string( $value ) ) {
424
				if ( !empty( $types[0] ) ) {
425
					$next_type = array_shift( $types );
426
					return $this->cast_and_filter_item( $return, $next_type, $key, $value, $types, $for_output );
427
				}
428
			}
429
430 View Code Duplication
			if ( isset( $type['children'] ) ) {
431
				$children = array();
432
				foreach ( (array) $value as $k => $child ) {
433
					$this->cast_and_filter_item( $children, $type['children'], $k, $child, array(), $for_output );
434
				}
435
				$return[$key] = (array) $children;
436
				break;
437
			}
438
439
			$return[$key] = (array) $value;
440
			break;
441
		case 'iso 8601 datetime' :
442
		case 'datetime' :
443
			// (string)s
444
			$dates = $this->parse_date( (string) $value );
445
			if ( $for_output ) {
446
				$return[$key] = $this->format_date( $dates[1], $dates[0] );
447
			} else {
448
				list( $return[$key], $return["{$key}_gmt"] ) = $dates;
449
			}
450
			break;
451
		case 'float' :
452
			$return[$key] = (float) $value;
453
			break;
454
		case 'int' :
455
		case 'integer' :
456
			$return[$key] = (int) $value;
457
			break;
458
		case 'bool' :
459
		case 'boolean' :
460
			$return[$key] = (bool) WPCOM_JSON_API::is_truthy( $value );
461
			break;
462
		case 'object' :
463
			// Fallback object -> false
464 View Code Duplication
			if ( is_scalar( $value ) || is_null( $value ) ) {
465
				if ( !empty( $types[0] ) && 'false' === $types[0]['type'] ) {
466
					return $this->cast_and_filter_item( $return, 'false', $key, $value, $types, $for_output );
467
				}
468
			}
469
470 View Code Duplication
			if ( isset( $type['children'] ) ) {
471
				$children = array();
472
				foreach ( (array) $value as $k => $child ) {
473
					$this->cast_and_filter_item( $children, $type['children'], $k, $child, array(), $for_output );
474
				}
475
				$return[$key] = (object) $children;
476
				break;
477
			}
478
479
			if ( isset( $type['subtype'] ) ) {
480
				return $this->cast_and_filter_item( $return, $type['subtype'], $key, $value, $types, $for_output );
481
			}
482
483
			$return[$key] = (object) $value;
484
			break;
485
		case 'post' :
486
			$return[$key] = (object) $this->cast_and_filter( $value, $this->post_object_format, false, $for_output );
487
			break;
488
		case 'comment' :
489
			$return[$key] = (object) $this->cast_and_filter( $value, $this->comment_object_format, false, $for_output );
490
			break;
491
		case 'tag' :
492
		case 'category' :
493
			$docs = array(
494
				'ID'          => '(int)',
495
				'name'        => '(string)',
496
				'slug'        => '(string)',
497
				'description' => '(HTML)',
498
				'post_count'  => '(int)',
499
				'meta'        => '(object)',
500
			);
501
			if ( 'category' === $type['type'] ) {
502
				$docs['parent'] = '(int)';
503
			}
504
			$return[$key] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
505
			break;
506
		case 'post_reference' :
507 View Code Duplication
		case 'comment_reference' :
508
			$docs = array(
509
				'ID'    => '(int)',
510
				'type'  => '(string)',
511
				'title' => '(string)',
512
				'link'  => '(URL)',
513
			);
514
			$return[$key] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
515
			break;
516 View Code Duplication
		case 'geo' :
517
			$docs = array(
518
				'latitude'  => '(float)',
519
				'longitude' => '(float)',
520
				'address'   => '(string)',
521
			);
522
			$return[$key] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
523
			break;
524
		case 'author' :
525
			$docs = array(
526
				'ID'             => '(int)',
527
				'user_login'     => '(string)',
528
				'login'          => '(string)',
529
				'email'          => '(string|false)',
530
				'name'           => '(string)',
531
				'first_name'     => '(string)',
532
				'last_name'      => '(string)',
533
				'nice_name'      => '(string)',
534
				'URL'            => '(URL)',
535
				'avatar_URL'     => '(URL)',
536
				'profile_URL'    => '(URL)',
537
				'is_super_admin' => '(bool)',
538
				'roles'          => '(array:string)'
539
			);
540
			$return[$key] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
541
			break;
542 View Code Duplication
		case 'role' :
543
			$docs = array(
544
				'name'         => '(string)',
545
				'display_name' => '(string)',
546
				'capabilities' => '(object:boolean)',
547
			);
548
			$return[$key] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
549
			break;
550
		case 'attachment' :
551
			$docs = array(
552
				'ID'        => '(int)',
553
				'URL'       => '(URL)',
554
				'guid'      => '(string)',
555
				'mime_type' => '(string)',
556
				'width'     => '(int)',
557
				'height'    => '(int)',
558
				'duration'  => '(int)',
559
			);
560
			$return[$key] = (object) $this->cast_and_filter(
561
				$value,
562
				/**
563
				 * Filter the documentation returned for a post attachment.
564
				 *
565
				 * @module json-api
566
				 *
567
				 * @since 1.9.0
568
				 *
569
				 * @param array $docs Array of documentation about a post attachment.
570
				 */
571
				apply_filters( 'wpcom_json_api_attachment_cast_and_filter', $docs ),
572
				false,
573
				$for_output
574
			);
575
			break;
576
		case 'metadata' :
577
			$docs = array(
578
				'id'       => '(int)',
579
				'key'       => '(string)',
580
				'value'     => '(string|false|float|int|array|object)',
581
				'previous_value' => '(string)',
582
				'operation'  => '(string)',
583
			);
584
			$return[$key] = (object) $this->cast_and_filter(
585
				$value,
586
				/** This filter is documented in class.json-api-endpoints.php */
587
				apply_filters( 'wpcom_json_api_attachment_cast_and_filter', $docs ),
588
				false,
589
				$for_output
590
			);
591
			break;
592
		case 'plugin' :
593
			$docs = array(
594
				'id'            => '(safehtml) The plugin\'s ID',
595
				'slug'          => '(safehtml) The plugin\'s Slug',
596
				'active'        => '(boolean)  The plugin status.',
597
				'update'        => '(object)   The plugin update info.',
598
				'name'          => '(safehtml) The name of the plugin.',
599
				'plugin_url'    => '(url)      Link to the plugin\'s web site.',
600
				'version'       => '(safehtml) The plugin version number.',
601
				'description'   => '(safehtml) Description of what the plugin does and/or notes from the author',
602
				'author'        => '(safehtml) The plugin author\'s name',
603
				'author_url'    => '(url)      The plugin author web site address',
604
				'network'       => '(boolean)  Whether the plugin can only be activated network wide.',
605
				'autoupdate'    => '(boolean)  Whether the plugin is auto updated',
606
				'log'           => '(array:safehtml) An array of update log strings.',
607
        'action_links'  => '(array) An array of action links that the plugin uses.',
608
			);
609
			$return[$key] = (object) $this->cast_and_filter(
610
				$value,
611
				/**
612
				 * Filter the documentation returned for a plugin.
613
				 *
614
				 * @module json-api
615
				 *
616
				 * @since 3.1.0
617
				 *
618
				 * @param array $docs Array of documentation about a plugin.
619
				 */
620
				apply_filters( 'wpcom_json_api_plugin_cast_and_filter', $docs ),
621
				false,
622
				$for_output
623
			);
624
			break;
625
		case 'jetpackmodule' :
626
			$docs = array(
627
				'id'          => '(string)   The module\'s ID',
628
				'active'      => '(boolean)  The module\'s status.',
629
				'name'        => '(string)   The module\'s name.',
630
				'description' => '(safehtml) The module\'s description.',
631
				'sort'        => '(int)      The module\'s display order.',
632
				'introduced'  => '(string)   The Jetpack version when the module was introduced.',
633
				'changed'     => '(string)   The Jetpack version when the module was changed.',
634
				'free'        => '(boolean)  The module\'s Free or Paid status.',
635
				'module_tags' => '(array)    The module\'s tags.'
636
			);
637
			$return[$key] = (object) $this->cast_and_filter(
638
				$value,
639
				/** This filter is documented in class.json-api-endpoints.php */
640
				apply_filters( 'wpcom_json_api_plugin_cast_and_filter', $docs ),
641
				false,
642
				$for_output
643
			);
644
			break;
645
		case 'sharing_button' :
646
			$docs = array(
647
				'ID'         => '(string)',
648
				'name'       => '(string)',
649
				'URL'        => '(string)',
650
				'icon'       => '(string)',
651
				'enabled'    => '(bool)',
652
				'visibility' => '(string)',
653
			);
654
			$return[$key] = (array) $this->cast_and_filter( $value, $docs, false, $for_output );
655
			break;
656
		case 'sharing_button_service':
657
			$docs = array(
658
				'ID'               => '(string) The service identifier',
659
				'name'             => '(string) The service name',
660
				'class_name'       => '(string) Class name for custom style sharing button elements',
661
				'genericon'        => '(string) The Genericon unicode character for the custom style sharing button icon',
662
				'preview_smart'    => '(string) An HTML snippet of a rendered sharing button smart preview',
663
				'preview_smart_js' => '(string) An HTML snippet of the page-wide initialization scripts used for rendering the sharing button smart preview'
664
			);
665
			$return[$key] = (array) $this->cast_and_filter( $value, $docs, false, $for_output );
666
			break;
667
		case 'taxonomy':
668
			$docs = array(
669
				'name'         => '(string) The taxonomy slug',
670
				'label'        => '(string) The taxonomy human-readable name',
671
				'labels'       => '(object) Mapping of labels for the taxonomy',
672
				'description'  => '(string) The taxonomy description',
673
				'hierarchical' => '(bool) Whether the taxonomy is hierarchical',
674
				'public'       => '(bool) Whether the taxonomy is public',
675
				'capabilities' => '(object) Mapping of current user capabilities for the taxonomy',
676
			);
677
			$return[$key] = (array) $this->cast_and_filter( $value, $docs, false, $for_output );
678
			break;
679
680
		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...
681
			$method_name = $type['type'] . '_docs';
682
			if ( method_exists( WPCOM_JSON_API_Jetpack_Overrides, $method_name ) ) {
683
				$docs = WPCOM_JSON_API_Jetpack_Overrides::$method_name();
684
			}
685
686
			if ( ! empty( $docs ) ) {
687
				$return[$key] = (object) $this->cast_and_filter(
688
					$value,
689
					/** This filter is documented in class.json-api-endpoints.php */
690
					apply_filters( 'wpcom_json_api_plugin_cast_and_filter', $docs ),
691
					false,
692
					$for_output
693
				);
694
			} else {
695
				trigger_error( "Unknown API casting type {$type['type']}", E_USER_WARNING );
696
			}
697
		}
698
	}
699
700
	function parse_types( $text ) {
701
		if ( !preg_match( '#^\(([^)]+)\)#', ltrim( $text ), $matches ) ) {
702
			return 'none';
703
		}
704
705
		$types = explode( '|', strtolower( $matches[1] ) );
706
		$return = array();
707
		foreach ( $types as $type ) {
708
			foreach ( array( ':' => 'children', '>' => 'subtype', '=' => 'default' ) as $operator => $meaning ) {
709
				if ( false !== strpos( $type, $operator ) ) {
710
					$item = explode( $operator, $type, 2 );
711
					$return[] = array( 'type' => $item[0], $meaning => $item[1] );
712
					continue 2;
713
				}
714
			}
715
			$return[] = compact( 'type' );
716
		}
717
718
		return $return;
719
	}
720
721
	/**
722
	 * Checks if the endpoint is publicly displayable
723
	 */
724
	function is_publicly_documentable() {
725
		return '__do_not_document' !== $this->group && true !== $this->in_testing;
726
	}
727
728
	/**
729
	 * Auto generates documentation based on description, method, path, path_labels, and query parameters.
730
	 * Echoes HTML.
731
	 */
732
	function document( $show_description = true ) {
733
		global $wpdb;
734
		$original_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : 'unset';
735
		unset( $GLOBALS['post'] );
736
737
		$doc = $this->generate_documentation();
738
739
		if ( $show_description ) :
740
?>
741
<caption>
742
	<h1><?php echo wp_kses_post( $doc['method'] ); ?> <?php echo wp_kses_post( $doc['path_labeled'] ); ?></h1>
743
	<p><?php echo wp_kses_post( $doc['description'] ); ?></p>
744
</caption>
745
746
<?php endif; ?>
747
748
<?php if ( true === $this->deprecated ) { ?>
749
<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...
750
<?php } ?>
751
752
<section class="resource-info">
753
	<h2 id="apidoc-resource-info">Resource Information</h2>
754
755
	<table class="api-doc api-doc-resource-parameters api-doc-resource">
756
757
	<thead>
758
		<tr>
759
			<th class="api-index-title" scope="column">&nbsp;</th>
760
			<th class="api-index-title" scope="column">&nbsp;</th>
761
		</tr>
762
	</thead>
763
	<tbody>
764
765
		<tr class="api-index-item">
766
			<th scope="row" class="parameter api-index-item-title">Method</th>
767
			<td class="type api-index-item-title"><?php echo wp_kses_post( $doc['method'] ); ?></td>
768
		</tr>
769
770
		<tr class="api-index-item">
771
			<th scope="row" class="parameter api-index-item-title">URL</th>
772
			<?php
773
			$version = WPCOM_JSON_API__CURRENT_VERSION;
774
			if ( !empty( $this->max_version ) ) {
775
				$version = $this->max_version;
776
			}
777
			?>
778
			<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>
779
		</tr>
780
781
		<tr class="api-index-item">
782
			<th scope="row" class="parameter api-index-item-title">Requires authentication?</th>
783
			<?php
784
			$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'] ) );
785
			?>
786
			<td class="type api-index-item-title"><?php echo ( true === (bool) $requires_auth->requires_authentication ? 'Yes' : 'No' ); ?></td>
787
		</tr>
788
789
	</tbody>
790
	</table>
791
792
</section>
793
794
<?php
795
796
		foreach ( array(
797
			'path'     => 'Method Parameters',
798
			'query'    => 'Query Parameters',
799
			'body'     => 'Request Parameters',
800
			'response' => 'Response Parameters',
801
		) as $doc_section_key => $label ) :
802
			$doc_section = 'response' === $doc_section_key ? $doc['response']['body'] : $doc['request'][$doc_section_key];
803
			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...
804
				continue;
805
			}
806
807
			$param_label = strtolower( str_replace( ' ', '-', $label ) );
808
?>
809
810
<section class="<?php echo $param_label; ?>">
811
812
<h2 id="apidoc-<?php echo esc_attr( $doc_section_key ); ?>"><?php echo wp_kses_post( $label ); ?></h2>
813
814
<table class="api-doc api-doc-<?php echo $param_label; ?>-parameters api-doc-<?php echo strtolower( str_replace( ' ', '-', $doc['group'] ) ); ?>">
815
816
<thead>
817
	<tr>
818
		<th class="api-index-title" scope="column">Parameter</th>
819
		<th class="api-index-title" scope="column">Type</th>
820
		<th class="api-index-title" scope="column">Description</th>
821
	</tr>
822
</thead>
823
<tbody>
824
825
<?php foreach ( $doc_section as $key => $item ) : ?>
826
827
	<tr class="api-index-item">
828
		<th scope="row" class="parameter api-index-item-title"><?php echo wp_kses_post( $key ); ?></th>
829
		<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...
830
		<td class="description api-index-item-body"><?php
831
832
		$this->generate_doc_description( $item['description'] );
833
834
		?></td>
835
	</tr>
836
837
<?php endforeach; ?>
838
</tbody>
839
</table>
840
</section>
841
<?php endforeach; ?>
842
843
<?php
844
		if ( 'unset' !== $original_post ) {
845
			$GLOBALS['post'] = $original_post;
846
		}
847
	}
848
849
	function add_http_build_query_to_php_content_example( $matches ) {
850
		$trimmed_match = ltrim( $matches[0] );
851
		$pad = substr( $matches[0], 0, -1 * strlen( $trimmed_match ) );
852
		$pad = ltrim( $pad, ' ' );
853
		$return = '  ' . str_replace( "\n", "\n  ", $matches[0] );
854
		return " http_build_query({$return}{$pad})";
855
	}
856
857
	/**
858
	 * Recursively generates the <dl>'s to document item descriptions.
859
	 * Echoes HTML.
860
	 */
861
	function generate_doc_description( $item ) {
862
		if ( is_array( $item ) ) : ?>
863
864
		<dl>
865
<?php			foreach ( $item as $description_key => $description_value ) : ?>
866
867
			<dt><?php echo wp_kses_post( $description_key . ':' ); ?></dt>
868
			<dd><?php $this->generate_doc_description( $description_value ); ?></dd>
869
870
<?php			endforeach; ?>
871
872
		</dl>
873
874
<?php
875
		else :
876
			echo wp_kses_post( $item );
877
		endif;
878
	}
879
880
	/**
881
	 * Auto generates documentation based on description, method, path, path_labels, and query parameters.
882
	 * Echoes HTML.
883
	 */
884
	function generate_documentation() {
885
		$format       = str_replace( '%d', '%s', $this->path );
886
		$path_labeled = $format;
887
		if ( ! empty( $this->path_labels ) ) {
888
			$path_labeled = vsprintf( $format, array_keys( $this->path_labels ) );
889
		}
890
		$boolean_arg  = array( 'false', 'true' );
891
		$naeloob_arg  = array( 'true', 'false' );
892
893
		$doc = array(
894
			'description'  => $this->description,
895
			'method'       => $this->method,
896
			'path_format'  => $this->path,
897
			'path_labeled' => $path_labeled,
898
			'group'        => $this->group,
899
			'request' => array(
900
				'path'  => array(),
901
				'query' => array(),
902
				'body'  => array(),
903
			),
904
			'response' => array(
905
				'body' => array(),
906
			)
907
		);
908
909
		foreach ( array( 'path_labels' => 'path', 'query' => 'query', 'request_format' => 'body', 'response_format' => 'body' ) as $_property => $doc_item ) {
910
			foreach ( (array) $this->$_property as $key => $description ) {
911
				if ( is_array( $description ) ) {
912
					$description_keys = array_keys( $description );
913
					if ( $boolean_arg === $description_keys || $naeloob_arg === $description_keys ) {
914
						$type = '(bool)';
915
					} else {
916
						$type = '(string)';
917
					}
918
919
					if ( 'response_format' !== $_property ) {
920
						// hack - don't show "(default)" in response format
921
						reset( $description );
922
						$description_key = key( $description );
923
						$description[$description_key] = "(default) {$description[$description_key]}";
924
					}
925
				} else {
926
					$types   = $this->parse_types( $description );
927
					$type    = array();
928
					$default = '';
929
930
					if ( 'none' == $types ) {
931
						$types = array();
932
						$types[]['type'] = 'none';
933
					}
934
935
					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...
936
						$type[] = $type_array['type'];
937
						if ( isset( $type_array['default'] ) ) {
938
							$default = $type_array['default'];
939
							if ( 'string' === $type_array['type'] ) {
940
								$default = "'$default'";
941
							}
942
						}
943
					}
944
					$type = '(' . join( '|', $type ) . ')';
945
					$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...
946
					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...
947
					$description = trim( $description );
948
					if ( $default ) {
949
						$description .= " Default: $default.";
950
					}
951
				}
952
953
				$item = compact( 'type', 'description' );
954
955
				if ( 'response_format' === $_property ) {
956
					$doc['response'][$doc_item][$key] = $item;
957
				} else {
958
					$doc['request'][$doc_item][$key] = $item;
959
				}
960
			}
961
		}
962
963
		return $doc;
964
	}
965
966
	function user_can_view_post( $post_id ) {
967
		$post = get_post( $post_id );
968
		if ( !$post || is_wp_error( $post ) ) {
969
			return false;
970
		}
971
972 View Code Duplication
		if ( 'inherit' === $post->post_status ) {
973
			$parent_post = get_post( $post->post_parent );
974
			$post_status_obj = get_post_status_object( $parent_post->post_status );
975
		} else {
976
			$post_status_obj = get_post_status_object( $post->post_status );
977
		}
978
979
		if ( !$post_status_obj->public ) {
980
			if ( is_user_logged_in() ) {
981
				if ( $post_status_obj->protected ) {
982
					if ( !current_user_can( 'edit_post', $post->ID ) ) {
983
						return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
984
					}
985
				} elseif ( $post_status_obj->private ) {
986
					if ( !current_user_can( 'read_post', $post->ID ) ) {
987
						return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
988
					}
989
				} elseif ( 'trash' === $post->post_status ) {
990
					if ( !current_user_can( 'edit_post', $post->ID ) ) {
991
						return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
992
					}
993
				} 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...
994
					//allow auto-drafts
995
				} else {
996
					return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
997
				}
998
			} else {
999
				return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
1000
			}
1001
		}
1002
1003 View Code Duplication
		if (
1004
			-1 == get_option( 'blog_public' ) &&
1005
			/**
1006
			 * Filter access to a specific post.
1007
			 *
1008
			 * @module json-api
1009
			 *
1010
			 * @since 3.4.0
1011
			 *
1012
			 * @param bool current_user_can( 'read_post', $post->ID ) Can the current user access the post.
1013
			 * @param WP_Post $post Post data.
1014
			 */
1015
			! apply_filters(
1016
				'wpcom_json_api_user_can_view_post',
1017
				current_user_can( 'read_post', $post->ID ),
1018
				$post
1019
			)
1020
		) {
1021
			return new WP_Error( 'unauthorized', 'User cannot view post', array( 'status_code' => 403, 'error' => 'private_blog' ) );
1022
		}
1023
1024 View Code Duplication
		if ( strlen( $post->post_password ) && !current_user_can( 'edit_post', $post->ID ) ) {
1025
			return new WP_Error( 'unauthorized', 'User cannot view password protected post', array( 'status_code' => 403, 'error' => 'password_protected' ) );
1026
		}
1027
1028
		return true;
1029
	}
1030
1031
	/**
1032
	 * Returns author object.
1033
	 *
1034
	 * @param $author user ID, user row, WP_User object, comment row, post row
1035
	 * @param $show_email output the author's email address?
1036
	 *
1037
	 * @return (object)
1038
	 */
1039
	function get_author( $author, $show_email = false ) {
1040
		if ( isset( $author->comment_author_email ) && !$author->user_id ) {
1041
			$ID          = 0;
1042
			$login       = '';
1043
			$email       = $author->comment_author_email;
1044
			$name        = $author->comment_author;
1045
			$first_name  = '';
1046
			$last_name   = '';
1047
			$URL         = $author->comment_author_url;
1048
			$avatar_URL  = get_avatar_url( $author );
1049
			$profile_URL = 'https://en.gravatar.com/' . md5( strtolower( trim( $email ) ) );
1050
			$nice        = '';
1051
			$site_id     = -1;
1052
1053
			// Comment author URLs and Emails are sent through wp_kses() on save, which replaces "&" with "&amp;"
1054
			// "&" is the only email/URL character altered by wp_kses()
1055
			foreach ( array( 'email', 'URL' ) as $field ) {
1056
				$$field = str_replace( '&amp;', '&', $$field );
1057
			}
1058
		} else {
1059
			if ( isset( $author->user_id ) && $author->user_id ) {
1060
				$author = $author->user_id;
1061
			} elseif ( isset( $author->user_email ) ) {
1062
				$author = $author->ID;
1063
			} elseif ( isset( $author->post_author ) ) {
1064
				// then $author is a Post Object.
1065
				if ( 0 == $author->post_author )
1066
					return null;
1067
				/**
1068
				 * Filter whether the current site is a Jetpack site.
1069
				 *
1070
				 * @module json-api
1071
				 *
1072
				 * @since 3.3.0
1073
				 *
1074
				 * @param bool false Is the current site a Jetpack site. Default to false.
1075
				 * @param int get_current_blog_id() Blog ID.
1076
				 */
1077
				$is_jetpack = true === apply_filters( 'is_jetpack_site', false, get_current_blog_id() );
1078
				$post_id = $author->ID;
1079
				if ( $is_jetpack && ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) {
1080
					$ID         = get_post_meta( $post_id, '_jetpack_post_author_external_id', true );
1081
					$email      = get_post_meta( $post_id, '_jetpack_author_email', true );
1082
					$login      = '';
1083
					$name       = get_post_meta( $post_id, '_jetpack_author', true );
1084
					$first_name = '';
1085
					$last_name  = '';
1086
					$URL        = '';
1087
					$nice       = '';
1088
				} else {
1089
					$author = $author->post_author;
1090
				}
1091
			}
1092
1093
			if ( ! isset( $ID ) ) {
1094
				$user = get_user_by( 'id', $author );
1095
				if ( ! $user || is_wp_error( $user ) ) {
1096
					trigger_error( 'Unknown user', E_USER_WARNING );
1097
1098
					return null;
1099
				}
1100
				$ID         = $user->ID;
1101
				$email      = $user->user_email;
1102
				$login      = $user->user_login;
1103
				$name       = $user->display_name;
1104
				$first_name = $user->first_name;
1105
				$last_name  = $user->last_name;
1106
				$URL        = $user->user_url;
1107
				$nice       = $user->user_nicename;
1108
			}
1109 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...
1110
				$active_blog = get_active_blog_for_user( $ID );
1111
				$site_id     = $active_blog->blog_id;
1112
				$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...
1113
			} else {
1114
				$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...
1115
				$site_id     = -1;
1116
			}
1117
1118
			$avatar_URL = get_avatar_url( $email );
1119
		}
1120
1121
		$email = $show_email ? (string) $email : false;
1122
1123
		$author = array(
1124
			'ID'          => (int) $ID,
1125
			'login'       => (string) $login,
1126
			'email'       => $email, // (string|bool)
1127
			'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...
1128
			'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...
1129
			'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...
1130
			'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...
1131
			'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...
1132
			'avatar_URL'  => (string) esc_url_raw( $avatar_URL ),
1133
			'profile_URL' => (string) esc_url_raw( $profile_URL ),
1134
		);
1135
1136
		if ($site_id > -1) {
1137
			$author['site_ID'] = (int) $site_id;
1138
		}
1139
1140
		return (object) $author;
1141
	}
1142
1143
	function get_media_item( $media_id ) {
1144
		$media_item = get_post( $media_id );
1145
1146
		if ( !$media_item || is_wp_error( $media_item ) )
1147
			return new WP_Error( 'unknown_media', 'Unknown Media', 404 );
1148
1149
		$response = array(
1150
			'id'    => strval( $media_item->ID ),
1151
			'date' =>  (string) $this->format_date( $media_item->post_date_gmt, $media_item->post_date ),
1152
			'parent'           => $media_item->post_parent,
1153
			'link'             => wp_get_attachment_url( $media_item->ID ),
1154
			'title'            => $media_item->post_title,
1155
			'caption'          => $media_item->post_excerpt,
1156
			'description'      => $media_item->post_content,
1157
			'metadata'         => wp_get_attachment_metadata( $media_item->ID ),
1158
		);
1159
1160
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM && is_array( $response['metadata'] ) && ! empty( $response['metadata']['file'] ) ) {
1161
			remove_filter( '_wp_relative_upload_path', 'wpcom_wp_relative_upload_path', 10 );
1162
			$response['metadata']['file'] = _wp_relative_upload_path( $response['metadata']['file'] );
1163
			add_filter( '_wp_relative_upload_path', 'wpcom_wp_relative_upload_path', 10, 2 );
1164
		}
1165
1166
		$response['meta'] = (object) array(
1167
			'links' => (object) array(
1168
				'self' => (string) $this->links->get_media_link( $this->api->get_blog_id_for_output(), $media_id ),
1169
				'help' => (string) $this->links->get_media_link( $this->api->get_blog_id_for_output(), $media_id, 'help' ),
1170
				'site' => (string) $this->links->get_site_link( $this->api->get_blog_id_for_output() ),
1171
			),
1172
		);
1173
1174
		return (object) $response;
1175
	}
1176
1177
	function get_media_item_v1_1( $media_id, $media_item = null, $file = null ) {
1178
1179
		if ( ! $media_item ) {
1180
			$media_item = get_post( $media_id );
1181
		}
1182
1183
		if ( ! $media_item || is_wp_error( $media_item ) )
1184
			return new WP_Error( 'unknown_media', 'Unknown Media', 404 );
1185
1186
		$attachment_file = wp_get_attachment_url( $media_item->ID );
1187
1188
		$file = basename( $attachment_file ? $attachment_file : $file );
1189
		$file_info = pathinfo( $file );
1190
		$ext  = isset( $file_info['extension'] ) ? $file_info['extension'] : null;
1191
1192
		$response = array(
1193
			'ID'           => $media_item->ID,
1194
			'URL'          => wp_get_attachment_url( $media_item->ID ),
1195
			'guid'         => $media_item->guid,
1196
			'date'         => (string) $this->format_date( $media_item->post_date_gmt, $media_item->post_date ),
1197
			'post_ID'      => $media_item->post_parent,
1198
			'author_ID'    => (int) $media_item->post_author,
1199
			'file'         => $file,
1200
			'mime_type'    => $media_item->post_mime_type,
1201
			'extension'    => $ext,
1202
			'title'        => $media_item->post_title,
1203
			'caption'      => $media_item->post_excerpt,
1204
			'description'  => $media_item->post_content,
1205
			'alt'          => get_post_meta( $media_item->ID, '_wp_attachment_image_alt', true ),
1206
			'icon'         => wp_mime_type_icon( $media_item->ID ),
1207
			'thumbnails'   => array()
1208
		);
1209
1210
		if ( in_array( $ext, array( 'jpg', 'jpeg', 'png', 'gif' ) ) ) {
1211
			$metadata = wp_get_attachment_metadata( $media_item->ID );
1212
			if ( isset( $metadata['height'], $metadata['width'] ) ) {
1213
				$response['height'] = $metadata['height'];
1214
				$response['width'] = $metadata['width'];
1215
			}
1216
1217
			if ( isset( $metadata['sizes'] ) ) {
1218
				/**
1219
				 * Filter the thumbnail sizes available for each attachment ID.
1220
				 *
1221
				 * @module json-api
1222
				 *
1223
				 * @since 3.9.0
1224
				 *
1225
				 * @param array $metadata['sizes'] Array of thumbnail sizes available for a given attachment ID.
1226
				 * @param string $media_id Attachment ID.
1227
				 */
1228
				$sizes = apply_filters( 'rest_api_thumbnail_sizes', $metadata['sizes'], $media_id );
1229
				if ( is_array( $sizes ) ) {
1230
					foreach ( $sizes as $size => $size_details ) {
1231
						$response['thumbnails'][ $size ] = dirname( $response['URL'] ) . '/' . $size_details['file'];
1232
					}
1233
				}
1234
			}
1235
1236
			if ( isset( $metadata['image_meta'] ) ) {
1237
				$response['exif'] = $metadata['image_meta'];
1238
			}
1239
		}
1240
1241
		if ( in_array( $ext, array( 'mp3', 'm4a', 'wav', 'ogg' ) ) ) {
1242
			$metadata = wp_get_attachment_metadata( $media_item->ID );
1243
			$response['length'] = $metadata['length'];
1244
			$response['exif']   = $metadata;
1245
		}
1246
1247
		if ( in_array( $ext, array( 'ogv', 'mp4', 'mov', 'wmv', 'avi', 'mpg', '3gp', '3g2', 'm4v' ) ) ) {
1248
			$metadata = wp_get_attachment_metadata( $media_item->ID );
1249
			if ( isset( $metadata['height'], $metadata['width'] ) ) {
1250
				$response['height'] = $metadata['height'];
1251
				$response['width']  = $metadata['width'];
1252
			}
1253
1254
			if ( isset( $metadata['length'] ) ) {
1255
				$response['length'] = $metadata['length'];
1256
			}
1257
1258
			// add VideoPress info
1259
			if ( function_exists( 'video_get_info_by_blogpostid' ) ) {
1260
				$info = video_get_info_by_blogpostid( $this->api->get_blog_id_for_output(), $media_id );
1261
1262
				// Thumbnails
1263
				if ( function_exists( 'video_format_done' ) && function_exists( 'video_image_url_by_guid' ) ) {
1264
					$response['thumbnails'] = array( 'fmt_hd' => '', 'fmt_dvd' => '', 'fmt_std' => '' );
1265
					foreach ( $response['thumbnails'] as $size => $thumbnail_url ) {
1266
						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 1260 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...
1267
							$response['thumbnails'][ $size ] = video_image_url_by_guid( $info->guid, $size );
1268
						} else {
1269
							unset( $response['thumbnails'][ $size ] );
1270
						}
1271
					}
1272
				}
1273
1274
				$response['videopress_guid'] = $info->guid;
1275
				$response['videopress_processing_done'] = true;
1276
				if ( '0000-00-00 00:00:00' == $info->finish_date_gmt ) {
1277
					$response['videopress_processing_done'] = false;
1278
				}
1279
			}
1280
		}
1281
1282
		$response['thumbnails'] = (object) $response['thumbnails'];
1283
1284
		$response['meta'] = (object) array(
1285
			'links' => (object) array(
1286
				'self' => (string) $this->links->get_media_link( $this->api->get_blog_id_for_output(), $media_id ),
1287
				'help' => (string) $this->links->get_media_link( $this->api->get_blog_id_for_output(), $media_id, 'help' ),
1288
				'site' => (string) $this->links->get_site_link( $this->api->get_blog_id_for_output() ),
1289
			),
1290
		);
1291
1292
		// add VideoPress link to the meta
1293
		if ( in_array( $ext, array( 'ogv', 'mp4', 'mov', 'wmv', 'avi', 'mpg', '3gp', '3g2', 'm4v' ) ) ) {
1294
			if ( function_exists( 'video_get_info_by_blogpostid' ) ) {
1295
				$response['meta']->links->videopress = (string) $this->links->get_link( '/videos/%s', $response['videopress_guid'], '' );
1296
			}
1297
		}
1298
1299
		if ( $media_item->post_parent > 0 ) {
1300
			$response['meta']->links->parent = (string) $this->links->get_post_link( $this->api->get_blog_id_for_output(), $media_item->post_parent );
1301
		}
1302
1303
		return (object) $response;
1304
	}
1305
1306
	function get_taxonomy( $taxonomy_id, $taxonomy_type, $context ) {
1307
1308
		$taxonomy = get_term_by( 'slug', $taxonomy_id, $taxonomy_type );
1309
		/// keep updating this function
1310
		if ( !$taxonomy || is_wp_error( $taxonomy ) ) {
1311
			return new WP_Error( 'unknown_taxonomy', 'Unknown taxonomy', 404 );
1312
		}
1313
1314
		return $this->format_taxonomy( $taxonomy, $taxonomy_type, $context );
1315
	}
1316
1317 View Code Duplication
	function format_taxonomy( $taxonomy, $taxonomy_type, $context ) {
1318
		// Permissions
1319
		switch ( $context ) {
1320
		case 'edit' :
1321
			$tax = get_taxonomy( $taxonomy_type );
1322
			if ( !current_user_can( $tax->cap->edit_terms ) )
1323
				return new WP_Error( 'unauthorized', 'User cannot edit taxonomy', 403 );
1324
			break;
1325
		case 'display' :
1326
			if ( -1 == get_option( 'blog_public' ) && ! current_user_can( 'read' ) ) {
1327
				return new WP_Error( 'unauthorized', 'User cannot view taxonomy', 403 );
1328
			}
1329
			break;
1330
		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...
1331
			return new WP_Error( 'invalid_context', 'Invalid API CONTEXT', 400 );
1332
		}
1333
1334
		$response                = array();
1335
		$response['ID']          = (int) $taxonomy->term_id;
1336
		$response['name']        = (string) $taxonomy->name;
1337
		$response['slug']        = (string) $taxonomy->slug;
1338
		$response['description'] = (string) $taxonomy->description;
1339
		$response['post_count']  = (int) $taxonomy->count;
1340
1341
		if ( is_taxonomy_hierarchical( $taxonomy_type ) ) {
1342
			$response['parent'] = (int) $taxonomy->parent;
1343
		}
1344
1345
		$response['meta'] = (object) array(
1346
			'links' => (object) array(
1347
				'self' => (string) $this->links->get_taxonomy_link( $this->api->get_blog_id_for_output(), $taxonomy->slug, $taxonomy_type ),
1348
				'help' => (string) $this->links->get_taxonomy_link( $this->api->get_blog_id_for_output(), $taxonomy->slug, $taxonomy_type, 'help' ),
1349
				'site' => (string) $this->links->get_site_link( $this->api->get_blog_id_for_output() ),
1350
			),
1351
		);
1352
1353
		return (object) $response;
1354
	}
1355
1356
	/**
1357
	 * Returns ISO 8601 formatted datetime: 2011-12-08T01:15:36-08:00
1358
	 *
1359
	 * @param $date_gmt (string) GMT datetime string.
1360
	 * @param $date (string) Optional.  Used to calculate the offset from GMT.
1361
	 *
1362
	 * @return string
1363
	 */
1364
	function format_date( $date_gmt, $date = null ) {
1365
		return WPCOM_JSON_API_Date::format_date( $date_gmt, $date );
1366
	}
1367
1368
	/**
1369
	 * Parses a date string and returns the local and GMT representations
1370
	 * of that date & time in 'YYYY-MM-DD HH:MM:SS' format without
1371
	 * timezones or offsets. If the parsed datetime was not localized to a
1372
	 * particular timezone or offset we will assume it was given in GMT
1373
	 * relative to now and will convert it to local time using either the
1374
	 * timezone set in the options table for the blog or the GMT offset.
1375
	 *
1376
	 * @param datetime string
1377
	 *
1378
	 * @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...
1379
	 */
1380
	function parse_date( $date_string ) {
1381
		$date_string_info = date_parse( $date_string );
1382
		if ( is_array( $date_string_info ) && 0 === $date_string_info['error_count'] ) {
1383
			// Check if it's already localized. Can't just check is_localtime because date_parse('oppossum') returns true; WTF, PHP.
1384
			if ( isset( $date_string_info['zone'] ) && true === $date_string_info['is_localtime'] ) {
1385
				$dt_local = clone $dt_utc = new DateTime( $date_string );
1386
				$dt_utc->setTimezone( new DateTimeZone( 'UTC' ) );
1387
				return array(
1388
					(string) $dt_local->format( 'Y-m-d H:i:s' ),
1389
					(string) $dt_utc->format( 'Y-m-d H:i:s' ),
1390
				);
1391
			}
1392
1393
			// It's parseable but no TZ info so assume UTC
1394
			$dt_local = clone $dt_utc = new DateTime( $date_string, new DateTimeZone( 'UTC' ) );
1395
		} else {
1396
			// Could not parse time, use now in UTC
1397
			$dt_local = clone $dt_utc = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
1398
		}
1399
1400
		// First try to use timezone as it's daylight savings aware.
1401
		$timezone_string = get_option( 'timezone_string' );
1402
		if ( $timezone_string ) {
1403
			$tz = timezone_open( $timezone_string );
1404
			if ( $tz ) {
1405
				$dt_local->setTimezone( $tz );
1406
				return array(
1407
					(string) $dt_local->format( 'Y-m-d H:i:s' ),
1408
					(string) $dt_utc->format( 'Y-m-d H:i:s' ),
1409
				);
1410
			}
1411
		}
1412
1413
		// Fallback to GMT offset (in hours)
1414
		// NOTE: TZ of $dt_local is still UTC, we simply modified the timestamp with an offset.
1415
		$gmt_offset_seconds = intval( get_option( 'gmt_offset' ) * 3600 );
1416
		$dt_local->modify("+{$gmt_offset_seconds} seconds");
1417
		return array(
1418
			(string) $dt_local->format( 'Y-m-d H:i:s' ),
1419
			(string) $dt_utc->format( 'Y-m-d H:i:s' ),
1420
		);
1421
	}
1422
1423
	// Load the functions.php file for the current theme to get its post formats, CPTs, etc.
1424
	function load_theme_functions() {
1425
		// bail if we've done this already (can happen when calling /batch endpoint)
1426
		if ( defined( 'REST_API_THEME_FUNCTIONS_LOADED' ) )
1427
			return;
1428
1429
		// VIP context loading is handled elsewhere, so bail to prevent
1430
		// duplicate loading. See `switch_to_blog_and_validate_user()`
1431
		if ( function_exists( 'wpcom_is_vip' ) && wpcom_is_vip() ) {
1432
			return;
1433
		}
1434
1435
		define( 'REST_API_THEME_FUNCTIONS_LOADED', true );
1436
1437
		// the theme info we care about is found either within functions.php or one of the jetpack files.
1438
		$function_files = array( '/functions.php', '/inc/jetpack.compat.php', '/inc/jetpack.php', '/includes/jetpack.compat.php' );
1439
1440
		$copy_dirs = array( get_template_directory() );
1441
1442
		// Is this a child theme? Load the child theme's functions file.
1443
		if ( get_stylesheet_directory() !== get_template_directory() && wpcom_is_child_theme() ) {
1444
			foreach ( $function_files as $function_file ) {
1445
				if ( file_exists( get_stylesheet_directory() . $function_file ) ) {
1446
					require_once(  get_stylesheet_directory() . $function_file );
1447
				}
1448
			}
1449
			$copy_dirs[] = get_stylesheet_directory();
1450
		}
1451
1452
		foreach ( $function_files as $function_file ) {
1453
			if ( file_exists( get_template_directory() . $function_file ) ) {
1454
				require_once(  get_template_directory() . $function_file );
1455
			}
1456
		}
1457
1458
		// add inc/wpcom.php and/or includes/wpcom.php
1459
		wpcom_load_theme_compat_file();
1460
1461
		// Enable including additional directories or files in actions to be copied
1462
		$copy_dirs = apply_filters( 'restapi_theme_action_copy_dirs', $copy_dirs );
1463
1464
		// since the stuff we care about (CPTS, post formats, are usually on setup or init hooks, we want to load those)
1465
		$this->copy_hooks( 'after_setup_theme', 'restapi_theme_after_setup_theme', $copy_dirs );
1466
1467
		/**
1468
		 * Fires functions hooked onto `after_setup_theme` by the theme for the purpose of the REST API.
1469
		 *
1470
		 * The REST API does not load the theme when processing requests.
1471
		 * To enable theme-based functionality, the API will load the '/functions.php',
1472
		 * '/inc/jetpack.compat.php', '/inc/jetpack.php', '/includes/jetpack.compat.php files
1473
		 * of the theme (parent and child) and copy functions hooked onto 'after_setup_theme' within those files.
1474
		 *
1475
		 * @module json-api
1476
		 *
1477
		 * @since 3.2.0
1478
		 */
1479
		do_action( 'restapi_theme_after_setup_theme' );
1480
		$this->copy_hooks( 'init', 'restapi_theme_init', $copy_dirs );
1481
1482
		/**
1483
		 * Fires functions hooked onto `init` by the theme for the purpose of the REST API.
1484
		 *
1485
		 * The REST API does not load the theme when processing requests.
1486
		 * To enable theme-based functionality, the API will load the '/functions.php',
1487
		 * '/inc/jetpack.compat.php', '/inc/jetpack.php', '/includes/jetpack.compat.php files
1488
		 * of the theme (parent and child) and copy functions hooked onto 'init' within those files.
1489
		 *
1490
		 * @module json-api
1491
		 *
1492
		 * @since 3.2.0
1493
		 */
1494
		do_action( 'restapi_theme_init' );
1495
	}
1496
1497
	function copy_hooks( $from_hook, $to_hook, $base_paths ) {
1498
		global $wp_filter;
1499
		foreach ( $wp_filter as $hook => $actions ) {
1500
1501
			if ( $from_hook != $hook ) {
1502
				continue;
1503
			}
1504
			if ( ! has_action( $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' ) || $this->api->is_authorized_with_upload_token();
0 ignored issues
show
Bug introduced by
The method is_authorized_with_upload_token() does not seem to exist on object<WPCOM_JSON_API>.

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

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

Loading history...
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
		if ( file_exists( $tmp ) ) {
1783
			@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...
1784
		}
1785
1786
		if ( is_wp_error( $id ) ) {
1787
			return $id;
1788
		}
1789
1790
		if ( ! $id || ! is_int( $id ) ) {
1791
			return false;
1792
		}
1793
1794
		return $id;
1795
	}
1796
1797
	/**
1798
	 * Checks that the mime type of the specified file is among those in a filterable list of mime types.
1799
	 *
1800
	 * @param string $file Path to file to get its mime type.
1801
	 *
1802
	 * @return bool
1803
	 */
1804 View Code Duplication
	protected function is_file_supported_for_sideloading( $file ) {
1805
		if ( class_exists( 'finfo' ) ) { // php 5.3+
1806
			$finfo = new finfo( FILEINFO_MIME );
1807
			$mime = explode( '; ', $finfo->file( $file ) );
1808
			$type = $mime[0];
1809
1810
		} elseif ( function_exists( 'mime_content_type' ) ) { // PHP 5.2
1811
			$type = mime_content_type( $file );
1812
1813
		} else {
1814
			return false;
1815
		}
1816
1817
		/**
1818
		 * Filter the list of supported mime types for media sideloading.
1819
		 *
1820
		 * @since 4.0.0
1821
		 *
1822
		 * @module json-api
1823
		 *
1824
		 * @param array $supported_mime_types Array of the supported mime types for media sideloading.
1825
		 */
1826
		$supported_mime_types = apply_filters( 'jetpack_supported_media_sideload_types', array(
1827
			'image/png',
1828
			'image/jpeg',
1829
			'image/gif',
1830
			'image/bmp',
1831
			'video/quicktime',
1832
			'video/mp4',
1833
			'video/mpeg',
1834
			'video/ogg',
1835
			'video/3gpp',
1836
			'video/3gpp2',
1837
			'video/h261',
1838
			'video/h262',
1839
			'video/h264',
1840
			'video/x-msvideo',
1841
			'video/x-ms-wmv',
1842
			'video/x-ms-asf',
1843
		) );
1844
1845
		// If the type returned was not an array as expected, then we know we don't have a match.
1846
		if ( ! is_array( $supported_mime_types ) ) {
1847
			return false;
1848
		}
1849
1850
		return in_array( $type, $supported_mime_types );
1851
	}
1852
1853
	function allow_video_uploads( $mimes ) {
1854
		// if we are on Jetpack, bail - Videos are already allowed
1855
		if ( ! defined( 'IS_WPCOM' ) || !IS_WPCOM ) {
1856
			return $mimes;
1857
		}
1858
1859
		// extra check that this filter is only ever applied during REST API requests
1860
		if ( ! defined( 'REST_API_REQUEST' ) || ! REST_API_REQUEST ) {
1861
			return $mimes;
1862
		}
1863
1864
		// bail early if they already have the upgrade..
1865
		if ( get_option( 'video_upgrade' ) == '1' ) {
1866
			return $mimes;
1867
		}
1868
1869
		// lets whitelist to only specific clients right now
1870
		$clients_allowed_video_uploads = array();
1871
		/**
1872
		 * Filter the list of whitelisted video clients.
1873
		 *
1874
		 * @module json-api
1875
		 *
1876
		 * @since 3.2.0
1877
		 *
1878
		 * @param array $clients_allowed_video_uploads Array of whitelisted Video clients.
1879
		 */
1880
		$clients_allowed_video_uploads = apply_filters( 'rest_api_clients_allowed_video_uploads', $clients_allowed_video_uploads );
1881
		if ( !in_array( $this->api->token_details['client_id'], $clients_allowed_video_uploads ) ) {
1882
			return $mimes;
1883
		}
1884
1885
		$mime_list = wp_get_mime_types();
1886
1887
		$video_exts = explode( ' ', get_site_option( 'video_upload_filetypes', false, false ) );
1888
		/**
1889
		 * Filter the video filetypes allowed on the site.
1890
		 *
1891
		 * @module json-api
1892
		 *
1893
		 * @since 3.2.0
1894
		 *
1895
		 * @param array $video_exts Array of video filetypes allowed on the site.
1896
		 */
1897
		$video_exts = apply_filters( 'video_upload_filetypes', $video_exts );
1898
		$video_mimes = array();
1899
1900
		if ( !empty( $video_exts ) ) {
1901
			foreach ( $video_exts as $ext ) {
1902
				foreach ( $mime_list as $ext_pattern => $mime ) {
1903
					if ( $ext != '' && strpos( $ext_pattern, $ext ) !== false )
1904
						$video_mimes[$ext_pattern] = $mime;
1905
				}
1906
			}
1907
1908
			$mimes = array_merge( $mimes, $video_mimes );
1909
		}
1910
1911
		return $mimes;
1912
	}
1913
1914
	function is_current_site_multi_user() {
1915
		$users = wp_cache_get( 'site_user_count', 'WPCOM_JSON_API_Endpoint' );
1916
		if ( false === $users ) {
1917
			$user_query = new WP_User_Query( array(
1918
				'blog_id' => get_current_blog_id(),
1919
				'fields'  => 'ID',
1920
			) );
1921
			$users = (int) $user_query->get_total();
1922
			wp_cache_set( 'site_user_count', $users, 'WPCOM_JSON_API_Endpoint', DAY_IN_SECONDS );
1923
		}
1924
		return $users > 1;
1925
	}
1926
1927
	function allows_cross_origin_requests() {
1928
		return 'GET' == $this->method || $this->allow_cross_origin_request;
1929
	}
1930
1931
	function allows_unauthorized_requests( $origin, $complete_access_origins  ) {
1932
		return 'GET' == $this->method || ( $this->allow_unauthorized_request && in_array( $origin, $complete_access_origins ) );
1933
	}
1934
1935
	function get_platform() {
1936
		return wpcom_get_sal_platform( $this->api->token_details );
1937
	}
1938
1939
	/**
1940
	 * Allows the endpoint to perform logic to allow it to decide whether-or-not it should force a
1941
	 * response from the WPCOM API, or potentially go to the Jetpack blog.
1942
	 *
1943
	 * Override this method if you want to do something different.
1944
	 *
1945
	 * @param  int  $blog_id
1946
	 * @return bool
1947
	 */
1948
	function force_wpcom_request( $blog_id ) {
1949
		return false;
1950
	}
1951
1952
	/**
1953
	 * Return endpoint response
1954
	 *
1955
	 * @param ... determined by ->$path
1956
	 *
1957
	 * @return
1958
	 * 	falsy: HTTP 500, no response body
1959
	 *	WP_Error( $error_code, $error_message, $http_status_code ): HTTP $status_code, json_encode( array( 'error' => $error_code, 'message' => $error_message ) ) response body
1960
	 *	$data: HTTP 200, json_encode( $data ) response body
1961
	 */
1962
	abstract function callback( $path = '' );
1963
1964
1965
}
1966
1967
require_once( dirname( __FILE__ ) . '/json-endpoints.php' );
1968