PluginProbe
WooCommerce Square / 4.3.2
WooCommerce Square v4.3.2
5.5.0 5.4.3 5.4.2 5.4.1 5.4.0 trunk 1.0.25 1.0.26 1.0.27 1.0.28 1.0.29 1.0.30 1.0.31 1.0.32 1.0.33 1.0.34 1.0.35 1.0.36 1.0.37 1.0.38 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 All 132 releases
woocommerce-square / includes / Framework / Api / Base.php
Base.php
677 lines 16.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WooCommerce Plugin Framework
4 *
5 * This source file is subject to the GNU General Public License v3.0
6 * that is bundled with this package in the file license.txt.
7 * It is also available through the world-wide-web at this URL:
8 * http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0 or later
9 * If you did not receive a copy of the license and are unable to
10 * obtain it through the world-wide-web, please send an email
11 * to license@skyverge.com so we can send you a copy immediately.
12 *
13 * @since 3.0.0
14 * @author WooCommerce / SkyVerge
15 * @copyright Copyright (c) 2013-2019, SkyVerge, Inc.
16 * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0 or later
17 */
18
19 namespace WooCommerce\Square\Framework\Api;
20
21 use WooCommerce;
22 use WooCommerce\Square\Framework\Square_Helper;
23
24 defined( 'ABSPATH' ) or exit;
25
26 /**
27 * Base API Class
28 *
29 * This class provides a standardized framework for constructing an API wrapper
30 * to external services. It is designed to be extremely flexible.
31 */
32 abstract class Base {
33
34 /** @var string request method, defaults to POST */
35 protected $request_method = 'POST';
36
37 /** @var string URI used for the request */
38 protected $request_uri;
39
40 /** @var array request headers */
41 protected $request_headers = array();
42
43 /** @var string request user-agent */
44 protected $request_user_agent;
45
46 /** @var string request HTTP version, defaults to 1.0 */
47 protected $request_http_version = '1.0';
48
49 /** @var string request duration */
50 protected $request_duration;
51
52 /** @var API_Request|object request */
53 protected $request;
54
55 /** @var string response code */
56 protected $response_code;
57
58 /** @var string response message */
59 protected $response_message;
60
61 /** @var array response headers */
62 protected $response_headers;
63
64 /** @var string raw response body */
65 protected $raw_response_body;
66
67 /** @var string response handler class name */
68 protected $response_handler;
69
70 /** @var API_Response|object response */
71 protected $response;
72
73 /**
74 * Perform the request and return the parsed response
75 *
76 * @since 3.0.0
77 *
78 * @param API_Request|object $request class instance which implements API_Request
79 * @return API_Response|object class instance which implements API_Response
80 * @throws API_Exception may be thrown in implementations
81 */
82 protected function perform_request( $request ) {
83
84 // ensure API is in its default state
85 $this->reset_response();
86
87 // save the request object
88 $this->request = $request;
89
90 $start_time = microtime( true );
91
92 // if this API requires TLS v1.2, force it
93 if ( $this->require_tls_1_2() ) {
94 add_action( 'http_api_curl', array( $this, 'set_tls_1_2_request' ), 10, 3 );
95 }
96
97 // perform the request
98 $response = $this->do_remote_request( $this->get_request_uri(), $this->get_request_args() );
99
100 // calculate request duration
101 $this->request_duration = round( microtime( true ) - $start_time, 5 );
102
103 try {
104
105 // parse & validate response
106 $response = $this->handle_response( $response );
107
108 } catch ( \Exception $e ) {
109
110 // alert other actors that a request has been made
111 $this->broadcast_request();
112
113 throw $e;
114 }
115
116 return $response;
117 }
118
119 /**
120 * Simple wrapper for wp_remote_request() so child classes can override this
121 * and provide their own transport mechanism if needed, e.g. a custom
122 * cURL implementation
123 *
124 * @since 3.0.0
125 *
126 * @param string $request_uri
127 * @param string $request_args
128 * @return array|\WP_Error
129 */
130 protected function do_remote_request( $request_uri, $request_args ) {
131
132 return wp_safe_remote_request( $request_uri, $request_args );
133 }
134
135 /**
136 * Handle and parse the response
137 *
138 * @since 3.0.0
139 * @param array|\WP_Error $response response data
140 * @throws \Exception network issues, timeouts, API errors, etc
141 * @return API_Request|object request class instance that implements API_Request
142 */
143 protected function handle_response( $response ) {
144
145 // check for WP HTTP API specific errors (network timeout, etc)
146 if ( is_wp_error( $response ) ) {
147 throw new \Exception( $response->get_error_message(), (int) $response->get_error_code() );
148 }
149
150 // set response data
151 $this->response_code = wp_remote_retrieve_response_code( $response );
152 $this->response_message = wp_remote_retrieve_response_message( $response );
153 $this->raw_response_body = wp_remote_retrieve_body( $response );
154
155 $response_headers = wp_remote_retrieve_headers( $response );
156
157 // WP 4.6+ returns an object
158 if ( is_object( $response_headers ) ) {
159 $response_headers = $response_headers->getAll();
160 }
161
162 $this->response_headers = $response_headers;
163
164 // parse the response body and tie it to the request
165 $this->response = $this->get_parsed_response( $this->raw_response_body );
166
167 // fire do_action() so other actors can act on request/response data,
168 // primarily used for logging
169 $this->broadcast_request();
170
171 return $this->response;
172 }
173
174 /**
175 * Return the parsed response object for the request
176 *
177 * @since 3.0.0
178 * @param string $raw_response_body
179 * @return object|API_Request response class instance which implements API_Request
180 */
181 protected function get_parsed_response( $raw_response_body ) {
182
183 $handler_class = $this->get_response_handler();
184
185 return new $handler_class( $raw_response_body );
186 }
187
188 /**
189 * Alert other actors that a request has been performed. This is primarily used
190 * for request logging.
191 *
192 * @since 3.0.0
193 */
194 protected function broadcast_request() {
195
196 $request_data = array(
197 'method' => $this->get_request_method(),
198 'uri' => $this->get_request_uri(),
199 'user-agent' => $this->get_request_user_agent(),
200 'headers' => $this->get_sanitized_request_headers(),
201 'body' => $this->get_sanitized_request_body(),
202 'duration' => $this->get_request_duration() . 's', // seconds
203 );
204
205 $response_data = array(
206 'code' => $this->get_response_code(),
207 'message' => $this->get_response_message(),
208 'headers' => $this->get_response_headers(),
209 'body' => $this->get_sanitized_response_body() ? $this->get_sanitized_response_body() : $this->get_raw_response_body(),
210 );
211
212 /**
213 * API Base Request Performed Action.
214 *
215 * Fired when an API request is performed via this base class. Plugins can
216 * hook into this to log request/response data.
217 *
218 * @since 3.0.0
219 * @param array $request_data {
220 * @type string $method request method, e.g. POST
221 * @type string $uri request URI
222 * @type string $user-agent
223 * @type string $headers request headers
224 * @type string $body request body
225 * @type string $duration in seconds
226 * }
227 * @param array $response data {
228 * @type string $code response HTTP code
229 * @type string $message response message
230 * @type string $headers response HTTP headers
231 * @type string $body response body
232 * }
233 * @param Base $this instance
234 */
235 do_action( 'wc_' . $this->get_api_id() . '_api_request_performed', $request_data, $response_data, $this );
236 }
237
238 /**
239 * Reset the API response members to their
240 *
241 * @since 3.0.0
242 */
243 protected function reset_response() {
244
245 $this->response_code = null;
246 $this->response_message = null;
247 $this->response_headers = null;
248 $this->raw_response_body = null;
249 $this->response = null;
250 $this->request_duration = null;
251 }
252
253 /**
254 * Get the request URI
255 *
256 * @since 3.0.0
257 * @return string
258 */
259 protected function get_request_uri() {
260
261 $uri = $this->request_uri . $this->get_request_path();
262
263 // append any query params to the URL when necessary
264 $query = $this->get_request_query();
265 if ( $query ) {
266
267 $url_parts = parse_url( $uri );
268
269 // if the URL already has some query params, add to them
270 if ( ! empty( $url_parts['query'] ) ) {
271 $query = '&' . $query;
272 } else {
273 $query = '?' . $query;
274 }
275
276 $uri = untrailingslashit( $uri ) . $query;
277 }
278
279 /**
280 * Request URI Filter.
281 *
282 * Allow actors to filter the request URI. Note that child classes can override
283 * this method, which means this filter may be invoked prior to the overridden
284 * method.
285 *
286 * @since 3.0.0
287 *
288 * @param string $uri current request URI
289 * @param Base class instance
290 */
291 return apply_filters( 'wc_' . $this->get_api_id() . '_api_request_uri', $uri, $this );
292 }
293
294 /**
295 * Gets the request path.
296 *
297 * @since 3.0.0
298 * @return string
299 */
300 protected function get_request_path() {
301
302 return ( $this->get_request() ) ? $this->get_request()->get_path() : '';
303 }
304
305 /**
306 * Gets the request URL query.
307 *
308 * @since 3.0.0
309 *
310 * @return string
311 */
312 protected function get_request_query() {
313
314 $query = '';
315 $request = $this->get_request();
316
317 if ( $request && in_array( strtoupper( $this->get_request_method() ), array( 'GET', 'HEAD' ), true ) ) {
318
319 $params = $request->get_params();
320
321 if ( ! empty( $params ) ) {
322 $query = http_build_query( $params, '', '&' );
323 }
324 }
325
326 return $query;
327 }
328
329 /**
330 * Get the request arguments in the format required by wp_remote_request()
331 *
332 * @since 3.0.0
333 *
334 * @return array
335 */
336 protected function get_request_args() {
337
338 $args = array(
339 'method' => $this->get_request_method(),
340 'timeout' => MINUTE_IN_SECONDS,
341 'redirection' => 0,
342 'httpversion' => $this->get_request_http_version(),
343 'sslverify' => true,
344 'blocking' => true,
345 'user-agent' => $this->get_request_user_agent(),
346 'headers' => $this->get_request_headers(),
347 'body' => $this->get_request_body(),
348 'cookies' => array(),
349 );
350
351 /**
352 * Request arguments.
353 *
354 * Allow other actors to filter the request arguments. Note that
355 * child classes can override this method, which means this filter may
356 * not be invoked, or may be invoked prior to the overridden method
357 *
358 * @since 3.0.0
359 * @param array $args request arguments
360 * @param Base class instance
361 */
362 return apply_filters( 'wc_' . $this->get_api_id() . '_http_request_args', $args, $this );
363 }
364
365 /**
366 * Get the request method, POST by default
367 *
368 * @since 3.0.0
369 * @return string
370 */
371 protected function get_request_method() {
372 // if the request object specifies the method to use, use that, otherwise use the API default
373 return $this->get_request() && $this->get_request()->get_method() ? $this->get_request()->get_method() : $this->request_method;
374 }
375
376 /**
377 * Gets the request body.
378 *
379 * @since 3.0.0
380 * @return string
381 */
382 protected function get_request_body() {
383
384 // GET & HEAD requests don't support a body
385 if ( in_array( strtoupper( $this->get_request_method() ), array( 'GET', 'HEAD' ), true ) ) {
386 return '';
387 }
388
389 return ( $this->get_request() && $this->get_request()->to_string() ) ? $this->get_request()->to_string() : '';
390 }
391
392 /**
393 * Gets the sanitized request body, for logging.
394 *
395 * @since 3.0.0
396 * @return string
397 */
398 protected function get_sanitized_request_body() {
399
400 // GET & HEAD requests don't support a body
401 if ( in_array( strtoupper( $this->get_request_method() ), array( 'GET', 'HEAD' ), true ) ) {
402 return '';
403 }
404
405 return ( $this->get_request() && $this->get_request()->to_string_safe() ) ? $this->get_request()->to_string_safe() : '';
406 }
407
408 /**
409 * Get the request HTTP version, 1.1 by default
410 *
411 * @since 3.0.0
412 * @return string
413 */
414 protected function get_request_http_version() {
415
416 return $this->request_http_version;
417 }
418
419 /**
420 * Get the request headers
421 *
422 * @since 3.0.0
423 * @return array
424 */
425 protected function get_request_headers() {
426 return $this->request_headers;
427 }
428
429 /**
430 * Get sanitized request headers suitable for logging, stripped of any
431 * confidential information
432 *
433 * The `Authorization` header is sanitized automatically.
434 *
435 * Child classes that implement any custom authorization headers should
436 * override this method to perform sanitization.
437 *
438 * @since 3.0.0
439 * @return array
440 */
441 protected function get_sanitized_request_headers() {
442
443 $headers = $this->get_request_headers();
444
445 if ( ! empty( $headers['Authorization'] ) ) {
446 $headers['Authorization'] = str_repeat( '*', strlen( $headers['Authorization'] ) );
447 }
448
449 return $headers;
450 }
451
452 /**
453 * Get the request user agent, defaults to:
454 *
455 * Dasherized-Plugin-Name/Plugin-Version (WooCommerce/WC-Version; WordPress/WP-Version)
456 *
457 * @since 3.0.0
458 * @return string
459 */
460 protected function get_request_user_agent() {
461
462 return sprintf( '%s/%s (WooCommerce/%s; WordPress/%s)', str_replace( ' ', '-', $this->get_plugin()->get_plugin_name() ), $this->get_plugin()->get_version(), WC_VERSION, $GLOBALS['wp_version'] );
463 }
464
465 /**
466 * Get the request duration in seconds, rounded to the 5th decimal place
467 *
468 * @since 3.0.0
469 * @return string
470 */
471 protected function get_request_duration() {
472 return $this->request_duration;
473 }
474
475 /**
476 * Get the response handler class name
477 *
478 * @since 3.0.0
479 * @return string
480 */
481 protected function get_response_handler() {
482 return $this->response_handler;
483 }
484
485 /**
486 * Get the response code
487 *
488 * @since 3.0.0
489 * @return string
490 */
491 protected function get_response_code() {
492 return $this->response_code;
493 }
494
495 /**
496 * Get the response message
497 *
498 * @since 3.0.0
499 * @return string
500 */
501 protected function get_response_message() {
502 return $this->response_message;
503 }
504
505 /**
506 * Get the response headers
507 *
508 * @since 3.0.0
509 * @return array
510 */
511 protected function get_response_headers() {
512 return $this->response_headers;
513 }
514
515 /**
516 * Get the raw response body, prior to any parsing or sanitization
517 *
518 * @since 3.0.0
519 * @return string
520 */
521 protected function get_raw_response_body() {
522 return $this->raw_response_body;
523 }
524
525 /**
526 * Get the sanitized response body, provided by the response class
527 * to_string_safe() method
528 *
529 * @since 3.0.0
530 * @return string|null
531 */
532 protected function get_sanitized_response_body() {
533 return is_callable( array( $this->get_response(), 'to_string_safe' ) ) ? $this->get_response()->to_string_safe() : null;
534 }
535
536 /**
537 * Returns the most recent request object.
538 *
539 * @since 3.0.0
540 *
541 * @return API_Request|object the most recent request object
542 */
543 public function get_request() {
544
545 return $this->request;
546 }
547
548 /**
549 * Returns the most recent response object.
550 *
551 * @since 3.0.0
552 *
553 * @return API_Response|object the most recent response object
554 */
555 public function get_response() {
556
557 return $this->response;
558 }
559
560 /**
561 * Get the ID for the API, used primarily to namespace the action name
562 * for broadcasting requests
563 *
564 * @since 3.0.0
565 * @return string
566 */
567 protected function get_api_id() {
568
569 return 'square';
570 }
571
572 /**
573 * Return a new request object
574 *
575 * Child classes must implement this to return an object that implements
576 * API_Request which should be used in the child class API methods
577 * to build the request. The returned API_Request should be passed
578 * to self::perform_request() by your concrete API methods
579 *
580 * @since 3.0.0
581 *
582 * @param array $args optional request arguments
583 * @return API_Request|object
584 */
585 abstract protected function get_new_request( $args = array() );
586
587 /**
588 * Return the plugin class instance associated with this API
589 *
590 * Child classes must implement this to return their plugin class instance
591 *
592 * This is used for defining the plugin ID used in filter names, as well
593 * as the plugin name used for the default user agent.
594 *
595 * @since 3.0.0
596 *
597 * @return WooCommerce\Square\Framework\Plugin
598 */
599 abstract protected function get_plugin();
600
601 /**
602 * Set the response handler class name. This class will be instantiated
603 * to parse the response for the request.
604 *
605 * @since 3.0.0
606 *
607 * @param string $handler handle class name
608 */
609 protected function set_response_handler( $handler ) {
610 $this->response_handler = $handler;
611 }
612
613 /**
614 * Maybe force TLS v1.2 requests.
615 *
616 * @since 3.0.0
617 *
618 * @param resource $handle the cURL handle returned by curl_init() (passed by reference)
619 * @param array $r the HTTP request arguments
620 * @param $url string the request URL
621 */
622 public function set_tls_1_2_request( $handle, $r, $url ) {
623
624 if ( ! Square_Helper::str_starts_with( $url, 'https://' ) ) {
625 return;
626 }
627
628 curl_setopt( $handle, CURLOPT_SSLVERSION, 6 );
629 }
630
631 /**
632 * Determine if TLS v1.2 is required for API requests.
633 *
634 * Subclasses should override this to return true if TLS v1.2 is required.
635 *
636 * @since 3.0.0
637 * @return bool
638 */
639 public function require_tls_1_2() {
640 return false;
641 }
642
643 /**
644 * Determines if TLS 1.2 is available.
645 *
646 * @since 3.0.0
647 *
648 * @return bool
649 */
650 public function is_tls_1_2_available() {
651
652 // assume availability to avoid notices for unknown SSL types
653 $is_available = true;
654
655 // check the cURL version if installed
656 if ( is_callable( 'curl_version' ) ) {
657
658 $versions = curl_version();
659
660 // cURL 7.34.0 is considered the minimum version that supports TLS 1.2
661 if ( version_compare( $versions['version'], '7.34.0', '<' ) ) {
662 $is_available = false;
663 }
664 }
665
666 /**
667 * Filters whether TLS 1.2 is available.
668 *
669 * @since 3.0.0
670 *
671 * @param bool $is_available whether TLS 1.2 is available
672 * @param Base $api API class instance
673 */
674 return apply_filters( 'wc_square_api_is_tls_1_2_available', $is_available, $this );
675 }
676 }
677