Completed
Push — add/changelog-50 ( 40a63e...720d2c )
by Jeremy
108:21 queued 97:51
created

WPCOM_JSON_API::output()   D

Complexity

Conditions 17
Paths 146

Size

Total Lines 80
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 47
nc 146
nop 4
dl 0
loc 80
rs 4.7009
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
defined( 'WPCOM_JSON_API__DEBUG' ) or define( 'WPCOM_JSON_API__DEBUG', false );
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
4
5
require_once dirname( __FILE__ ) . '/sal/class.json-api-platform.php';
6
7
class WPCOM_JSON_API {
8
	static $self = null;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $self.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
9
10
	public $endpoints = array();
11
12
	public $token_details = array();
13
14
	public $method = '';
15
	public $url = '';
16
	public $path = '';
17
	public $version = null;
18
	public $query = array();
19
	public $post_body = null;
20
	public $files = null;
21
	public $content_type = null;
22
	public $accept = '';
23
24
	public $_server_https;
25
	public $exit = true;
26
	public $public_api_scheme = 'https';
27
28
	public $output_status_code = 200;
29
30
	public $trapped_error = null;
31
	public $did_output = false;
32
33
	public $extra_headers = array();
34
35
	/**
36
	 * @return WPCOM_JSON_API instance
37
	 */
38
	static function init( $method = null, $url = null, $post_body = null ) {
39
		if ( !self::$self ) {
40
			$class = function_exists( 'get_called_class' ) ? get_called_class() : __CLASS__;
41
			self::$self = new $class( $method, $url, $post_body );
42
		}
43
		return self::$self;
44
	}
45
46
	function add( WPCOM_JSON_API_Endpoint $endpoint ) {
47
		$path_versions = serialize( array (
48
			$endpoint->path,
49
			$endpoint->min_version,
50
			$endpoint->max_version,
51
		) );
52
		if ( !isset( $this->endpoints[$path_versions] ) ) {
53
			$this->endpoints[$path_versions] = array();
54
		}
55
		$this->endpoints[$path_versions][$endpoint->method] = $endpoint;
56
	}
57
58
	static function is_truthy( $value ) {
59
		switch ( strtolower( (string) $value ) ) {
60
		case '1' :
61
		case 't' :
62
		case 'true' :
63
			return true;
64
		}
65
66
		return false;
67
	}
68
69
	static function is_falsy( $value ) {
70
		switch ( strtolower( (string) $value ) ) {
71
			case '0' :
72
			case 'f' :
73
			case 'false' :
74
				return true;
75
		}
76
77
		return false;
78
	}
79
80
	function __construct() {
81
		$args = func_get_args();
82
		call_user_func_array( array( $this, 'setup_inputs' ), $args );
83
	}
84
85
	function setup_inputs( $method = null, $url = null, $post_body = null ) {
86
		if ( is_null( $method ) ) {
87
			$this->method = strtoupper( $_SERVER['REQUEST_METHOD'] );
88
		} else {
89
			$this->method = strtoupper( $method );
90
		}
91
		if ( is_null( $url ) ) {
92
			$this->url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
93
		} else {
94
			$this->url = $url;
95
		}
96
97
		$parsed     = parse_url( $this->url );
98
		$this->path = $parsed['path'];
99
100
		if ( !empty( $parsed['query'] ) ) {
101
			wp_parse_str( $parsed['query'], $this->query );
102
		}
103
104
		if ( isset( $_SERVER['HTTP_ACCEPT'] ) && $_SERVER['HTTP_ACCEPT'] ) {
105
			$this->accept = $_SERVER['HTTP_ACCEPT'];
106
		}
107
108
		if ( 'POST' === $this->method ) {
109
			if ( is_null( $post_body ) ) {
110
				$this->post_body = file_get_contents( 'php://input' );
111
112
				if ( isset( $_SERVER['HTTP_CONTENT_TYPE'] ) && $_SERVER['HTTP_CONTENT_TYPE'] ) {
113
					$this->content_type = $_SERVER['HTTP_CONTENT_TYPE'];
114
				} elseif ( isset( $_SERVER['CONTENT_TYPE'] ) && $_SERVER['CONTENT_TYPE'] ) {
115
					$this->content_type = $_SERVER['CONTENT_TYPE'] ;
116
				} elseif ( '{' === $this->post_body[0] ) {
117
					$this->content_type = 'application/json';
118
				} else {
119
					$this->content_type = 'application/x-www-form-urlencoded';
120
				}
121
122
				if ( 0 === strpos( strtolower( $this->content_type ), 'multipart/' ) ) {
123
					$this->post_body = http_build_query( stripslashes_deep( $_POST ) );
124
					$this->files = $_FILES;
125
					$this->content_type = 'multipart/form-data';
126
				}
127
			} else {
128
				$this->post_body = $post_body;
129
				$this->content_type = '{' === isset( $this->post_body[0] ) && $this->post_body[0] ? 'application/json' : 'application/x-www-form-urlencoded';
130
			}
131
		} else {
132
			$this->post_body = null;
133
			$this->content_type = null;
134
		}
135
136
		$this->_server_https = array_key_exists( 'HTTPS', $_SERVER ) ? $_SERVER['HTTPS'] : '--UNset--';
137
	}
138
139
	function initialize() {
140
		$this->token_details['blog_id'] = Jetpack_Options::get_option( 'id' );
141
	}
142
143
	function serve( $exit = true ) {
144
		ini_set( 'display_errors', false );
145
146
		$this->exit = (bool) $exit;
147
148
		// This was causing problems with Jetpack, but is necessary for wpcom
149
		// @see https://github.com/Automattic/jetpack/pull/2603
150
		// @see r124548-wpcom
151
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
152
			add_filter( 'home_url', array( $this, 'ensure_http_scheme_of_home_url' ), 10, 3 );
153
		}
154
155
		add_filter( 'user_can_richedit', '__return_true' );
156
157
		add_filter( 'comment_edit_pre', array( $this, 'comment_edit_pre' ) );
158
159
		$initialization = $this->initialize();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $initialization is correct as $this->initialize() (which targets WPCOM_JSON_API::initialize()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
160
		if ( 'OPTIONS' == $this->method ) {
161
			/**
162
			 * Fires before the page output.
163
			 * Can be used to specify custom header options.
164
			 *
165
			 * @module json-api
166
			 *
167
			 * @since 3.1.0
168
			 */
169
			do_action( 'wpcom_json_api_options' );
170
			return $this->output( 200, '', 'text/plain' );
171
		}
172
173
		if ( is_wp_error( $initialization ) ) {
174
			$this->output_error( $initialization );
175
			return;
176
		}
177
178
		// Normalize path and extract API version
179
		$this->path = untrailingslashit( $this->path );
180
		preg_match( '#^/rest/v(\d+(\.\d+)*)#', $this->path, $matches );
181
		$this->path = substr( $this->path, strlen( $matches[0] ) );
182
		$this->version = $matches[1];
183
184
		$allowed_methods = array( 'GET', 'POST' );
185
		$four_oh_five = false;
186
187
		$is_help = preg_match( '#/help/?$#i', $this->path );
188
		$matching_endpoints = array();
189
190
		if ( $is_help ) {
191
			$origin = get_http_origin();
192
193
			if ( !empty( $origin ) && 'GET' == $this->method ) {
194
				header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
195
			}
196
197
			$this->path = substr( rtrim( $this->path, '/' ), 0, -5 );
198
			// Show help for all matching endpoints regardless of method
199
			$methods = $allowed_methods;
200
			$find_all_matching_endpoints = true;
201
			// How deep to truncate each endpoint's path to see if it matches this help request
202
			$depth = substr_count( $this->path, '/' ) + 1;
203
			if ( false !== stripos( $this->accept, 'javascript' ) || false !== stripos( $this->accept, 'json' ) ) {
204
				$help_content_type = 'json';
205
			} else {
206
				$help_content_type = 'html';
207
			}
208
		} else {
209
			if ( in_array( $this->method, $allowed_methods ) ) {
210
				// Only serve requested method
211
				$methods = array( $this->method );
212
				$find_all_matching_endpoints = false;
213
			} else {
214
				// We don't allow this requested method - find matching endpoints and send 405
215
				$methods = $allowed_methods;
216
				$find_all_matching_endpoints = true;
217
				$four_oh_five = true;
218
			}
219
		}
220
221
		// Find which endpoint to serve
222
		$found = false;
223
		foreach ( $this->endpoints as $endpoint_path_versions => $endpoints_by_method ) {
224
			$endpoint_path_versions = unserialize( $endpoint_path_versions );
225
			$endpoint_path        = $endpoint_path_versions[0];
226
			$endpoint_min_version = $endpoint_path_versions[1];
227
			$endpoint_max_version = $endpoint_path_versions[2];
228
229
			// Make sure max_version is not less than min_version
230
			if ( version_compare( $endpoint_max_version, $endpoint_min_version, '<' ) ) {
231
				$endpoint_max_version = $endpoint_min_version;
232
			}
233
234
			foreach ( $methods as $method ) {
235
				if ( !isset( $endpoints_by_method[$method] ) ) {
236
					continue;
237
				}
238
239
				// Normalize
240
				$endpoint_path = untrailingslashit( $endpoint_path );
241
				if ( $is_help ) {
242
					// Truncate path at help depth
243
					$endpoint_path = join( '/', array_slice( explode( '/', $endpoint_path ), 0, $depth ) );
0 ignored issues
show
Bug introduced by
The variable $depth 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...
244
				}
245
246
				// Generate regular expression from sprintf()
247
				$endpoint_path_regex = str_replace( array( '%s', '%d' ), array( '([^/?&]+)', '(\d+)' ), $endpoint_path );
248
249
				if ( !preg_match( "#^$endpoint_path_regex\$#", $this->path, $path_pieces ) ) {
250
					// This endpoint does not match the requested path.
251
					continue;
252
				}
253
254
				if ( version_compare( $this->version, $endpoint_min_version, '<' ) || version_compare( $this->version, $endpoint_max_version, '>' ) ) {
255
					// This endpoint does not match the requested version.
256
					continue;
257
				}
258
259
				$found = true;
260
261
				if ( $find_all_matching_endpoints ) {
262
					$matching_endpoints[] = array( $endpoints_by_method[$method], $path_pieces );
263
				} else {
264
					// The method parameters are now in $path_pieces
265
					$endpoint = $endpoints_by_method[$method];
266
					break 2;
267
				}
268
			}
269
		}
270
271
		if ( !$found ) {
272
			return $this->output( 404, '', 'text/plain' );
273
		}
274
275
		if ( $four_oh_five ) {
276
			$allowed_methods = array();
277
			foreach ( $matching_endpoints as $matching_endpoint ) {
278
				$allowed_methods[] = $matching_endpoint[0]->method;
279
			}
280
281
			header( 'Allow: ' . strtoupper( join( ',', array_unique( $allowed_methods ) ) ) );
282
			return $this->output( 405, array( 'error' => 'not_allowed', 'error_message' => 'Method not allowed' ) );
283
		}
284
285
		if ( $is_help ) {
286
			/**
287
			 * Fires before the API output.
288
			 *
289
			 * @since 1.9.0
290
			 *
291
			 * @param string help.
292
			 */
293
			do_action( 'wpcom_json_api_output', 'help' );
294
			$proxied = function_exists( 'wpcom_is_proxied_request' ) ? wpcom_is_proxied_request() : false;
295
			if ( 'json' === $help_content_type ) {
0 ignored issues
show
Bug introduced by
The variable $help_content_type 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...
296
				$docs = array();
297
				foreach ( $matching_endpoints as $matching_endpoint ) {
298
					if ( $matching_endpoint[0]->is_publicly_documentable() || $proxied || WPCOM_JSON_API__DEBUG )
299
						$docs[] = call_user_func( array( $matching_endpoint[0], 'generate_documentation' ) );
300
				}
301
				return $this->output( 200, $docs );
302
			} else {
303
				status_header( 200 );
304
				foreach ( $matching_endpoints as $matching_endpoint ) {
305
					if ( $matching_endpoint[0]->is_publicly_documentable() || $proxied || WPCOM_JSON_API__DEBUG )
306
						call_user_func( array( $matching_endpoint[0], 'document' ) );
307
				}
308
			}
309
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method serve() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
310
		}
311
312
		if ( $endpoint->in_testing && !WPCOM_JSON_API__DEBUG ) {
0 ignored issues
show
Bug introduced by
The variable $endpoint 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...
313
			return $this->output( 404, '', 'text/plain' );
314
		}
315
316
		/** This action is documented in class.json-api.php */
317
		do_action( 'wpcom_json_api_output', $endpoint->stat );
318
319
		$response = $this->process_request( $endpoint, $path_pieces );
0 ignored issues
show
Bug introduced by
The variable $path_pieces 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...
320
321
		if ( !$response && !is_array( $response ) ) {
322
			return $this->output( 500, '', 'text/plain' );
323
		} elseif ( is_wp_error( $response ) ) {
324
			return $this->output_error( $response );
325
		}
326
327
		$output_status_code = $this->output_status_code;
328
		$this->set_output_status_code();
329
330
		return $this->output( $output_status_code, $response, 'application/json', $this->extra_headers );
331
	}
332
333
	function process_request( WPCOM_JSON_API_Endpoint $endpoint, $path_pieces ) {
334
		$this->endpoint = $endpoint;
0 ignored issues
show
Bug introduced by
The property endpoint does not seem to exist. Did you mean endpoints?

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...
335
		return call_user_func_array( array( $endpoint, 'callback' ), $path_pieces );
336
	}
337
338
	function output_early( $status_code, $response = null, $content_type = 'application/json' ) {
339
		$exit = $this->exit;
340
		$this->exit = false;
341
		if ( is_wp_error( $response ) )
342
			$this->output_error( $response );
343
		else
344
			$this->output( $status_code, $response, $content_type );
345
		$this->exit = $exit;
346
		if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
347
			$this->finish_request();
348
		}
349
	}
350
351
	function set_output_status_code( $code = 200 ) {
352
		$this->output_status_code = $code;
353
	}
354
355
	function output( $status_code, $response = null, $content_type = 'application/json', $extra = array() ) {
356
		// In case output() was called before the callback returned
357
		if ( $this->did_output ) {
358
			if ( $this->exit )
359
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method output() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
360
			return $content_type;
361
		}
362
		$this->did_output = true;
363
364
		// 400s and 404s are allowed for all origins
365
		if ( 404 == $status_code || 400 == $status_code )
366
			header( 'Access-Control-Allow-Origin: *' );
367
368
		if ( is_null( $response ) ) {
369
			$response = new stdClass;
370
		}
371
372
		if ( 'text/plain' === $content_type ) {
373
			status_header( (int) $status_code );
374
			header( 'Content-Type: text/plain' );
375
			foreach( $extra as $key => $value ) {
376
				header( "$key: $value" );
377
			}
378
			echo $response;
379
			if ( $this->exit ) {
380
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method output() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
381
			}
382
383
			return $content_type;
384
		}
385
386
		$response = $this->filter_fields( $response );
387
388
		if ( isset( $this->query['http_envelope'] ) && self::is_truthy( $this->query['http_envelope'] ) ) {
389
			$headers = array(
390
				array(
391
					'name' => 'Content-Type',
392
					'value' => $content_type,
393
				)
394
			);
395
			
396
			foreach( $extra as $key => $value ) {
397
				$headers[] = array( 'name' => $key, 'value' => $value );
398
			}
399
400
			$response = array(
401
				'code' => (int) $status_code,
402
				'headers' => $headers,
403
				'body' => $response,
404
			);
405
			$status_code = 200;
406
			$content_type = 'application/json';
407
		}
408
409
		status_header( (int) $status_code );
410
		header( "Content-Type: $content_type" );
411
		if ( isset( $this->query['callback'] ) && is_string( $this->query['callback'] ) ) {
412
			$callback = preg_replace( '/[^a-z0-9_.]/i', '', $this->query['callback'] );
413
		} else {
414
			$callback = false;
415
		}
416
417
		if ( $callback ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $callback of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
418
			// Mitigate Rosetta Flash [1] by setting the Content-Type-Options: nosniff header
419
			// and by prepending the JSONP response with a JS comment.
420
			// [1] http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
421
			echo "/**/$callback(";
422
423
		}
424
		echo $this->json_encode( $response );
425
		if ( $callback ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $callback of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
426
			echo ");";
427
		}
428
429
		if ( $this->exit ) {
430
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method output() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
431
		}
432
433
		return $content_type;
434
	}
435
436
	public static function serializable_error ( $error ) {
437
438
		$status_code = $error->get_error_data();
439
440
		if ( is_array( $status_code ) )
441
			$status_code = $status_code['status_code'];
442
443
		if ( !$status_code ) {
444
			$status_code = 400;
445
		}
446
		$response = array(
447
			'error'   => $error->get_error_code(),
448
			'message' => $error->get_error_message(),
449
		);
450
		return array(
451
			'status_code' => $status_code,
452
			'errors' => $response
453
		);
454
	}
455
456
	function output_error( $error ) {
457
		$error_response = $this->serializable_error( $error );
458
459
		return $this->output( $error_response[ 'status_code'], $error_response['errors'] );
460
	}
461
462
	function filter_fields( $response ) {
463
		if ( empty( $this->query['fields'] ) || ( is_array( $response ) && ! empty( $response['error'] ) ) || ! empty( $this->endpoint->custom_fields_filtering ) )
0 ignored issues
show
Bug introduced by
The property endpoint does not seem to exist. Did you mean endpoints?

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...
464
			return $response;
465
466
		$fields = array_map( 'trim', explode( ',', $this->query['fields'] ) );
467
468
		if ( is_object( $response ) ) {
469
			$response = (array) $response;
470
		}
471
472
		$has_filtered = false;
473
		if ( is_array( $response ) && empty( $response['ID'] ) ) {
474
			$keys_to_filter = array(
475
				'categories',
476
				'comments',
477
				'connections',
478
				'domains',
479
				'groups',
480
				'likes',
481
				'media',
482
				'notes',
483
				'posts',
484
				'services',
485
				'sites',
486
				'suggestions',
487
				'tags',
488
				'themes',
489
				'topics',
490
				'users',
491
			);
492
493
			foreach ( $keys_to_filter as $key_to_filter ) {
494
				if ( ! isset( $response[ $key_to_filter ] ) || $has_filtered )
495
					continue;
496
497
				foreach ( $response[ $key_to_filter ] as $key => $values ) {
498
					if ( is_object( $values ) ) {
499
						$response[ $key_to_filter ][ $key ] = (object) array_intersect_key( (array) $values, array_flip( $fields ) );
500
					} elseif ( is_array( $values ) ) {
501
						$response[ $key_to_filter ][ $key ] = array_intersect_key( $values, array_flip( $fields ) );
502
					}
503
				}
504
505
				$has_filtered = true;
506
			}
507
		}
508
509
		if ( ! $has_filtered ) {
510
			if ( is_object( $response ) ) {
511
				$response = (object) array_intersect_key( (array) $response, array_flip( $fields ) );
512
			} else if ( is_array( $response ) ) {
513
				$response = array_intersect_key( $response, array_flip( $fields ) );
514
			}
515
		}
516
517
		return $response;
518
	}
519
520
	function ensure_http_scheme_of_home_url( $url, $path, $original_scheme ) {
521
		if ( $original_scheme ) {
522
			return $url;
523
		}
524
525
		return preg_replace( '#^https:#', 'http:', $url );
526
	}
527
528
	function comment_edit_pre( $comment_content ) {
529
		return htmlspecialchars_decode( $comment_content, ENT_QUOTES );
530
	}
531
532
	function json_encode( $data ) {
533
		return json_encode( $data );
534
	}
535
536
	function ends_with( $haystack, $needle ) {
537
		return $needle === substr( $haystack, -strlen( $needle ) );
538
	}
539
540
	// Returns the site's blog_id in the WP.com ecosystem
541
	function get_blog_id_for_output() {
542
		return $this->token_details['blog_id'];
543
	}
544
545
	// Returns the site's local blog_id
546
	function get_blog_id( $blog_id ) {
547
		return $GLOBALS['blog_id'];
548
	}
549
550
	function switch_to_blog_and_validate_user( $blog_id = 0, $verify_token_for_blog = true ) {
551
		if ( $this->is_restricted_blog( $blog_id ) ) {
552
			return new WP_Error( 'unauthorized', 'User cannot access this restricted blog', 403 );
553
		}
554
555
		if ( -1 == get_option( 'blog_public' ) && !current_user_can( 'read' ) ) {
556
			return new WP_Error( 'unauthorized', 'User cannot access this private blog.', 403 );
557
		}
558
559
		return $blog_id;
560
	}
561
562
	// Returns true if the specified blog ID is a restricted blog
563
	function is_restricted_blog( $blog_id ) {
564
		/**
565
		 * Filters all REST API access and return a 403 unauthorized response for all Restricted blog IDs.
566
		 *
567
		 * @module json-api
568
		 *
569
		 * @since 3.4.0
570
		 *
571
		 * @param array $array Array of Blog IDs.
572
		 */
573
		$restricted_blog_ids = apply_filters( 'wpcom_json_api_restricted_blog_ids', array() );
574
		return true === in_array( $blog_id, $restricted_blog_ids );
575
	}
576
577
	function post_like_count( $blog_id, $post_id ) {
578
		return 0;
579
	}
580
581
	function is_liked( $blog_id, $post_id ) {
582
		return false;
583
	}
584
585
	function is_reblogged( $blog_id, $post_id ) {
586
		return false;
587
	}
588
589
	function is_following( $blog_id ) {
590
		return false;
591
	}
592
593
	function add_global_ID( $blog_id, $post_id ) {
594
		return '';
595
	}
596
597
	function get_avatar_url( $email, $avatar_size = null ) {
598
		if ( function_exists( 'wpcom_get_avatar_url' ) ) {
599
			return null === $avatar_size
600
				? wpcom_get_avatar_url( $email )
601
				: wpcom_get_avatar_url( $email, $avatar_size );
602
		} else {
603
			return null === $avatar_size
604
				? get_avatar_url( $email )
605
				: get_avatar_url( $email, $avatar_size );
606
		}
607
	}
608
609
	/**
610
	 * traps `wp_die()` calls and outputs a JSON response instead.
611
	 * The result is always output, never returned.
612
	 *
613
	 * @param string|null $error_code  Call with string to start the trapping.  Call with null to stop.
614
	 * @param int         $http_status  HTTP status code, 400 by default.
615
	 */
616
	function trap_wp_die( $error_code = null, $http_status = 400 ) {
617
		if ( is_null( $error_code ) ) {
618
			$this->trapped_error = null;
619
			// Stop trapping
620
			remove_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) );
621
			return;
622
		}
623
624
		// If API called via PHP, bail: don't do our custom wp_die().  Do the normal wp_die().
625
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
626
			if ( ! defined( 'REST_API_REQUEST' ) || ! REST_API_REQUEST ) {
627
				return;
628
			}
629
		} else {
630
			if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
631
				return;
632
			}
633
		}
634
635
		$this->trapped_error = array(
636
			'status'  => $http_status,
637
			'code'    => $error_code,
638
			'message' => '',
639
		);
640
		// Start trapping
641
		add_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) );
642
	}
643
644
	function wp_die_handler_callback() {
645
		return array( $this, 'wp_die_handler' );
646
	}
647
648
	function wp_die_handler( $message, $title = '', $args = array() ) {
649
		// Allow wp_die calls to override HTTP status code...
650
		$args = wp_parse_args( $args, array(
651
			'response' => $this->trapped_error['status'],
652
		) );
653
654
		// ... unless it's 500 ( see http://wp.me/pMz3w-5VV )
655
		if ( (int) $args['response'] !== 500 ) {
656
			$this->trapped_error['status'] = $args['response'];
657
		}
658
659
		if ( $title ) {
660
			$message = "$title: $message";
661
		}
662
663
		$this->trapped_error['message'] = wp_kses( $message, array() );
664
665
		switch ( $this->trapped_error['code'] ) {
666
			case 'comment_failure' :
667
				if ( did_action( 'comment_duplicate_trigger' ) ) {
668
					$this->trapped_error['code'] = 'comment_duplicate';
669
				} else if ( did_action( 'comment_flood_trigger' ) ) {
670
					$this->trapped_error['code'] = 'comment_flood';
671
				}
672
				break;
673
		}
674
675
		// We still want to exit so that code execution stops where it should.
676
		// Attach the JSON output to the WordPress shutdown handler
677
		add_action( 'shutdown', array( $this, 'output_trapped_error' ), 0 );
678
		exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method wp_die_handler() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
679
	}
680
681
	function output_trapped_error() {
682
		$this->exit = false; // We're already exiting once.  Don't do it twice.
683
		$this->output( $this->trapped_error['status'], (object) array(
684
			'error'   => $this->trapped_error['code'],
685
			'message' => $this->trapped_error['message'],
686
		) );
687
	}
688
689
	function finish_request() {
690
		if ( function_exists( 'fastcgi_finish_request' ) )
691
			return fastcgi_finish_request();
692
	}
693
}
694