Completed
Push — master ( f47a1d...37f03f )
by Claudio
28:06
created

WC_API_Server::is_xml_request()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 12
Ratio 100 %
Metric Value
dl 12
loc 12
rs 8.8571
nc 3
cc 5
eloc 6
nop 0
1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 23 and the first side effect is on line 18.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

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

Loading history...
2
/**
3
 * WooCommerce API
4
 *
5
 * Handles REST API requests
6
 *
7
 * This class and related code (JSON response handler, resource classes) are based on WP-API v0.6 (https://github.com/WP-API/WP-API)
8
 * Many thanks to Ryan McCue and any other contributors!
9
 *
10
 * @author      WooThemes
11
 * @category    API
12
 * @package     WooCommerce/API
13
 * @since       2.1
14
 * @version     2.1
15
 */
16
17
if ( ! defined( 'ABSPATH' ) ) {
18
	exit; // Exit if accessed directly
19
}
20
21
require_once ABSPATH . 'wp-admin/includes/admin.php';
22
23
class WC_API_Server {
24
25
	const METHOD_GET    = 1;
26
	const METHOD_POST   = 2;
27
	const METHOD_PUT    = 4;
28
	const METHOD_PATCH  = 8;
29
	const METHOD_DELETE = 16;
30
31
	const READABLE   = 1;  // GET
32
	const CREATABLE  = 2;  // POST
33
	const EDITABLE   = 14; // POST | PUT | PATCH
34
	const DELETABLE  = 16; // DELETE
35
	const ALLMETHODS = 31; // GET | POST | PUT | PATCH | DELETE
36
37
	/**
38
	 * Does the endpoint accept a raw request body?
39
	 */
40
	const ACCEPT_RAW_DATA = 64;
41
42
	/** Does the endpoint accept a request body? (either JSON or XML) */
43
	const ACCEPT_DATA = 128;
44
45
	/**
46
	 * Should we hide this endpoint from the index?
47
	 */
48
	const HIDDEN_ENDPOINT = 256;
49
50
	/**
51
	 * Map of HTTP verbs to constants
52
	 * @var array
53
	 */
54
	public static $method_map = array(
55
		'HEAD'   => self::METHOD_GET,
56
		'GET'    => self::METHOD_GET,
57
		'POST'   => self::METHOD_POST,
58
		'PUT'    => self::METHOD_PUT,
59
		'PATCH'  => self::METHOD_PATCH,
60
		'DELETE' => self::METHOD_DELETE,
61
	);
62
63
	/**
64
	 * Requested path (relative to the API root, wp-json.php)
65
	 *
66
	 * @var string
67
	 */
68
	public $path = '';
69
70
	/**
71
	 * Requested method (GET/HEAD/POST/PUT/PATCH/DELETE)
72
	 *
73
	 * @var string
74
	 */
75
	public $method = 'HEAD';
76
77
	/**
78
	 * Request parameters
79
	 *
80
	 * This acts as an abstraction of the superglobals
81
	 * (GET => $_GET, POST => $_POST)
82
	 *
83
	 * @var array
84
	 */
85
	public $params = array( 'GET' => array(), 'POST' => array() );
86
87
	/**
88
	 * Request headers
89
	 *
90
	 * @var array
91
	 */
92
	public $headers = array();
93
94
	/**
95
	 * Request files (matches $_FILES)
96
	 *
97
	 * @var array
98
	 */
99
	public $files = array();
100
101
	/**
102
	 * Request/Response handler, either JSON by default
103
	 * or XML if requested by client
104
	 *
105
	 * @var WC_API_Handler
106
	 */
107
	public $handler;
108
109
110
	/**
111
	 * Setup class and set request/response handler
112
	 *
113
	 * @since 2.1
114
	 * @param $path
115
	 * @return WC_API_Server
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
116
	 */
117
	public function __construct( $path ) {
118
119
		if ( empty( $path ) ) {
120
			if ( isset( $_SERVER['PATH_INFO'] ) )
121
				$path = $_SERVER['PATH_INFO'];
122
			else
123
				$path = '/';
124
		}
125
126
		$this->path           = $path;
127
		$this->method         = $_SERVER['REQUEST_METHOD'];
128
		$this->params['GET']  = $_GET;
129
		$this->params['POST'] = $_POST;
130
		$this->headers        = $this->get_headers( $_SERVER );
131
		$this->files          = $_FILES;
132
133
		// Compatibility for clients that can't use PUT/PATCH/DELETE
134
		if ( isset( $_GET['_method'] ) ) {
135
			$this->method = strtoupper( $_GET['_method'] );
136
		} elseif ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) {
137
			$this->method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'];
138
		}
139
140
		// determine type of request/response and load handler, JSON by default
141
		if ( $this->is_json_request() )
142
			$handler_class = 'WC_API_JSON_Handler';
143
144
		elseif ( $this->is_xml_request() )
145
			$handler_class = 'WC_API_XML_Handler';
146
147
		else
148
			$handler_class = apply_filters( 'woocommerce_api_default_response_handler', 'WC_API_JSON_Handler', $this->path, $this );
149
150
		$this->handler = new $handler_class();
151
	}
152
153
	/**
154
	 * Check authentication for the request
155
	 *
156
	 * @since 2.1
157
	 * @return WP_User|WP_Error WP_User object indicates successful login, WP_Error indicates unsuccessful login
158
	 */
159 View Code Duplication
	public function check_authentication() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
160
161
		// allow plugins to remove default authentication or add their own authentication
162
		$user = apply_filters( 'woocommerce_api_check_authentication', null, $this );
163
164
		// API requests run under the context of the authenticated user
165
		if ( is_a( $user, 'WP_User' ) )
166
			wp_set_current_user( $user->ID );
167
168
		// WP_Errors are handled in serve_request()
169
		elseif ( ! is_wp_error( $user ) )
170
			$user = new WP_Error( 'woocommerce_api_authentication_error', __( 'Invalid authentication method', 'woocommerce' ), array( 'code' => 500 ) );
171
172
		return $user;
173
	}
174
175
	/**
176
	 * Convert an error to an array
177
	 *
178
	 * This iterates over all error codes and messages to change it into a flat
179
	 * array. This enables simpler client behaviour, as it is represented as a
180
	 * list in JSON rather than an object/map
181
	 *
182
	 * @since 2.1
183
	 * @param WP_Error $error
184
	 * @return array List of associative arrays with code and message keys
185
	 */
186 View Code Duplication
	protected function error_to_array( $error ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
187
		$errors = array();
188
		foreach ( (array) $error->errors as $code => $messages ) {
189
			foreach ( (array) $messages as $message ) {
190
				$errors[] = array( 'code' => $code, 'message' => $message );
191
			}
192
		}
193
		return array( 'errors' => $errors );
194
	}
195
196
	/**
197
	 * Handle serving an API request
198
	 *
199
	 * Matches the current server URI to a route and runs the first matching
200
	 * callback then outputs a JSON representation of the returned value.
201
	 *
202
	 * @since 2.1
203
	 * @uses WC_API_Server::dispatch()
204
	 */
205 View Code Duplication
	public function serve_request() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
206
207
		do_action( 'woocommerce_api_server_before_serve', $this );
208
209
		$this->header( 'Content-Type', $this->handler->get_content_type(), true );
210
211
		// the API is enabled by default
212
		if ( ! apply_filters( 'woocommerce_api_enabled', true, $this ) || ( 'no' === get_option( 'woocommerce_api_enabled' ) ) ) {
213
214
			$this->send_status( 404 );
215
216
			echo $this->handler->generate_response( array( 'errors' => array( 'code' => 'woocommerce_api_disabled', 'message' => 'The WooCommerce API is disabled on this site' ) ) );
217
218
			return;
219
		}
220
221
		$result = $this->check_authentication();
222
223
		// if authorization check was successful, dispatch the request
224
		if ( ! is_wp_error( $result ) ) {
225
			$result = $this->dispatch();
226
		}
227
228
		// handle any dispatch errors
229
		if ( is_wp_error( $result ) ) {
230
			$data = $result->get_error_data();
231
			if ( is_array( $data ) && isset( $data['status'] ) ) {
232
				$this->send_status( $data['status'] );
233
			}
234
235
			$result = $this->error_to_array( $result );
236
		}
237
238
		// This is a filter rather than an action, since this is designed to be
239
		// re-entrant if needed
240
		$served = apply_filters( 'woocommerce_api_serve_request', false, $result, $this );
241
242
		if ( ! $served ) {
243
244
			if ( 'HEAD' === $this->method )
245
				return;
246
247
			echo $this->handler->generate_response( $result );
248
		}
249
	}
250
251
	/**
252
	 * Retrieve the route map
253
	 *
254
	 * The route map is an associative array with path regexes as the keys. The
255
	 * value is an indexed array with the callback function/method as the first
256
	 * item, and a bitmask of HTTP methods as the second item (see the class
257
	 * constants).
258
	 *
259
	 * Each route can be mapped to more than one callback by using an array of
260
	 * the indexed arrays. This allows mapping e.g. GET requests to one callback
261
	 * and POST requests to another.
262
	 *
263
	 * Note that the path regexes (array keys) must have @ escaped, as this is
264
	 * used as the delimiter with preg_match()
265
	 *
266
	 * @since 2.1
267
	 * @return array `'/path/regex' => array( $callback, $bitmask )` or `'/path/regex' => array( array( $callback, $bitmask ), ...)`
268
	 */
269 View Code Duplication
	public function get_routes() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
270
271
		// index added by default
272
		$endpoints = array(
273
274
			'/' => array( array( $this, 'get_index' ), self::READABLE ),
275
		);
276
277
		$endpoints = apply_filters( 'woocommerce_api_endpoints', $endpoints );
278
279
		// Normalise the endpoints
280
		foreach ( $endpoints as $route => &$handlers ) {
281
			if ( count( $handlers ) <= 2 && isset( $handlers[1] ) && ! is_array( $handlers[1] ) ) {
282
				$handlers = array( $handlers );
283
			}
284
		}
285
286
		return $endpoints;
287
	}
288
289
	/**
290
	 * Match the request to a callback and call it
291
	 *
292
	 * @since 2.1
293
	 * @return mixed The value returned by the callback, or a WP_Error instance
294
	 */
295 View Code Duplication
	public function dispatch() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
296
297
		switch ( $this->method ) {
298
299
			case 'HEAD':
300
			case 'GET':
301
				$method = self::METHOD_GET;
302
				break;
303
304
			case 'POST':
305
				$method = self::METHOD_POST;
306
				break;
307
308
			case 'PUT':
309
				$method = self::METHOD_PUT;
310
				break;
311
312
			case 'PATCH':
313
				$method = self::METHOD_PATCH;
314
				break;
315
316
			case 'DELETE':
317
				$method = self::METHOD_DELETE;
318
				break;
319
320
			default:
321
				return new WP_Error( 'woocommerce_api_unsupported_method', __( 'Unsupported request method', 'woocommerce' ), array( 'status' => 400 ) );
322
		}
323
324
		foreach ( $this->get_routes() as $route => $handlers ) {
325
			foreach ( $handlers as $handler ) {
326
				$callback = $handler[0];
327
				$supported = isset( $handler[1] ) ? $handler[1] : self::METHOD_GET;
328
329
				if ( !( $supported & $method ) )
330
					continue;
331
332
				$match = preg_match( '@^' . $route . '$@i', urldecode( $this->path ), $args );
333
334
				if ( !$match )
335
					continue;
336
337
				if ( ! is_callable( $callback ) )
338
					return new WP_Error( 'woocommerce_api_invalid_handler', __( 'The handler for the route is invalid', 'woocommerce' ), array( 'status' => 500 ) );
339
340
				$args = array_merge( $args, $this->params['GET'] );
341
				if ( $method & self::METHOD_POST ) {
342
					$args = array_merge( $args, $this->params['POST'] );
343
				}
344
				if ( $supported & self::ACCEPT_DATA ) {
345
					$data = $this->handler->parse_body( $this->get_raw_data() );
346
					$args = array_merge( $args, array( 'data' => $data ) );
347
				}
348
				elseif ( $supported & self::ACCEPT_RAW_DATA ) {
349
					$data = $this->get_raw_data();
350
					$args = array_merge( $args, array( 'data' => $data ) );
351
				}
352
353
				$args['_method']  = $method;
354
				$args['_route']   = $route;
355
				$args['_path']    = $this->path;
356
				$args['_headers'] = $this->headers;
357
				$args['_files']   = $this->files;
358
359
				$args = apply_filters( 'woocommerce_api_dispatch_args', $args, $callback );
360
361
				// Allow plugins to halt the request via this filter
362
				if ( is_wp_error( $args ) ) {
363
					return $args;
364
				}
365
366
				$params = $this->sort_callback_params( $callback, $args );
367
				if ( is_wp_error( $params ) )
368
					return $params;
369
370
				return call_user_func_array( $callback, $params );
371
			}
372
		}
373
374
		return new WP_Error( 'woocommerce_api_no_route', __( 'No route was found matching the URL and request method', 'woocommerce' ), array( 'status' => 404 ) );
375
	}
376
377
	/**
378
	 * Sort parameters by order specified in method declaration
379
	 *
380
	 * Takes a callback and a list of available params, then filters and sorts
381
	 * by the parameters the method actually needs, using the Reflection API
382
	 *
383
	 * @since 2.1
384
	 * @param callable|array $callback the endpoint callback
385
	 * @param array $provided the provided request parameters
386
	 * @return array
387
	 */
388
	protected function sort_callback_params( $callback, $provided ) {
389
		if ( is_array( $callback ) )
390
			$ref_func = new ReflectionMethod( $callback[0], $callback[1] );
391
		else
392
			$ref_func = new ReflectionFunction( $callback );
393
394
		$wanted = $ref_func->getParameters();
395
		$ordered_parameters = array();
396
397
		foreach ( $wanted as $param ) {
398
			if ( isset( $provided[ $param->getName() ] ) ) {
399
				// We have this parameters in the list to choose from
400
401
				$ordered_parameters[] = is_array( $provided[ $param->getName() ] ) ? array_map( 'urldecode', $provided[ $param->getName() ] ) : urldecode( $provided[ $param->getName() ] );
402
			}
403
			elseif ( $param->isDefaultValueAvailable() ) {
404
				// We don't have this parameter, but it's optional
405
				$ordered_parameters[] = $param->getDefaultValue();
406
			}
407
			else {
408
				// We don't have this parameter and it wasn't optional, abort!
409
				return new WP_Error( 'woocommerce_api_missing_callback_param', sprintf( __( 'Missing parameter %s', 'woocommerce' ), $param->getName() ), array( 'status' => 400 ) );
410
			}
411
		}
412
		return $ordered_parameters;
413
	}
414
415
	/**
416
	 * Get the site index.
417
	 *
418
	 * This endpoint describes the capabilities of the site.
419
	 *
420
	 * @since 2.1
421
	 * @return array Index entity
422
	 */
423
	public function get_index() {
424
425
		// General site data
426
		$available = array( 'store' => array(
427
			'name'        => get_option( 'blogname' ),
428
			'description' => get_option( 'blogdescription' ),
429
			'URL'         => get_option( 'siteurl' ),
430
			'wc_version'  => WC()->version,
431
			'routes'      => array(),
432
			'meta'        => array(
433
				'timezone'			 => wc_timezone_string(),
434
				'currency'       	 => get_woocommerce_currency(),
435
				'currency_format'    => get_woocommerce_currency_symbol(),
436
				'tax_included'   	 => wc_prices_include_tax(),
437
				'weight_unit'    	 => get_option( 'woocommerce_weight_unit' ),
438
				'dimension_unit' 	 => get_option( 'woocommerce_dimension_unit' ),
439
				'ssl_enabled'    	 => ( 'yes' === get_option( 'woocommerce_force_ssl_checkout' ) ),
440
				'permalinks_enabled' => ( '' !== get_option( 'permalink_structure' ) ),
441
				'links'          	 => array(
442
					'help' => 'http://woothemes.github.io/woocommerce/rest-api/',
443
				),
444
			),
445
		) );
446
447
		// Find the available routes
448
		foreach ( $this->get_routes() as $route => $callbacks ) {
449
			$data = array();
450
451
			$route = preg_replace( '#\(\?P(<\w+?>).*?\)#', '$1', $route );
452
			$methods = array();
0 ignored issues
show
Unused Code introduced by
$methods is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
453
			foreach ( self::$method_map as $name => $bitmask ) {
454
				foreach ( $callbacks as $callback ) {
455
					// Skip to the next route if any callback is hidden
456
					if ( $callback[1] & self::HIDDEN_ENDPOINT )
457
						continue 3;
458
459
					if ( $callback[1] & $bitmask )
460
						$data['supports'][] = $name;
461
462
					if ( $callback[1] & self::ACCEPT_DATA )
463
						$data['accepts_data'] = true;
464
465
					// For non-variable routes, generate links
466
					if ( strpos( $route, '<' ) === false ) {
467
						$data['meta'] = array(
468
							'self' => get_woocommerce_api_url( $route ),
469
						);
470
					}
471
				}
472
			}
473
			$available['store']['routes'][ $route ] = apply_filters( 'woocommerce_api_endpoints_description', $data );
474
		}
475
		return apply_filters( 'woocommerce_api_index', $available );
476
	}
477
478
	/**
479
	 * Send a HTTP status code
480
	 *
481
	 * @since 2.1
482
	 * @param int $code HTTP status
483
	 */
484
	public function send_status( $code ) {
485
		status_header( $code );
486
	}
487
488
	/**
489
	 * Send a HTTP header
490
	 *
491
	 * @since 2.1
492
	 * @param string $key Header key
493
	 * @param string $value Header value
494
	 * @param boolean $replace Should we replace the existing header?
495
	 */
496
	public function header( $key, $value, $replace = true ) {
497
		header( sprintf( '%s: %s', $key, $value ), $replace );
498
	}
499
500
	/**
501
	 * Send a Link header
502
	 *
503
	 * @internal The $rel parameter is first, as this looks nicer when sending multiple
504
	 *
505
	 * @link http://tools.ietf.org/html/rfc5988
506
	 * @link http://www.iana.org/assignments/link-relations/link-relations.xml
507
	 *
508
	 * @since 2.1
509
	 * @param string $rel Link relation. Either a registered type, or an absolute URL
510
	 * @param string $link Target IRI for the link
511
	 * @param array $other Other parameters to send, as an associative array
512
	 */
513 View Code Duplication
	public function link_header( $rel, $link, $other = array() ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
514
515
		$header = sprintf( '<%s>; rel="%s"', $link, esc_attr( $rel ) );
516
517
		foreach ( $other as $key => $value ) {
518
519
			if ( 'title' == $key ) {
520
521
				$value = '"' . $value . '"';
522
			}
523
524
			$header .= '; ' . $key . '=' . $value;
525
		}
526
527
		$this->header( 'Link', $header, false );
528
	}
529
530
	/**
531
	 * Send pagination headers for resources
532
	 *
533
	 * @since 2.1
534
	 * @param WP_Query|WP_User_Query $query
535
	 */
536
	public function add_pagination_headers( $query ) {
537
538
		// WP_User_Query
539
		if ( is_a( $query, 'WP_User_Query' ) ) {
540
541
			$page        = $query->page;
542
			$single      = count( $query->get_results() ) == 1;
543
			$total       = $query->get_total();
544
			$total_pages = $query->total_pages;
545
546
		// WP_Query
547 View Code Duplication
		} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
548
549
			$page        = $query->get( 'paged' );
550
			$single      = $query->is_single();
551
			$total       = $query->found_posts;
552
			$total_pages = $query->max_num_pages;
553
		}
554
555
		if ( ! $page )
556
			$page = 1;
557
558
		$next_page = absint( $page ) + 1;
559
560 View Code Duplication
		if ( ! $single ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
561
562
			// first/prev
563
			if ( $page > 1 ) {
564
				$this->link_header( 'first', $this->get_paginated_url( 1 ) );
565
				$this->link_header( 'prev', $this->get_paginated_url( $page -1 ) );
566
			}
567
568
			// next
569
			if ( $next_page <= $total_pages ) {
570
				$this->link_header( 'next', $this->get_paginated_url( $next_page ) );
571
			}
572
573
			// last
574
			if ( $page != $total_pages )
575
				$this->link_header( 'last', $this->get_paginated_url( $total_pages ) );
576
		}
577
578
		$this->header( 'X-WC-Total', $total );
579
		$this->header( 'X-WC-TotalPages', $total_pages );
580
581
		do_action( 'woocommerce_api_pagination_headers', $this, $query );
582
	}
583
584
	/**
585
	 * Returns the request URL with the page query parameter set to the specified page
586
	 *
587
	 * @since 2.1
588
	 * @param int $page
589
	 * @return string
590
	 */
591 View Code Duplication
	private function get_paginated_url( $page ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
592
593
		// remove existing page query param
594
		$request = remove_query_arg( 'page' );
595
596
		// add provided page query param
597
		$request = urldecode( add_query_arg( 'page', $page, $request ) );
598
599
		// get the home host
600
		$host = parse_url( get_home_url(), PHP_URL_HOST );
601
602
		return set_url_scheme( "http://{$host}{$request}" );
603
	}
604
605
	/**
606
	 * Retrieve the raw request entity (body)
607
	 *
608
	 * @since 2.1
609
	 * @return string
610
	 */
611 View Code Duplication
	public function get_raw_data() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
612
		// $HTTP_RAW_POST_DATA is deprecated on PHP 5.6
613
		if ( function_exists( 'phpversion' ) && version_compare( phpversion(), '5.6', '>=' ) ) {
614
			return file_get_contents( 'php://input' );
615
		}
616
617
		global $HTTP_RAW_POST_DATA;
618
619
		// A bug in PHP < 5.2.2 makes $HTTP_RAW_POST_DATA not set by default,
620
		// but we can do it ourself.
621
		if ( ! isset( $HTTP_RAW_POST_DATA ) ) {
622
			$HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
623
		}
624
625
		return $HTTP_RAW_POST_DATA;
626
	}
627
628
	/**
629
	 * Parse an RFC3339 datetime into a MySQl datetime
630
	 *
631
	 * Invalid dates default to unix epoch
632
	 *
633
	 * @since 2.1
634
	 * @param string $datetime RFC3339 datetime
635
	 * @return string MySQl datetime (YYYY-MM-DD HH:MM:SS)
636
	 */
637 View Code Duplication
	public function parse_datetime( $datetime ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
638
639
		// Strip millisecond precision (a full stop followed by one or more digits)
640
		if ( strpos( $datetime, '.' ) !== false ) {
641
			$datetime = preg_replace( '/\.\d+/', '', $datetime );
642
		}
643
644
		// default timezone to UTC
645
		$datetime = preg_replace( '/[+-]\d+:+\d+$/', '+00:00', $datetime );
646
647
		try {
648
649
			$datetime = new DateTime( $datetime, new DateTimeZone( 'UTC' ) );
650
651
		} catch ( Exception $e ) {
652
653
			$datetime = new DateTime( '@0' );
654
655
		}
656
657
		return $datetime->format( 'Y-m-d H:i:s' );
658
	}
659
660
	/**
661
	 * Format a unix timestamp or MySQL datetime into an RFC3339 datetime
662
	 *
663
	 * @since 2.1
664
	 * @param int|string $timestamp unix timestamp or MySQL datetime
665
	 * @param bool $convert_to_utc
666
	 * @return string RFC3339 datetime
667
	 */
668 View Code Duplication
	public function format_datetime( $timestamp, $convert_to_utc = false ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
669
670
		if ( $convert_to_utc ) {
671
			$timezone = new DateTimeZone( wc_timezone_string() );
672
		} else {
673
			$timezone = new DateTimeZone( 'UTC' );
674
		}
675
676
		try {
677
678
			if ( is_numeric( $timestamp ) ) {
679
				$date = new DateTime( "@{$timestamp}" );
680
			} else {
681
				$date = new DateTime( $timestamp, $timezone );
682
			}
683
684
			// convert to UTC by adjusting the time based on the offset of the site's timezone
685
			if ( $convert_to_utc ) {
686
				$date->modify( -1 * $date->getOffset() . ' seconds' );
687
			}
688
689
		} catch ( Exception $e ) {
690
691
			$date = new DateTime( '@0' );
692
		}
693
694
		return $date->format( 'Y-m-d\TH:i:s\Z' );
695
	}
696
697
	/**
698
	 * Extract headers from a PHP-style $_SERVER array
699
	 *
700
	 * @since 2.1
701
	 * @param array $server Associative array similar to $_SERVER
702
	 * @return array Headers extracted from the input
703
	 */
704 View Code Duplication
	public function get_headers($server) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
705
		$headers = array();
706
		// CONTENT_* headers are not prefixed with HTTP_
707
		$additional = array('CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true);
708
709
		foreach ($server as $key => $value) {
710
			if ( strpos( $key, 'HTTP_' ) === 0) {
711
				$headers[ substr( $key, 5 ) ] = $value;
712
			}
713
			elseif ( isset( $additional[ $key ] ) ) {
714
				$headers[ $key ] = $value;
715
			}
716
		}
717
718
		return $headers;
719
	}
720
721
	/**
722
	 * Check if the current request accepts a JSON response by checking the endpoint suffix (.json) or
723
	 * the HTTP ACCEPT header
724
	 *
725
	 * @since 2.1
726
	 * @return bool
727
	 */
728 View Code Duplication
	private function is_json_request() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
729
730
		// check path
731
		if ( false !== stripos( $this->path, '.json' ) )
732
			return true;
733
734
		// check ACCEPT header, only 'application/json' is acceptable, see RFC 4627
735
		if ( isset( $this->headers['ACCEPT'] ) && 'application/json' == $this->headers['ACCEPT'] )
736
			return true;
737
738
		return false;
739
	}
740
741
	/**
742
	 * Check if the current request accepts an XML response by checking the endpoint suffix (.xml) or
743
	 * the HTTP ACCEPT header
744
	 *
745
	 * @since 2.1
746
	 * @return bool
747
	 */
748 View Code Duplication
	private function is_xml_request() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
749
750
		// check path
751
		if ( false !== stripos( $this->path, '.xml' ) )
752
			return true;
753
754
		// check headers, 'application/xml' or 'text/xml' are acceptable, see RFC 2376
755
		if ( isset( $this->headers['ACCEPT'] ) && ( 'application/xml' == $this->headers['ACCEPT'] || 'text/xml' == $this->headers['ACCEPT'] ) )
756
			return true;
757
758
		return false;
759
	}
760
}
761