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