From c52ec6f7b256b69bc0a6c6c9b9bc0406d292ad83 Mon Sep 17 00:00:00 2001 From: vendidero Date: Thu, 12 Dec 2024 16:38:37 +0100 Subject: [PATCH] Added REST API base classes to be used in shipping provider integrations. --- src/API/Auth/Auth.php | 40 +++++++ src/API/Auth/Basic.php | 35 ++++++ src/API/Auth/OAuth.php | 26 ++++ src/API/REST.php | 230 ++++++++++++++++++++++++++++++++++++ src/API/Response.php | 89 ++++++++++++++ src/Interfaces/RESTAuth.php | 37 ++++++ src/Order.php | 2 +- 7 files changed, 458 insertions(+), 1 deletion(-) create mode 100644 src/API/Auth/Auth.php create mode 100644 src/API/Auth/Basic.php create mode 100644 src/API/Auth/OAuth.php create mode 100644 src/API/REST.php create mode 100644 src/API/Response.php create mode 100644 src/Interfaces/RESTAuth.php diff --git a/src/API/Auth/Auth.php b/src/API/Auth/Auth.php new file mode 100644 index 0000000..8d4cbcb --- /dev/null +++ b/src/API/Auth/Auth.php @@ -0,0 +1,40 @@ +api = $api; + } + + /** + * @return REST + */ + public function get_api() { + return $this->api; + } + + public function is_unauthenticated_response( $code ) { + return in_array( (int) $code, array( 401, 403 ), true ); + } + + protected function get_request_url( $endpoint = '', $query_args = array() ) { + if ( ! strstr( $endpoint, 'http://' ) && ! strstr( $endpoint, 'https://' ) ) { + $endpoint = trailingslashit( $this->get_url() ) . $endpoint; + } + + return add_query_arg( $query_args, $endpoint ); + } +} diff --git a/src/API/Auth/Basic.php b/src/API/Auth/Basic.php new file mode 100644 index 0000000..f9ccf84 --- /dev/null +++ b/src/API/Auth/Basic.php @@ -0,0 +1,35 @@ +has_auth() ) { + $headers['Authorization'] = 'Basic ' . base64_encode( $this->get_username() . ':' . $this->get_password() ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + } + + return $headers; + } + + public function has_auth() { + return ! empty( $this->get_username() ) && ! empty( $this->get_password() ); + } + + public function revoke() { + } +} diff --git a/src/API/Auth/OAuth.php b/src/API/Auth/OAuth.php new file mode 100644 index 0000000..738660a --- /dev/null +++ b/src/API/Auth/OAuth.php @@ -0,0 +1,26 @@ +has_auth() ) { + $headers['Authorization'] = 'Bearer ' . $this->get_access_token(); + } + + return $headers; + } + + public function has_auth() { + return $this->get_access_token() ? true : false; + } +} diff --git a/src/API/REST.php b/src/API/REST.php new file mode 100644 index 0000000..06e4cff --- /dev/null +++ b/src/API/REST.php @@ -0,0 +1,230 @@ +auth ) ) { + $this->auth = $this->get_auth_instance(); + } + + return $this->auth; + } + + /** + * @return bool + */ + protected function is_auth_request( $url ) { + return strstr( $url, $this->get_auth()->get_url() ); + } + + protected function get_timeout( $request_type = 'GET' ) { + return 'GET' === $request_type ? 30 : 100; + } + + protected function get_content_type() { + return 'application/json'; + } + + protected function maybe_encode_body( $body_args, $content_type = '' ) { + if ( empty( $content_type ) ) { + $content_type = $this->get_content_type(); + } + + if ( 'application/json' === $content_type ) { + return wp_json_encode( $body_args, JSON_PRETTY_PRINT ); + } elseif ( 'application/x-www-form-urlencoded' === $content_type ) { + return http_build_query( $body_args ); + } + + return $body_args; + } + + /** + * @param $url + * @param $type + * @param $body_args + * @param $headers + * + * @return Response + */ + protected function get_response( $url, $type = 'GET', $body_args = array(), $headers = array() ) { + $headers = $this->get_headers( $headers ); + $response = false; + $is_auth_request = false; + + if ( $this->is_auth_request( $url ) ) { + $is_auth_request = true; + } elseif ( ! $this->get_auth()->has_auth() ) { + $auth_response = $this->get_auth()->auth(); + } + + if ( 'GET' === $type ) { + $response = wp_remote_get( + esc_url_raw( $url ), + array( + 'headers' => $headers, + 'timeout' => $this->get_timeout( $type ), + ) + ); + } elseif ( 'POST' === $type ) { + $response = wp_remote_post( + esc_url_raw( $url ), + array( + 'headers' => $headers, + 'timeout' => $this->get_timeout( $type ), + 'body' => $this->maybe_encode_body( $body_args, $headers['Content-Type'] ), + ) + ); + } elseif ( 'PUT' === $type ) { + $response = wp_remote_request( + esc_url_raw( $url ), + array( + 'headers' => $headers, + 'timeout' => $this->get_timeout( $type ), + 'body' => $this->maybe_encode_body( $body_args, $headers['Content-Type'] ), + 'method' => 'PUT', + ) + ); + } elseif ( 'DELETE' === $type ) { + $response = wp_remote_request( + esc_url_raw( $url ), + array( + 'headers' => $headers, + 'timeout' => $this->get_timeout( $type ), + 'body' => $this->maybe_encode_body( $body_args, $headers['Content-Type'] ), + 'method' => 'DELETE', + ) + ); + } + + if ( false !== $response ) { + if ( is_wp_error( $response ) ) { + return new Response( 500, array(), array(), $response ); + } + + $response_code = wp_remote_retrieve_response_code( $response ); + $response_body = wp_remote_retrieve_body( $response ); + $response_headers = wp_remote_retrieve_headers( $response ); + $response_obj = new Response( $response_code, $response_body, $response_headers ); + + if ( $response_obj->get_code() >= 300 ) { + if ( ! $is_auth_request && ! isset( $body_args['is_retry'] ) && $this->get_auth()->is_unauthenticated_response( $response_obj->get_code() ) ) { + $this->get_auth()->revoke(); + $body_args['is_retry'] = true; + + return $this->get_response( $url, $type, $body_args, $headers ); + } + + $response = $this->parse_error( $response_obj ); + + return $response; + } + + return new Response( $response_code, $response_body, $response_headers ); + } + + return new Response( 500, array(), array(), new ShipmentError( 'rest-error', sprintf( _x( 'Error while trying to perform REST request to %s', 'shipments', 'woocommerce-germanized-shipments' ), $url ) ) ); + } + + /** + * @param Response $response + * + * @return Response + */ + protected function parse_error( $response ) { + $error = new ShipmentError(); + $body = $response->get_body(); + if ( isset( $body['message'] ) ) { + $error->add( $response->get_code(), wp_kses_post( $body['message'] ) ); + } else { + $error->add( $response->get_code(), _x( 'There was an unknown error calling the API.', 'shipments', 'woocommerce-germanized-shipments' ) ); + } + + $response->set_error( $error ); + + return $response; + } + + protected function get_request_url( $endpoint = '', $query_args = array() ) { + if ( ! strstr( $endpoint, 'http://' ) && ! strstr( $endpoint, 'https://' ) ) { + $endpoint = trailingslashit( $this->get_url() ) . $endpoint; + } + + return add_query_arg( $query_args, $endpoint ); + } + + /** + * @param string $endpoint + * @param array $query_args + * + * @return Response + */ + public function get( $endpoint = '', $query_args = array(), $header = array() ) { + return $this->get_response( $this->get_request_url( $endpoint, $query_args ), 'GET', array(), $header ); + } + + /** + * @param string $endpoint + * @param array $query_args + * + * @return Response + */ + public function post( $endpoint = '', $body_args = array(), $header = array() ) { + return $this->get_response( $this->get_request_url( $endpoint ), 'POST', $body_args, $header ); + } + + /** + * @param string $endpoint + * @param array $query_args + * + * @return Response + */ + public function put( $endpoint = '', $body_args = array(), $header = array() ) { + return $this->get_response( $this->get_request_url( $endpoint ), 'PUT', $body_args, $header ); + } + + /** + * @param string $endpoint + * @param array $query_args + * + * @return Response + */ + public function delete( $endpoint = '', $body_args = array(), $header = array() ) { + return $this->get_response( $this->get_request_url( $endpoint ), 'DELETE', $body_args, $header ); + } + + protected function get_headers( $headers = array() ) { + $headers = wp_parse_args( + $headers, + array( + 'Content-Type' => $this->get_content_type(), + 'Accept' => 'application/json', + 'User-Agent' => 'Germanized/' . Package::get_version(), + ) + ); + + $headers = array_replace_recursive( $headers, $this->get_auth()->get_headers() ); + + return $headers; + } +} diff --git a/src/API/Response.php b/src/API/Response.php new file mode 100644 index 0000000..7314a0d --- /dev/null +++ b/src/API/Response.php @@ -0,0 +1,89 @@ +code = absint( $code ); + $this->body = $body; + $this->headers = (array) $headers; + + $this->set_error( $error ); + } + + /** + * @param \WP_Error|ShipmentError|null $error + * + * @return void + */ + public function set_error( $error ) { + if ( is_wp_error( $error ) ) { + $error = ShipmentError::from_wp_error( $error ); + } + + $this->error = $error; + } + + public function get_body_raw() { + return $this->body; + } + + public function get_body() { + return json_decode( $this->get_body_raw(), true ); + } + + public function get_headers() { + return $this->headers; + } + + public function get( $prop ) { + $body = $this->get_body(); + + return isset( $body[ $prop ] ) ? $body[ $prop ] : null; + } + + public function get_code() { + return $this->code; + } + + public function is_error() { + return $this->get_error() && ! $this->is_soft_error() ? true : false; + } + + public function is_soft_error() { + return $this->get_error() ? $this->get_error()->is_soft_error() : false; + } + + /** + * @return ShipmentError|null + */ + public function get_soft_error() { + return $this->is_soft_error() ? $this->get_error() : null; + } + + /** + * @return null|ShipmentError + */ + public function get_error() { + return $this->error; + } +} diff --git a/src/Interfaces/RESTAuth.php b/src/Interfaces/RESTAuth.php new file mode 100644 index 0000000..55a43d1 --- /dev/null +++ b/src/Interfaces/RESTAuth.php @@ -0,0 +1,37 @@ +get_method_id(), $pickup_methods, true ) ) {