Completed
Push — add/blogname-locale-connect-ur... ( 948a6a...8e4901 )
by
unknown
30:01 queued 20:34
created

WPCOM_JSON_API::add()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 11
rs 9.4285
cc 2
eloc 8
nc 2
nop 1
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
	/**
34
	 * @return WPCOM_JSON_API instance
35
	 */
36
	static function init( $method = null, $url = null, $post_body = null ) {
37
		if ( !self::$self ) {
38
			$class = function_exists( 'get_called_class' ) ? get_called_class() : __CLASS__;
39
			self::$self = new $class( $method, $url, $post_body );
40
		}
41
		return self::$self;
42
	}
43
44
	function add( WPCOM_JSON_API_Endpoint $endpoint ) {
45
		$path_versions = serialize( array (
46
			$endpoint->path,
47
			$endpoint->min_version,
48
			$endpoint->max_version,
49
		) );
50
		if ( !isset( $this->endpoints[$path_versions] ) ) {
51
			$this->endpoints[$path_versions] = array();
52
		}
53
		$this->endpoints[$path_versions][$endpoint->method] = $endpoint;
54
	}
55
56
	static function is_truthy( $value ) {
57
		switch ( strtolower( (string) $value ) ) {
58
		case '1' :
59
		case 't' :
60
		case 'true' :
61
			return true;
62
		}
63
64
		return false;
65
	}
66
67
	static function is_falsy( $value ) {
68
		switch ( strtolower( (string) $value ) ) {
69
			case '0' :
70
			case 'f' :
71
			case 'false' :
72
				return true;
73
		}
74
75
		return false;
76
	}
77
78
	function __construct() {
79
		$args = func_get_args();
80
		call_user_func_array( array( $this, 'setup_inputs' ), $args );
81
	}
82
83
	function setup_inputs( $method = null, $url = null, $post_body = null ) {
84
		if ( is_null( $method ) ) {
85
			$this->method = strtoupper( $_SERVER['REQUEST_METHOD'] );
86
		} else {
87
			$this->method = strtoupper( $method );
88
		}
89
		if ( is_null( $url ) ) {
90
			$this->url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
91
		} else {
92
			$this->url = $url;
93
		}
94
95
		$parsed     = parse_url( $this->url );
96
		$this->path = $parsed['path'];
97
98
		if ( !empty( $parsed['query'] ) ) {
99
			wp_parse_str( $parsed['query'], $this->query );
100
		}
101
102
		if ( isset( $_SERVER['HTTP_ACCEPT'] ) && $_SERVER['HTTP_ACCEPT'] ) {
103
			$this->accept = $_SERVER['HTTP_ACCEPT'];
104
		}
105
106
		if ( 'POST' === $this->method ) {
107
			if ( is_null( $post_body ) ) {
108
				$this->post_body = file_get_contents( 'php://input' );
109
110
				if ( isset( $_SERVER['HTTP_CONTENT_TYPE'] ) && $_SERVER['HTTP_CONTENT_TYPE'] ) {
111
					$this->content_type = $_SERVER['HTTP_CONTENT_TYPE'];
112
				} elseif ( isset( $_SERVER['CONTENT_TYPE'] ) && $_SERVER['CONTENT_TYPE'] ) {
113
					$this->content_type = $_SERVER['CONTENT_TYPE'] ;
114
				} elseif ( '{' === $this->post_body[0] ) {
115
					$this->content_type = 'application/json';
116
				} else {
117
					$this->content_type = 'application/x-www-form-urlencoded';
118
				}
119
120
				if ( 0 === strpos( strtolower( $this->content_type ), 'multipart/' ) ) {
121
					$this->post_body = http_build_query( stripslashes_deep( $_POST ) );
122
					$this->files = $_FILES;
123
					$this->content_type = 'multipart/form-data';
124
				}
125
			} else {
126
				$this->post_body = $post_body;
127
				$this->content_type = '{' === isset( $this->post_body[0] ) && $this->post_body[0] ? 'application/json' : 'application/x-www-form-urlencoded';
128
			}
129
		} else {
130
			$this->post_body = null;
131
			$this->content_type = null;
132
		}
133
134
		$this->_server_https = array_key_exists( 'HTTPS', $_SERVER ) ? $_SERVER['HTTPS'] : '--UNset--';
135
	}
136
137
	function initialize() {
138
		$this->token_details['blog_id'] = Jetpack_Options::get_option( 'id' );
139
	}
140
141
	function serve( $exit = true ) {
142
		ini_set( 'display_errors', false );
143
144
		$this->exit = (bool) $exit;
145
146
		// This was causing problems with Jetpack, but is necessary for wpcom
147
		// @see https://github.com/Automattic/jetpack/pull/2603
148
		// @see r124548-wpcom
149
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
150
			add_filter( 'home_url', array( $this, 'ensure_http_scheme_of_home_url' ), 10, 3 );
151
		}
152
153
		add_filter( 'user_can_richedit', '__return_true' );
154
155
		add_filter( 'comment_edit_pre', array( $this, 'comment_edit_pre' ) );
156
157
		$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...
158
		if ( 'OPTIONS' == $this->method ) {
159
			/**
160
			 * Fires before the page output.
161
			 * Can be used to specify custom header options.
162
			 *
163
			 * @module json-api
164
			 *
165
			 * @since 3.1.0
166
			 */
167
			do_action( 'wpcom_json_api_options' );
168
			return $this->output( 200, '', 'plain/text' );
169
		}
170
171
		if ( is_wp_error( $initialization ) ) {
172
			$this->output_error( $initialization );
173
			return;
174
		}
175
176
		// Normalize path and extract API version
177
		$this->path = untrailingslashit( $this->path );
178
		preg_match( '#^/rest/v(\d+(\.\d+)*)#', $this->path, $matches );
179
		$this->path = substr( $this->path, strlen( $matches[0] ) );
180
		$this->version = $matches[1];
181
182
		$allowed_methods = array( 'GET', 'POST' );
183
		$four_oh_five = false;
184
185
		$is_help = preg_match( '#/help/?$#i', $this->path );
186
		$matching_endpoints = array();
187
188
		if ( $is_help ) {
189
			$origin = get_http_origin();
190
191
			if ( !empty( $origin ) && 'GET' == $this->method ) {
192
				header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
193
			}
194
195
			$this->path = substr( rtrim( $this->path, '/' ), 0, -5 );
196
			// Show help for all matching endpoints regardless of method
197
			$methods = $allowed_methods;
198
			$find_all_matching_endpoints = true;
199
			// How deep to truncate each endpoint's path to see if it matches this help request
200
			$depth = substr_count( $this->path, '/' ) + 1;
201
			if ( false !== stripos( $this->accept, 'javascript' ) || false !== stripos( $this->accept, 'json' ) ) {
202
				$help_content_type = 'json';
203
			} else {
204
				$help_content_type = 'html';
205
			}
206
		} else {
207
			if ( in_array( $this->method, $allowed_methods ) ) {
208
				// Only serve requested method
209
				$methods = array( $this->method );
210
				$find_all_matching_endpoints = false;
211
			} else {
212
				// We don't allow this requested method - find matching endpoints and send 405
213
				$methods = $allowed_methods;
214
				$find_all_matching_endpoints = true;
215
				$four_oh_five = true;
216
			}
217
		}
218
219
		// Find which endpoint to serve
220
		$found = false;
221
		foreach ( $this->endpoints as $endpoint_path_versions => $endpoints_by_method ) {
222
			$endpoint_path_versions = unserialize( $endpoint_path_versions );
223
			$endpoint_path        = $endpoint_path_versions[0];
224
			$endpoint_min_version = $endpoint_path_versions[1];
225
			$endpoint_max_version = $endpoint_path_versions[2];
226
227
			// Make sure max_version is not less than min_version
228
			if ( version_compare( $endpoint_max_version, $endpoint_min_version, '<' ) ) {
229
				$endpoint_max_version = $endpoint_min_version;
230
			}
231
232
			foreach ( $methods as $method ) {
233
				if ( !isset( $endpoints_by_method[$method] ) ) {
234
					continue;
235
				}
236
237
				// Normalize
238
				$endpoint_path = untrailingslashit( $endpoint_path );
239
				if ( $is_help ) {
240
					// Truncate path at help depth
241
					$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...
242
				}
243
244
				// Generate regular expression from sprintf()
245
				$endpoint_path_regex = str_replace( array( '%s', '%d' ), array( '([^/?&]+)', '(\d+)' ), $endpoint_path );
246
247
				if ( !preg_match( "#^$endpoint_path_regex\$#", $this->path, $path_pieces ) ) {
248
					// This endpoint does not match the requested path.
249
					continue;
250
				}
251
252
				if ( version_compare( $this->version, $endpoint_min_version, '<' ) || version_compare( $this->version, $endpoint_max_version, '>' ) ) {
253
					// This endpoint does not match the requested version.
254
					continue;
255
				}
256
257
				$found = true;
258
259
				if ( $find_all_matching_endpoints ) {
260
					$matching_endpoints[] = array( $endpoints_by_method[$method], $path_pieces );
261
				} else {
262
					// The method parameters are now in $path_pieces
263
					$endpoint = $endpoints_by_method[$method];
264
					break 2;
265
				}
266
			}
267
		}
268
269
		if ( !$found ) {
270
			return $this->output( 404, '', 'text/plain' );
271
		}
272
273
		if ( $four_oh_five ) {
274
			$allowed_methods = array();
275
			foreach ( $matching_endpoints as $matching_endpoint ) {
276
				$allowed_methods[] = $matching_endpoint[0]->method;
277
			}
278
279
			header( 'Allow: ' . strtoupper( join( ',', array_unique( $allowed_methods ) ) ) );
280
			return $this->output( 405, array( 'error' => 'not_allowed', 'error_message' => 'Method not allowed' ) );
281
		}
282
283
		if ( $is_help ) {
284
			/**
285
			 * Fires before the API output.
286
			 *
287
			 * @since 1.9.0
288
			 *
289
			 * @param string help.
290
			 */
291
			do_action( 'wpcom_json_api_output', 'help' );
292
			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...
293
				$docs = array();
294 View Code Duplication
				foreach ( $matching_endpoints as $matching_endpoint ) {
295
					if ( $matching_endpoint[0]->is_publicly_documentable() || WPCOM_JSON_API__DEBUG )
296
						$docs[] = call_user_func( array( $matching_endpoint[0], 'generate_documentation' ) );
297
				}
298
				return $this->output( 200, $docs );
299
			} else {
300
				status_header( 200 );
301 View Code Duplication
				foreach ( $matching_endpoints as $matching_endpoint ) {
302
					if ( $matching_endpoint[0]->is_publicly_documentable() || WPCOM_JSON_API__DEBUG )
303
						call_user_func( array( $matching_endpoint[0], 'document' ) );
304
				}
305
			}
306
			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...
307
		}
308
309
		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...
310
			return $this->output( 404, '', 'text/plain' );
311
		}
312
313
		/** This action is documented in class.json-api.php */
314
		do_action( 'wpcom_json_api_output', $endpoint->stat );
315
316
		$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...
317
318
		if ( !$response && !is_array( $response ) ) {
319
			return $this->output( 500, '', 'text/plain' );
320
		} elseif ( is_wp_error( $response ) ) {
321
			return $this->output_error( $response );
322
		}
323
324
		$output_status_code = $this->output_status_code;
325
		$this->set_output_status_code();
326
327
		return $this->output( $output_status_code, $response );
328
	}
329
330
	function process_request( WPCOM_JSON_API_Endpoint $endpoint, $path_pieces ) {
331
		$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...
332
		return call_user_func_array( array( $endpoint, 'callback' ), $path_pieces );
333
	}
334
335
	function output_early( $status_code, $response = null, $content_type = 'application/json' ) {
336
		$exit = $this->exit;
337
		$this->exit = false;
338
		if ( is_wp_error( $response ) )
339
			$this->output_error( $response );
340
		else
341
			$this->output( $status_code, $response, $content_type );
342
		$this->exit = $exit;
343
		if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
344
			$this->finish_request();
345
		}
346
	}
347
348
	function set_output_status_code( $code = 200 ) {
349
		$this->output_status_code = $code;
350
	}
351
352
	function output( $status_code, $response = null, $content_type = 'application/json' ) {
353
		// In case output() was called before the callback returned
354
		if ( $this->did_output ) {
355
			if ( $this->exit )
356
				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...
357
			return $content_type;
358
		}
359
		$this->did_output = true;
360
361
		// 400s and 404s are allowed for all origins
362
		if ( 404 == $status_code || 400 == $status_code )
363
			header( 'Access-Control-Allow-Origin: *' );
364
365
		if ( is_null( $response ) ) {
366
			$response = new stdClass;
367
		}
368
369
		if ( 'text/plain' === $content_type ) {
370
			status_header( (int) $status_code );
371
			header( 'Content-Type: text/plain' );
372
			echo $response;
373
			if ( $this->exit ) {
374
				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...
375
			}
376
377
			return $content_type;
378
		}
379
380
		$response = $this->filter_fields( $response );
381
382
		if ( isset( $this->query['http_envelope'] ) && self::is_truthy( $this->query['http_envelope'] ) ) {
383
			$response = array(
384
				'code' => (int) $status_code,
385
				'headers' => array(
386
					array(
387
						'name' => 'Content-Type',
388
						'value' => $content_type,
389
					),
390
				),
391
				'body' => $response,
392
			);
393
			$status_code = 200;
394
			$content_type = 'application/json';
395
		}
396
397
		status_header( (int) $status_code );
398
		header( "Content-Type: $content_type" );
399
		if ( isset( $this->query['callback'] ) && is_string( $this->query['callback'] ) ) {
400
			$callback = preg_replace( '/[^a-z0-9_.]/i', '', $this->query['callback'] );
401
		} else {
402
			$callback = false;
403
		}
404
405
		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...
406
			// Mitigate Rosetta Flash [1] by setting the Content-Type-Options: nosniff header
407
			// and by prepending the JSONP response with a JS comment.
408
			// [1] http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
409
			echo "/**/$callback(";
410
411
		}
412
		echo $this->json_encode( $response );
413
		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...
414
			echo ");";
415
		}
416
417
		if ( $this->exit ) {
418
			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...
419
		}
420
421
		return $content_type;
422
	}
423
424
	public static function serializable_error ( $error ) {
425
426
		$status_code = $error->get_error_data();
427
428
		if ( is_array( $status_code ) )
429
			$status_code = $status_code['status_code'];
430
431
		if ( !$status_code ) {
432
			$status_code = 400;
433
		}
434
		$response = array(
435
			'error'   => $error->get_error_code(),
436
			'message' => $error->get_error_message(),
437
		);
438
		return array(
439
			'status_code' => $status_code,
440
			'errors' => $response
441
		);
442
	}
443
444
	function output_error( $error ) {
445
		if ( function_exists( 'bump_stats_extra' ) ) {
446
			$client_id = ! empty( $this->token_details['client_id'] ) ? $this->token_details['client_id'] : 0;
447
			bump_stats_extra( 'rest-api-errors', $client_id );
448
		}
449
450
		$error_response = $this->serializable_error( $error );
451
452
		return $this->output( $error_response[ 'status_code'], $error_response['errors'] );
453
	}
454
455
	function filter_fields( $response ) {
456
		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...
457
			return $response;
458
459
		$fields = array_map( 'trim', explode( ',', $this->query['fields'] ) );
460
461
		if ( is_object( $response ) ) {
462
			$response = (array) $response;
463
		}
464
465
		$has_filtered = false;
466
		if ( is_array( $response ) && empty( $response['ID'] ) ) {
467
			$keys_to_filter = array(
468
				'categories',
469
				'comments',
470
				'connections',
471
				'domains',
472
				'groups',
473
				'likes',
474
				'media',
475
				'notes',
476
				'posts',
477
				'services',
478
				'sites',
479
				'suggestions',
480
				'tags',
481
				'themes',
482
				'topics',
483
				'users',
484
			);
485
486
			foreach ( $keys_to_filter as $key_to_filter ) {
487
				if ( ! isset( $response[ $key_to_filter ] ) || $has_filtered )
488
					continue;
489
490
				foreach ( $response[ $key_to_filter ] as $key => $values ) {
491
					if ( is_object( $values ) ) {
492
						$response[ $key_to_filter ][ $key ] = (object) array_intersect_key( (array) $values, array_flip( $fields ) );
493
					} elseif ( is_array( $values ) ) {
494
						$response[ $key_to_filter ][ $key ] = array_intersect_key( $values, array_flip( $fields ) );
495
					}
496
				}
497
498
				$has_filtered = true;
499
			}
500
		}
501
502
		if ( ! $has_filtered ) {
503
			if ( is_object( $response ) ) {
504
				$response = (object) array_intersect_key( (array) $response, array_flip( $fields ) );
505
			} else if ( is_array( $response ) ) {
506
				$response = array_intersect_key( $response, array_flip( $fields ) );
507
			}
508
		}
509
510
		return $response;
511
	}
512
513
	function ensure_http_scheme_of_home_url( $url, $path, $original_scheme ) {
514
		if ( $original_scheme ) {
515
			return $url;
516
		}
517
518
		return preg_replace( '#^https:#', 'http:', $url );
519
	}
520
521
	function comment_edit_pre( $comment_content ) {
522
		return htmlspecialchars_decode( $comment_content, ENT_QUOTES );
523
	}
524
525
	function json_encode( $data ) {
526
		return json_encode( $data );
527
	}
528
529
	function ends_with( $haystack, $needle ) {
530
		return $needle === substr( $haystack, -strlen( $needle ) );
531
	}
532
533
	// Returns the site's blog_id in the WP.com ecosystem
534
	function get_blog_id_for_output() {
535
		return $this->token_details['blog_id'];
536
	}
537
538
	// Returns the site's local blog_id
539
	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...
540
		return $GLOBALS['blog_id'];
541
	}
542
543
	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...
544
		if ( $this->is_restricted_blog( $blog_id ) ) {
545
			return new WP_Error( 'unauthorized', 'User cannot access this restricted blog', 403 );
546
		}
547
548
		if ( -1 == get_option( 'blog_public' ) && !current_user_can( 'read' ) ) {
549
			return new WP_Error( 'unauthorized', 'User cannot access this private blog.', 403 );
550
		}
551
552
		return $blog_id;
553
	}
554
555
	// Returns true if the specified blog ID is a restricted blog
556
	function is_restricted_blog( $blog_id ) {
557
		/**
558
		 * Filters all REST API access and return a 403 unauthorized response for all Restricted blog IDs.
559
		 *
560
		 * @module json-api
561
		 *
562
		 * @since 3.4.0
563
		 *
564
		 * @param array $array Array of Blog IDs.
565
		 */
566
		$restricted_blog_ids = apply_filters( 'wpcom_json_api_restricted_blog_ids', array() );
567
		return true === in_array( $blog_id, $restricted_blog_ids );
568
	}
569
570
	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...
571
		return 0;
572
	}
573
574
	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...
575
		return false;
576
	}
577
578
	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...
579
		return false;
580
	}
581
582
	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...
583
		return false;
584
	}
585
586
	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...
587
		return '';
588
	}
589
590
	function get_avatar_url( $email, $avatar_size = 96 ) {
591
		add_filter( 'pre_option_show_avatars', '__return_true', 999 );
592
		$_SERVER['HTTPS'] = 'off';
593
594
		$avatar_img_element = get_avatar( $email, $avatar_size, '' );
595
596
		if ( !$avatar_img_element || is_wp_error( $avatar_img_element ) ) {
597
			$return = '';
598
		} elseif ( !preg_match( '#src=([\'"])?(.*?)(?(1)\\1|\s)#', $avatar_img_element, $matches ) ) {
599
			$return = '';
600
		} else {
601
			$return = esc_url_raw( htmlspecialchars_decode( $matches[2] ) );
602
		}
603
604
		remove_filter( 'pre_option_show_avatars', '__return_true', 999 );
605
		if ( '--UNset--' === $this->_server_https ) {
606
			unset( $_SERVER['HTTPS'] );
607
		} else {
608
			$_SERVER['HTTPS'] = $this->_server_https;
609
		}
610
611
		return $return;
612
	}
613
614
	/**
615
	 * Traps `wp_die()` calls and outputs a JSON response instead.
616
	 * The result is always output, never returned.
617
	 *
618
	 * @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...
619
	 */
620
	function trap_wp_die( $error_code = null ) {
621
		// Stop trapping
622
		if ( is_null( $error_code ) ) {
623
			$this->trapped_error = null;
624
			remove_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) );
625
			return;
626
		}
627
628
		// If API called via PHP, bail: don't do our custom wp_die().  Do the normal wp_die().
629
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
630
			if ( ! defined( 'REST_API_REQUEST' ) || ! REST_API_REQUEST ) {
631
				return;
632
			}
633
		} else {
634
			if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
635
				return;
636
			}
637
		}
638
639
		// Start trapping
640
		$this->trapped_error = array(
641
			'status'  => 500,
642
			'code'    => $error_code,
643
			'message' => '',
644
		);
645
646
		add_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) );
647
	}
648
649
	function wp_die_handler_callback() {
650
		return array( $this, 'wp_die_handler' );
651
	}
652
653
	function wp_die_handler( $message, $title = '', $args = array() ) {
654
		$args = wp_parse_args( $args, array(
655
			'response' => 500,
656
		) );
657
658
		if ( $title ) {
659
			$message = "$title: $message";
660
		}
661
662
		switch ( $this->trapped_error['code'] ) {
663
		case 'comment_failure' :
664
			if ( did_action( 'comment_duplicate_trigger' ) ) {
665
				$this->trapped_error['code'] = 'comment_duplicate';
666
			} else if ( did_action( 'comment_flood_trigger' ) ) {
667
				$this->trapped_error['code'] = 'comment_flood';
668
			}
669
			break;
670
		}
671
672
		$this->trapped_error['status']  = $args['response'];
673
		$this->trapped_error['message'] = wp_kses( $message, array() );
674
675
		// We still want to exit so that code execution stops where it should.
676
		// Attach the JSON output to 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