Completed
Pull Request — add/telegram-sharebuttons (#3775)
by
unknown
30:50 queued 21:07
created

WPCOM_JSON_API::serve()   F

Complexity

Conditions 38
Paths 9832

Size

Total Lines 200
Code Lines 107

Duplication

Lines 8
Ratio 4 %
Metric Value
dl 8
loc 200
rs 2
nc 9832
cc 38
eloc 107
nop 1

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
class WPCOM_JSON_API {
6
	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...
7
8
	public $endpoints = array();
9
10
	public $token_details = array();
11
12
	public $method = '';
13
	public $url = '';
14
	public $path = '';
15
	public $version = null;
16
	public $query = array();
17
	public $post_body = null;
18
	public $files = null;
19
	public $content_type = null;
20
	public $accept = '';
21
22
	public $_server_https;
23
	public $exit = true;
24
	public $public_api_scheme = 'https';
25
26
	public $output_status_code = 200;
27
28
	public $trapped_error = null;
29
	public $did_output = false;
30
31
	/**
32
	 * @return WPCOM_JSON_API instance
33
	 */
34
	static function init( $method = null, $url = null, $post_body = null ) {
35
		if ( !self::$self ) {
36
			$class = function_exists( 'get_called_class' ) ? get_called_class() : __CLASS__;
37
			self::$self = new $class( $method, $url, $post_body );
38
		}
39
		return self::$self;
40
	}
41
42
	function add( WPCOM_JSON_API_Endpoint $endpoint ) {
43
		$path_versions = serialize( array (
44
			$endpoint->path,
45
			$endpoint->min_version,
46
			$endpoint->max_version,
47
		) );
48
		if ( !isset( $this->endpoints[$path_versions] ) ) {
49
			$this->endpoints[$path_versions] = array();
50
		}
51
		$this->endpoints[$path_versions][$endpoint->method] = $endpoint;
52
	}
53
54
	static function is_truthy( $value ) {
55
		switch ( strtolower( (string) $value ) ) {
56
		case '1' :
57
		case 't' :
58
		case 'true' :
59
			return true;
60
		}
61
62
		return false;
63
	}
64
65
	static function is_falsy( $value ) {
66
		switch ( strtolower( (string) $value ) ) {
67
			case '0' :
68
			case 'f' :
69
			case 'false' :
70
				return true;
71
		}
72
73
		return false;
74
	}
75
76
	function __construct() {
77
		$args = func_get_args();
78
		call_user_func_array( array( $this, 'setup_inputs' ), $args );
79
	}
80
81
	function setup_inputs( $method = null, $url = null, $post_body = null ) {
82
		if ( is_null( $method ) ) {
83
			$this->method = strtoupper( $_SERVER['REQUEST_METHOD'] );
84
		} else {
85
			$this->method = strtoupper( $method );
86
		}
87
		if ( is_null( $url ) ) {
88
			$this->url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
89
		} else {
90
			$this->url = $url;
91
		}
92
93
		$parsed     = parse_url( $this->url );
94
		$this->path = $parsed['path'];
95
96
		if ( !empty( $parsed['query'] ) ) {
97
			wp_parse_str( $parsed['query'], $this->query );
98
		}
99
100
		if ( isset( $_SERVER['HTTP_ACCEPT'] ) && $_SERVER['HTTP_ACCEPT'] ) {
101
			$this->accept = $_SERVER['HTTP_ACCEPT'];
102
		}
103
104
		if ( 'POST' === $this->method ) {
105
			if ( is_null( $post_body ) ) {
106
				$this->post_body = file_get_contents( 'php://input' );
107
108
				if ( isset( $_SERVER['HTTP_CONTENT_TYPE'] ) && $_SERVER['HTTP_CONTENT_TYPE'] ) {
109
					$this->content_type = $_SERVER['HTTP_CONTENT_TYPE'];
110
				} elseif ( isset( $_SERVER['CONTENT_TYPE'] ) && $_SERVER['CONTENT_TYPE'] ) {
111
					$this->content_type = $_SERVER['CONTENT_TYPE'] ;
112
				} elseif ( '{' === $this->post_body[0] ) {
113
					$this->content_type = 'application/json';
114
				} else {
115
					$this->content_type = 'application/x-www-form-urlencoded';
116
				}
117
118
				if ( 0 === strpos( strtolower( $this->content_type ), 'multipart/' ) ) {
119
					$this->post_body = http_build_query( stripslashes_deep( $_POST ) );
120
					$this->files = $_FILES;
121
					$this->content_type = 'multipart/form-data';
122
				}
123
			} else {
124
				$this->post_body = $post_body;
125
				$this->content_type = '{' === isset( $this->post_body[0] ) && $this->post_body[0] ? 'application/json' : 'application/x-www-form-urlencoded';
126
			}
127
		} else {
128
			$this->post_body = null;
129
			$this->content_type = null;
130
		}
131
132
		$this->_server_https = array_key_exists( 'HTTPS', $_SERVER ) ? $_SERVER['HTTPS'] : '--UNset--';
133
	}
134
135
	function initialize() {
136
		$this->token_details['blog_id'] = Jetpack_Options::get_option( 'id' );
137
	}
138
139
	function serve( $exit = true ) {
140
		ini_set( 'display_errors', false );
141
142
		$this->exit = (bool) $exit;
143
144
		// This was causing problems with Jetpack, but is necessary for wpcom
145
		// @see https://github.com/Automattic/jetpack/pull/2603
146
		// @see r124548-wpcom
147
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
148
			add_filter( 'home_url', array( $this, 'ensure_http_scheme_of_home_url' ), 10, 3 );
149
		}
150
151
		add_filter( 'user_can_richedit', '__return_true' );
152
153
		add_filter( 'comment_edit_pre', array( $this, 'comment_edit_pre' ) );
154
155
		$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...
156
		if ( 'OPTIONS' == $this->method ) {
157
			/**
158
			 * Fires before the page output.
159
			 * Can be used to specify custom header options.
160
			 *
161
			 * @module json-api
162
			 *
163
			 * @since 3.1.0
164
			 */
165
			do_action( 'wpcom_json_api_options' );
166
			return $this->output( 200, '', 'plain/text' );
167
		}
168
169
		if ( is_wp_error( $initialization ) ) {
170
			$this->output_error( $initialization );
171
			return;
172
		}
173
174
		// Normalize path and extract API version
175
		$this->path = untrailingslashit( $this->path );
176
		preg_match( '#^/rest/v(\d+(\.\d+)*)#', $this->path, $matches );
177
178
		// HACK Alert!
179
		// In order to workaround a bug in the iOS 5.6 release we need to handle /rest/sites/new as if it was
180
		// /rest/v1.1/sites/new
181
		if ( $this->path === '/rest/sites/new' ) {
182
			$this->version = '1.1';
183
			$this->path = '/sites/new';
184
		} else if ( $this->path === '/rest/users/new' ) {
185
			$this->version = '1.1';
186
			$this->path = '/users/new';
187
		} else {
188
			$this->path = substr( $this->path, strlen( $matches[0] ) );
189
			$this->version = $matches[1];
190
		}
191
192
		$allowed_methods = array( 'GET', 'POST' );
193
		$four_oh_five = false;
194
195
		$is_help = preg_match( '#/help/?$#i', $this->path );
196
		$matching_endpoints = array();
197
198
		if ( $is_help ) {
199
			$origin = get_http_origin();
200
201
			if ( !empty( $origin ) && 'GET' == $this->method ) {
202
				header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
203
			}
204
205
			$this->path = substr( rtrim( $this->path, '/' ), 0, -5 );
206
			// Show help for all matching endpoints regardless of method
207
			$methods = $allowed_methods;
208
			$find_all_matching_endpoints = true;
209
			// How deep to truncate each endpoint's path to see if it matches this help request
210
			$depth = substr_count( $this->path, '/' ) + 1;
211
			if ( false !== stripos( $this->accept, 'javascript' ) || false !== stripos( $this->accept, 'json' ) ) {
212
				$help_content_type = 'json';
213
			} else {
214
				$help_content_type = 'html';
215
			}
216
		} else {
217
			if ( in_array( $this->method, $allowed_methods ) ) {
218
				// Only serve requested method
219
				$methods = array( $this->method );
220
				$find_all_matching_endpoints = false;
221
			} else {
222
				// We don't allow this requested method - find matching endpoints and send 405
223
				$methods = $allowed_methods;
224
				$find_all_matching_endpoints = true;
225
				$four_oh_five = true;
226
			}
227
		}
228
229
		// Find which endpoint to serve
230
		$found = false;
231
		foreach ( $this->endpoints as $endpoint_path_versions => $endpoints_by_method ) {
232
			$endpoint_path_versions = unserialize( $endpoint_path_versions );
233
			$endpoint_path        = $endpoint_path_versions[0];
234
			$endpoint_min_version = $endpoint_path_versions[1];
235
			$endpoint_max_version = $endpoint_path_versions[2];
236
237
			// Make sure max_version is not less than min_version
238
			if ( version_compare( $endpoint_max_version, $endpoint_min_version, '<' ) ) {
239
				$endpoint_max_version = $endpoint_min_version;
240
			}
241
242
			foreach ( $methods as $method ) {
243
				if ( !isset( $endpoints_by_method[$method] ) ) {
244
					continue;
245
				}
246
247
				// Normalize
248
				$endpoint_path = untrailingslashit( $endpoint_path );
249
				if ( $is_help ) {
250
					// Truncate path at help depth
251
					$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...
252
				}
253
254
				// Generate regular expression from sprintf()
255
				$endpoint_path_regex = str_replace( array( '%s', '%d' ), array( '([^/?&]+)', '(\d+)' ), $endpoint_path );
256
257
				if ( !preg_match( "#^$endpoint_path_regex\$#", $this->path, $path_pieces ) ) {
258
					// This endpoint does not match the requested path.
259
					continue;
260
				}
261
262
				if ( version_compare( $this->version, $endpoint_min_version, '<' ) || version_compare( $this->version, $endpoint_max_version, '>' ) ) {
263
					// This endpoint does not match the requested version.
264
					continue;
265
				}
266
267
				$found = true;
268
269
				if ( $find_all_matching_endpoints ) {
270
					$matching_endpoints[] = array( $endpoints_by_method[$method], $path_pieces );
271
				} else {
272
					// The method parameters are now in $path_pieces
273
					$endpoint = $endpoints_by_method[$method];
274
					break 2;
275
				}
276
			}
277
		}
278
279
		if ( !$found ) {
280
			return $this->output( 404, '', 'text/plain' );
281
		}
282
283
		if ( $four_oh_five ) {
284
			$allowed_methods = array();
285
			foreach ( $matching_endpoints as $matching_endpoint ) {
286
				$allowed_methods[] = $matching_endpoint[0]->method;
287
			}
288
289
			header( 'Allow: ' . strtoupper( join( ',', array_unique( $allowed_methods ) ) ) );
290
			return $this->output( 405, array( 'error' => 'not_allowed', 'error_message' => 'Method not allowed' ) );
291
		}
292
293
		if ( $is_help ) {
294
			/**
295
			 * Fires before the API output.
296
			 *
297
			 * @since 1.9.0
298
			 *
299
			 * @param string help.
300
			 */
301
			do_action( 'wpcom_json_api_output', 'help' );
302
			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...
303
				$docs = array();
304 View Code Duplication
				foreach ( $matching_endpoints as $matching_endpoint ) {
305
					if ( $matching_endpoint[0]->is_publicly_documentable() || WPCOM_JSON_API__DEBUG )
306
						$docs[] = call_user_func( array( $matching_endpoint[0], 'generate_documentation' ) );
307
				}
308
				return $this->output( 200, $docs );
309
			} else {
310
				status_header( 200 );
311 View Code Duplication
				foreach ( $matching_endpoints as $matching_endpoint ) {
312
					if ( $matching_endpoint[0]->is_publicly_documentable() || WPCOM_JSON_API__DEBUG )
313
						call_user_func( array( $matching_endpoint[0], 'document' ) );
314
				}
315
			}
316
			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...
317
		}
318
319
		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...
320
			return $this->output( 404, '', 'text/plain' );
321
		}
322
323
		/** This action is documented in class.json-api.php */
324
		do_action( 'wpcom_json_api_output', $endpoint->stat );
325
326
		$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...
327
328
		if ( !$response && !is_array( $response ) ) {
329
			return $this->output( 500, '', 'text/plain' );
330
		} elseif ( is_wp_error( $response ) ) {
331
			return $this->output_error( $response );
332
		}
333
334
		$output_status_code = $this->output_status_code;
335
		$this->set_output_status_code();
336
337
		return $this->output( $output_status_code, $response );
338
	}
339
340
	function process_request( WPCOM_JSON_API_Endpoint $endpoint, $path_pieces ) {
341
		$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...
342
		return call_user_func_array( array( $endpoint, 'callback' ), $path_pieces );
343
	}
344
345
	function output_early( $status_code, $response = null, $content_type = 'application/json' ) {
346
		$exit = $this->exit;
347
		$this->exit = false;
348
		if ( is_wp_error( $response ) )
349
			$this->output_error( $response );
350
		else
351
			$this->output( $status_code, $response, $content_type );
352
		$this->exit = $exit;
353
		if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
354
			$this->finish_request();
355
		}
356
	}
357
358
	function set_output_status_code( $code = 200 ) {
359
		$this->output_status_code = $code;
360
	}
361
362
	function output( $status_code, $response = null, $content_type = 'application/json' ) {
363
		// In case output() was called before the callback returned
364
		if ( $this->did_output ) {
365
			if ( $this->exit )
366
				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...
367
			return $content_type;
368
		}
369
		$this->did_output = true;
370
371
		// 400s and 404s are allowed for all origins
372
		if ( 404 == $status_code || 400 == $status_code )
373
			header( 'Access-Control-Allow-Origin: *' );
374
375
		if ( is_null( $response ) ) {
376
			$response = new stdClass;
377
		}
378
379
		if ( 'text/plain' === $content_type ) {
380
			status_header( (int) $status_code );
381
			header( 'Content-Type: text/plain' );
382
			echo $response;
383
			if ( $this->exit ) {
384
				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...
385
			}
386
387
			return $content_type;
388
		}
389
390
		$response = $this->filter_fields( $response );
391
392
		if ( isset( $this->query['http_envelope'] ) && self::is_truthy( $this->query['http_envelope'] ) ) {
393
			$response = array(
394
				'code' => (int) $status_code,
395
				'headers' => array(
396
					array(
397
						'name' => 'Content-Type',
398
						'value' => $content_type,
399
					),
400
				),
401
				'body' => $response,
402
			);
403
			$status_code = 200;
404
			$content_type = 'application/json';
405
		}
406
407
		status_header( (int) $status_code );
408
		header( "Content-Type: $content_type" );
409
		if ( isset( $this->query['callback'] ) && is_string( $this->query['callback'] ) ) {
410
			$callback = preg_replace( '/[^a-z0-9_.]/i', '', $this->query['callback'] );
411
		} else {
412
			$callback = false;
413
		}
414
415
		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...
416
			// Mitigate Rosetta Flash [1] by setting the Content-Type-Options: nosniff header
417
			// and by prepending the JSONP response with a JS comment.
418
			// [1] http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
419
			echo "/**/$callback(";
420
421
		}
422
		echo $this->json_encode( $response );
423
		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...
424
			echo ");";
425
		}
426
427
		if ( $this->exit ) {
428
			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...
429
		}
430
431
		return $content_type;
432
	}
433
434
	public static function serializable_error ( $error ) {
435
436
		$status_code = $error->get_error_data();
437
438
		if ( is_array( $status_code ) )
439
			$status_code = $status_code['status_code'];
440
441
		if ( !$status_code ) {
442
			$status_code = 400;
443
		}
444
		$response = array(
445
			'error'   => $error->get_error_code(),
446
			'message' => $error->get_error_message(),
447
		);
448
		return array(
449
			'status_code' => $status_code,
450
			'errors' => $response
451
		);
452
	}
453
454
	function output_error( $error ) {
455
		if ( function_exists( 'bump_stats_extra' ) ) {
456
			$client_id = ! empty( $this->token_details['client_id'] ) ? $this->token_details['client_id'] : 0;
457
			bump_stats_extra( 'rest-api-errors', $client_id );
458
		}
459
460
		$error_response = $this->serializable_error( $error );
461
462
		return $this->output( $error_response[ 'status_code'], $error_response['errors'] );
463
	}
464
465
	function filter_fields( $response ) {
466
		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...
467
			return $response;
468
469
		$fields = array_map( 'trim', explode( ',', $this->query['fields'] ) );
470
471
		if ( is_object( $response ) ) {
472
			$response = (array) $response;
473
		}
474
475
		$has_filtered = false;
476
		if ( is_array( $response ) && empty( $response['ID'] ) ) {
477
			$keys_to_filter = array(
478
				'categories',
479
				'comments',
480
				'connections',
481
				'domains',
482
				'groups',
483
				'likes',
484
				'media',
485
				'notes',
486
				'posts',
487
				'services',
488
				'sites',
489
				'suggestions',
490
				'tags',
491
				'themes',
492
				'topics',
493
				'users',
494
			);
495
496
			foreach ( $keys_to_filter as $key_to_filter ) {
497
				if ( ! isset( $response[ $key_to_filter ] ) || $has_filtered )
498
					continue;
499
500
				foreach ( $response[ $key_to_filter ] as $key => $values ) {
501
					if ( is_object( $values ) ) {
502
						$response[ $key_to_filter ][ $key ] = (object) array_intersect_key( (array) $values, array_flip( $fields ) );
503
					} elseif ( is_array( $values ) ) {
504
						$response[ $key_to_filter ][ $key ] = array_intersect_key( $values, array_flip( $fields ) );
505
					}
506
				}
507
508
				$has_filtered = true;
509
			}
510
		}
511
512
		if ( ! $has_filtered ) {
513
			if ( is_object( $response ) ) {
514
				$response = (object) array_intersect_key( (array) $response, array_flip( $fields ) );
515
			} else if ( is_array( $response ) ) {
516
				$response = array_intersect_key( $response, array_flip( $fields ) );
517
			}
518
		}
519
520
		return $response;
521
	}
522
523
	function ensure_http_scheme_of_home_url( $url, $path, $original_scheme ) {
524
		if ( $original_scheme ) {
525
			return $url;
526
		}
527
528
		return preg_replace( '#^https:#', 'http:', $url );
529
	}
530
531
	function comment_edit_pre( $comment_content ) {
532
		return htmlspecialchars_decode( $comment_content, ENT_QUOTES );
533
	}
534
535
	function json_encode( $data ) {
536
		return json_encode( $data );
537
	}
538
539
	function ends_with( $haystack, $needle ) {
540
		return $needle === substr( $haystack, -strlen( $needle ) );
541
	}
542
543
	// Returns the site's blog_id in the WP.com ecosystem
544
	function get_blog_id_for_output() {
545
		return $this->token_details['blog_id'];
546
	}
547
548
	// Returns the site's local blog_id
549
	function get_blog_id( $blog_id ) {
0 ignored issues
show
Unused Code introduced by
The parameter $blog_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
550
		return $GLOBALS['blog_id'];
551
	}
552
553
	function switch_to_blog_and_validate_user( $blog_id = 0, $verify_token_for_blog = true ) {
0 ignored issues
show
Unused Code introduced by
The parameter $verify_token_for_blog is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
554
		if ( $this->is_restricted_blog( $blog_id ) ) {
555
			return new WP_Error( 'unauthorized', 'User cannot access this restricted blog', 403 );
556
		}
557
558 View Code Duplication
		if ( -1 == get_option( 'blog_public' ) && !current_user_can( 'read' ) ) {
559
			return new WP_Error( 'unauthorized', 'User cannot access this private blog.', 403 );
560
		}
561
562
		return $blog_id;
563
	}
564
565
	// Returns true if the specified blog ID is a restricted blog
566
	function is_restricted_blog( $blog_id ) {
567
		/**
568
		 * Filters all REST API access and return a 403 unauthorized response for all Restricted blog IDs.
569
		 *
570
		 * @module json-api
571
		 *
572
		 * @since 3.4.0
573
		 *
574
		 * @param array $array Array of Blog IDs.
575
		 */
576
		$restricted_blog_ids = apply_filters( 'wpcom_json_api_restricted_blog_ids', array() );
577
		return true === in_array( $blog_id, $restricted_blog_ids );
578
	}
579
580
	function post_like_count( $blog_id, $post_id ) {
0 ignored issues
show
Unused Code introduced by
The parameter $blog_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $post_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
581
		return 0;
582
	}
583
584
	function is_liked( $blog_id, $post_id ) {
0 ignored issues
show
Unused Code introduced by
The parameter $blog_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $post_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
585
		return false;
586
	}
587
588
	function is_reblogged( $blog_id, $post_id ) {
0 ignored issues
show
Unused Code introduced by
The parameter $blog_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $post_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
589
		return false;
590
	}
591
592
	function is_following( $blog_id ) {
0 ignored issues
show
Unused Code introduced by
The parameter $blog_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
593
		return false;
594
	}
595
596
	function add_global_ID( $blog_id, $post_id ) {
0 ignored issues
show
Unused Code introduced by
The parameter $blog_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $post_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
597
		return '';
598
	}
599
600
	function get_avatar_url( $email, $avatar_size = 96 ) {
601
		add_filter( 'pre_option_show_avatars', '__return_true', 999 );
602
		$_SERVER['HTTPS'] = 'off';
603
604
		$avatar_img_element = get_avatar( $email, $avatar_size, '' );
605
606
		if ( !$avatar_img_element || is_wp_error( $avatar_img_element ) ) {
607
			$return = '';
608
		} elseif ( !preg_match( '#src=([\'"])?(.*?)(?(1)\\1|\s)#', $avatar_img_element, $matches ) ) {
609
			$return = '';
610
		} else {
611
			$return = esc_url_raw( htmlspecialchars_decode( $matches[2] ) );
612
		}
613
614
		remove_filter( 'pre_option_show_avatars', '__return_true', 999 );
615
		if ( '--UNset--' === $this->_server_https ) {
616
			unset( $_SERVER['HTTPS'] );
617
		} else {
618
			$_SERVER['HTTPS'] = $this->_server_https;
619
		}
620
621
		return $return;
622
	}
623
624
	/**
625
	 * Traps `wp_die()` calls and outputs a JSON response instead.
626
	 * The result is always output, never returned.
627
	 *
628
	 * @param string|null $error_code.  Call with string to start the trapping.  Call with null to stop.
0 ignored issues
show
Documentation introduced by
There is no parameter named $error_code.. Did you maybe mean $error_code?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
629
	 */
630
	function trap_wp_die( $error_code = null ) {
631
		// Stop trapping
632
		if ( is_null( $error_code ) ) {
633
			$this->trapped_error = null;
634
			remove_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) );
635
			return;
636
		}
637
638
		// If API called via PHP, bail: don't do our custom wp_die().  Do the normal wp_die().
639
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
640
			if ( ! defined( 'REST_API_REQUEST' ) || ! REST_API_REQUEST ) {
641
				return;
642
			}
643
		} else {
644
			if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
645
				return;
646
			}
647
		}
648
649
		// Start trapping
650
		$this->trapped_error = array(
651
			'status'  => 500,
652
			'code'    => $error_code,
653
			'message' => '',
654
		);
655
656
		add_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) );
657
	}
658
659
	function wp_die_handler_callback() {
660
		return array( $this, 'wp_die_handler' );
661
	}
662
663
	function wp_die_handler( $message, $title = '', $args = array() ) {
664
		$args = wp_parse_args( $args, array(
665
			'response' => 500,
666
		) );
667
668
		if ( $title ) {
669
			$message = "$title: $message";
670
		}
671
672
		switch ( $this->trapped_error['code'] ) {
673
		case 'comment_failure' :
674
			if ( did_action( 'comment_duplicate_trigger' ) ) {
675
				$this->trapped_error['code'] = 'comment_duplicate';
676
			} else if ( did_action( 'comment_flood_trigger' ) ) {
677
				$this->trapped_error['code'] = 'comment_flood';
678
			}
679
			break;
680
		}
681
682
		$this->trapped_error['status']  = $args['response'];
683
		$this->trapped_error['message'] = wp_kses( $message, array() );
684
685
		// We still want to exit so that code execution stops where it should.
686
		// Attach the JSON output to WordPress' shutdown handler
687
		add_action( 'shutdown', array( $this, 'output_trapped_error' ), 0 );
688
		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...
689
	}
690
691
	function output_trapped_error() {
692
		$this->exit = false; // We're already exiting once.  Don't do it twice.
693
		$this->output( $this->trapped_error['status'], (object) array(
694
			'error'   => $this->trapped_error['code'],
695
			'message' => $this->trapped_error['message'],
696
		) );
697
	}
698
699
	function finish_request() {
700
		if ( function_exists( 'fastcgi_finish_request' ) )
701
			return fastcgi_finish_request();
702
	}
703
}
704