Skip to content
Snippets Groups Projects
Commit f053972f authored by Lucas García's avatar Lucas García
Browse files

Merge branch 'feat/multi-backend' into 'main'

Multi backend plugin

See merge request !3
parents 6e843538 aebfbd97
No related branches found
No related tags found
1 merge request!3Multi backend plugin
<?php
namespace WPCT_HTTP;
use Exception;
/**
* HTTP Backend
*
* @since 3.0.0
*/
class Http_Backend
{
/**
* Handle backend data.
*
* @since 3.0.0
*/
private $data = null;
/**
* Store backend data
*
* @since 3.0.0
*/
public function __construct($name)
{
$this->data = $this->get_backend($name);
if (!$this->data) {
throw new Exception("Http backend error: Unkown backend with name {$name}");
}
}
/**
* Backend data getter.
*
* @since 3.0.0
*/
private function get_backend($name)
{
$backends = Settings::get_setting('wpct-http-bridge', 'general', 'backends');
foreach ($backends as $backend) {
if ($backend['name'] === $name) {
return $backend;
}
}
return null;
}
/**
* Intercept class gets and lookup on backend data.
*
* @since 3.0.0
*/
public function __get($attr)
{
if (isset($this->data[$attr])) {
return $this->data[$attr];
}
return null;
}
/**
* Get backend absolute URL.
*
* @since 3.0.0
*
* @param string $path URL relative path.
* @return string $url Absolute URL.
*/
public function get_endpoint_url($path)
{
$url_data = parse_url($path);
if (isset($url_data['scheme'])) {
return $path;
}
$base_url = $this->base_url;
return preg_replace('/\/$/', '', $base_url) . '/' . preg_replace('/^\//', '', $path);
}
/**
* Get backend default headers.
*
* @since 3.0.0
*
* @return array $headers Backend headers.
*/
public function get_headers()
{
$headers = [];
foreach ($this->headers as $header) {
$headers[strtolower(trim($header['name']))] = trim($header['value']);
}
return $headers;
}
}
// Get new backend instance.
add_filter('wpct_http_backend', function ($null, $name) {
return new Http_Backend($name);
}, 10, 2);
......@@ -3,15 +3,84 @@
namespace WPCT_HTTP;
use WP_Error;
use Exception;
require_once 'class-multipart.php';
/**
* HTTP Client.
*
* @since 3.0.0
*/
class Http_Client
{
public static function get($url, $headers = [])
/**
* Default request arguments.
*
* @since 3.0.0
*
* @var array $args_defaults.
*/
private const args_defaults = [
'params' => [],
'data' => [],
'headers' => [
'connection' => 'keep-alive',
'accept' => 'application/json',
'content-type' => 'application/json',
],
'files' => []
];
/**
* Fills request arguments with defaults.
*
* @since 3.0.0
*
* @param array $args Request arguments.
* @return array $args Request arguments with defaults.
*/
public static function req_args($args = [])
{
$url = Http_Client::get_endpoint_url($url);
$args = array_merge(Http_Client::args_defaults, (array) $args);
$args['headers'] = Http_Client::req_headers($args['headers']);
return $args;
}
/**
* Add query params to URLs.
*
* @since 3.0.0
*
* @param string $url Target URL.
* @param array $params Associative array with query params.
* @return string URL with query params.
*/
private static function add_query_str($url, $params)
{
$parsed = parse_url($url);
if (isset($parsed['query'])) {
parse_str($parsed['query'], $query);
$params = array_merge($query, $params);
$url = preg_replace('/?.*$/', '', $url);
}
return $url . '?'. http_build_query($params);
}
/**
* Performs a GET request.
*
* @since 3.0.0
*
* @param string $url Target url.
* @param array $params Associative array with query params.
* @param array $headers Associative array with HTTP headers.
* @return array|WP_Error $response Response data or error.
*/
public static function get($url, $params = [], $headers = [])
{
$url = Http_Client::add_query_str($url, $params);
$args = [
'method' => 'GET',
'headers' => Http_Client::req_headers($headers, 'GET', $url)
......@@ -19,25 +88,47 @@ class Http_Client
return Http_Client::do_request($url, $args);
}
public static function post($url, $data = [], $headers = [])
/**
* Performs a POST request. Default content type is application/json, any other
* mimetype should be encoded before and passed in as string.
* If $files is defined and is array, content type switches to multipart/form-data.
*
* @since 3.0.0
*
* @param string $url Target URL.
* @param array $data Associative array with the request payload.
* @param array $headers Associative array with HTTP headers.
* @param array $files Associative array with filename and paths.
* @return array|WP_Error $response Response data or error.
*/
public static function post($url, $data, $headers, $files = null)
{
$url = Http_Client::get_endpoint_url($url);
$payload = json_encode($data);
$headers = Http_Client::req_headers($headers, 'POST', $url);
$headers['Content-Type'] = 'application/json';
$args = [
if (is_array($files) && !empty($files)) {
return Http_Client::post_multipart($url, $data, $files, $headers);
}
$body = is_string($data) ? $data : json_encode($data);
return Http_Client::do_request($url, [
'method' => 'POST',
'headers' => $headers,
'body' => $payload
];
return Http_Client::do_request($url, $args);
'body' => $body
]);
}
public static function post_multipart($url, $data = [], $files = [], $headers = [])
/**
* Performs a POST request with multipart/form-data content type payload.
*
* @since 3.0.0
*
* @param string $url Target URL.
* @param array $data Associative array with the request payload.
* @param array $files Associative array with filename and paths.
* @param array $headers Associative array with HTTP headers.
* @return array|WP_Error $response Response data or error.
*/
public static function post_multipart($url, $data, $files, $headers)
{
$url = Http_Client::get_endpoint_url($url);
$multipart = new Multipart();
$multipart->add_array($data);
foreach ($files as $name => $path) {
......@@ -52,35 +143,59 @@ class Http_Client
$multipart->add_file($name, $path, $filetype['type']);
}
$headers = Http_Client::req_headers($headers, 'POST', $url);
$headers['Content-Type'] = $multipart->content_type();
$args = [
$headers['content-type'] = $multipart->content_type();
return Http_Client::do_request($url, [
'method' => 'POST',
'headers' => $headers,
'body' => $multipart->data()
];
return Http_Client::do_request($url, $args);
]);
}
public static function put($url, $data = [], $headers = [])
/**
* Performs a PUT request. Default content type is application/json, any other
* mimetype should be encoded before and passed in as string.
* If $files is defined and is array, content type switches to multipart/form-data.
*
* @since 3.0.0
*
* @param string $url Target URL.
* @param array $data Associative array with the request payload.
* @param array $headers Associative array with HTTP headers.
* @param array $files Associative array with filename and paths.
* @return array|WP_Error $response Response data or error.
*/
public static function put($url, $data, $headers, $files = null)
{
$url = Http_Client::get_endpoint_url($url);
$payload = json_encode($data);
if (is_array($files) && !empty($files)) {
return Http_Client::put_multipart($url, $data, $files, $headers);
}
$payload = is_string($data) ? $data : json_encode($data);
$headers = Http_Client::req_headers($headers, 'PUT', $url);
$headers['Content-Type'] = 'application/json';
$args = [
return Http_Client::do_request($url, [
'method' => 'PUT',
'headers' => $headers,
'body' => $payload
];
return Http_Client::do_request($url, $args);
]);
}
public static function put_multipart($url, $data = [], $files = [], $headers = [])
/**
* Performs a PUT request with multipart/form-data content type payload.
*
* @since 3.0.0
*
* @param string $url Target URL.
* @param array $data Associative array with the request payload.
* @param array $files Associative array with filename and paths.
* @param array $headers Associative array with HTTP headers.
* @return array|WP_Error $response Response data or error.
*/
private static function put_multipart($url, $data, $files, $headers)
{
$url = Http_Client::get_endpoint_url($url);
$multipart = new Multipart();
$multipart->add_array($data);
foreach ($files as $name => $path) {
......@@ -92,31 +207,70 @@ class Http_Client
$multipart->add_file($name, $path, $filetype['type']);
}
$headers = Http_Client::req_headers($headers, 'PUT', $url);
$headers['Content-Type'] = $multipart->content_type();
$args = [
$headers['content-type'] = $multipart->content_type();
return Http_Client::do_request($url, [
'method' => 'PUT',
'headers' => $headers,
'body' => $multipart->data()
];
return Http_Client::do_request($url, $args);
]);
}
public static function delete($url, $headers = [])
/**
* Performs a DETELE request.
*
* @since 3.0.0
*
* @param string $url Target url.
* @param array $params Associative array with query params.
* @param array $headers Associative array with HTTP headers.
* @return array|WP_Error $response Response data or error.
*/
public static function delete($url, $params, $headers)
{
$url = Http_Client::get_endpoint_url($url);
$args = [
$url = Http_Client::add_query_str($url, $params);
return Http_Client::do_request($url, [
'method' => 'DELETE',
'headers' => Http_Client::req_headers($headers, 'DELETE', $url)
];
return Http_Client::do_request($url, $args);
]);
}
/**
* Performs a request on top of WP_Http client
*
* @since 3.0.0
*
* @param string $url Target URL.
* @param array $args WP_Http::request arguments.
* @return array|WP_Error $response Response data or error.
*/
private static function do_request($url, $args)
{
$request = ['url' => $url, 'args' => $args];
$response = wp_remote_request($url, $args);
global $wp_version;
$args = array_merge(
[
'method' => 'POST',
'timeout' => 5,
'redirection' => 5,
'httpversion' => '1.0',
'user-agent' => 'WordPress/' . $wp_version . '; ' . home_url(),
'blocking' => true,
'headers' => [],
'cookies' => [],
'body' => null,
'compress' => false,
'decompress' => true,
'sslverify' => true,
'stream' => false,
'filename' => null
],
$args
);
$request = apply_filters('wpct_http_request_args', ['url' => $url, 'args' => $args]);
$response = wp_remote_request($request['url'], $request['args']);
if (is_wp_error($response)) {
$response->add_data(['request' => $request]);
return $response;
......@@ -136,37 +290,31 @@ class Http_Client
return $response;
}
private static function get_endpoint_url($url)
{
$url_data = parse_url($url);
if (isset($url_data['scheme'])) {
return $url;
} else {
$base_url = Http_Client::option_getter('wpct-http-bridge_general', 'base_url');
return preg_replace('/\/$/', '', $base_url . '/' . preg_replace('/^\//', '', $url));
}
}
private static function req_headers($headers, $method = null, $url = null)
/**
* Add default headers to an array.
*
* @since 3.0.0
*
* @param array $headers Associative array with HTTP headers.
* @return array $headers Associative array with HTTP headers.
*/
private static function req_headers($headers)
{
$headers['Connection'] = 'keep-alive';
$headers['Accept'] = 'application/json';
$headers['API-KEY'] = Http_Client::option_getter('wpct-http-bridge_general', 'api_key');
$headers['Accept-Language'] = Http_Client::get_locale();
return apply_filters('wpct_http_headers', $headers, $method, $url);
}
private static function option_getter($setting, $option)
{
$setting = get_option($setting);
if (!$setting) {
throw new Exception('Wpct Http Bridge: You should configure base url on plugin settings');
}
return isset($setting[$option]) ? $setting[$option] : null;
return array_merge([
'host' => $_SERVER['HTTP_HOST'],
'referer' => $_SERVER['HTTP_REFERER'],
'accept-language' => Http_Client::get_locale(),
'content-type' => 'application/json',
], (array) $headers);
}
/**
* Use wpct-i18n to get the current language locale.
*
* @since 2.0.4
*
* @return string ISO-2 locale representation.
*/
private static function get_locale()
{
$locale = apply_filters('wpct_i18n_current_language', null, 'locale');
......@@ -178,36 +326,62 @@ class Http_Client
}
}
// Performs a get request to Odoo
function wpct_http_get($url, $headers = [])
{
return Http_Client::get($url, $headers);
}
// Performs a post request to Odoo
function wpct_http_post($url, $data = [], $headers = [])
{
return Http_Client::post($url, $data, $headers);
}
function wpct_hn_post_multipart($url, $data = [], $files = [], $headers = [])
/**
* Public function to perform a GET requests.
*
* @since 3.0.0
*
* @param string $url Target URL.
* @param array $args Associative array with request arguments.
* @return array|WP_Error $response Response data or error.
*/
function wpct_http_get($url, $args = [])
{
return Http_Client::post_multipart($url, $data, $files, $headers);
['params' => $params, 'headers' => $headers ] = Http_Client::req_args($args);
return Http_Client::get($url, $params, $headers);
}
// Performs a put request to Odoo
function wpct_http_put($url, $data = [], $headers = [])
/**
* Public function to perform a POST requests.
*
* @since 3.0.0
*
* @param string $url Target URL.
* @param array $args Associative array with request arguments.
* @return array|WP_Error $response Response data or error.
*/
function wpct_http_post($url, $args = [])
{
return Http_Client::put($url, $data, $headers);
['data' => $data, 'headers' => $headers, 'files' => $files ] = Http_Client::req_args($args);
return Http_Client::post($url, $data, $headers, $files);
}
function wpct_http_put_multipart($url, $data = [], $files = [], $headers = [])
/**
* Public function to perform a PUT requests.
*
* @since 3.0.0
*
* @param string $url Target URL.
* @param array $args Associative array with request arguments.
* @return array|WP_Error $response Response data or error.
*/
function wpct_http_put($url, $arguments = [])
{
return Http_Client::put_multipart($url, $data, $files, $headers);
['data' => $data, 'headers' => $headers, 'files' => $files ] = Http_Client::req_args($arguments);
return Http_Client::put($url, $data, $headers, $files);
}
// Performs a delete request to Odoo
function wpct_http_delete($url, $headers = [])
/**
* Public function to perform a DELETE requests.
*
* @since 3.0.0
*
* @param string $url Target URL.
* @param array $args Associative array with request arguments.
* @return array|WP_Error $response Response data or error.
*/
function wpct_http_delete($url, $args = [])
{
return Http_Client::delete($url, $headers);
['params' => $params, 'headers' => $headers ] = Http_Client::req_args($args);
return Http_Client::delete($url, $params, $headers);
}
......@@ -4,10 +4,28 @@ namespace WPCT_HTTP;
use Exception;
/**
* JWT REST API authentication
*
* @since 2.0.0
*/
class JWT
{
/**
* Handle auth secret.
*
* @since 2.0.0
*/
private static $key = WPCT_HTTP_AUTH_SECRET;
/**
* Get encoded payload token.
*
* @since 2.0.0
*
* @param array $payload Token payload.
* @return string $token JWT encoded token.
*/
public function encode($payload)
{
$header = json_encode([
......@@ -24,6 +42,14 @@ class JWT
return $header . '.' . $payload . '.' . $signature;
}
/**
* Get decoded token payload.
*
* @since 2.0.0
*
* @param string $token JWT encoded token.
* @return array $payload Token payload.
*/
public function decode($token)
{
if (
......@@ -53,12 +79,27 @@ class JWT
return $payload;
}
/**
* URL conformant base64 encoder.
*
* @since 2.0.0
*
* @param string $text Source string.
* @return string $base64 Encoded string.
*/
private function base64URLEncode($text)
{
return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($text));
}
/**
* URL conformant base64 decoder.
*
* @since 2.0.0
*
* @param string $base64 Encoded string.
* @return string $text Decoded string.
*/
private function base64URLDecode($text)
{
return base64_decode(
......
......@@ -4,10 +4,27 @@ namespace WPCT_HTTP;
use WPCT_ABSTRACT\Menu as BaseMenu;
/**
* Plugin menu class.
*
* @since 1.0.0
*/
class Menu extends BaseMenu
{
/**
* Handle plugin settings class name.
*
* @since 3.0.0
*
* @var string $settings_class Settings class name.
*/
protected static $settings_class = '\WPCT_HTTP\Settings';
/**
* Render plugin menu page.
*
* @since 3.0.0
*/
protected function render_page($echo = true)
{
ob_start();
......
......@@ -12,16 +12,40 @@ class Settings extends BaseSettings
$this->register_setting(
'general',
[
'base_url' => [
'type' => 'string'
'backends' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'name' => ['type' => 'string'],
'base_url' => ['type' => 'string'],
'headers' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'name' => ['type' => 'string'],
'value' => ['type' => 'string'],
],
],
],
],
],
],
'api_key' => [
'type' => 'string'
]
],
[
'base_url' => 'http://' . $url['host'],
'api_key' => 'backend-api-key'
'backends' => [
[
'name' => 'ERP',
'base_url' => 'https://erp.' . $url['host'],
'headers' => [
[
'name' => 'Authorization',
'value' => 'Bearer <backend-api-token>'
]
],
],
],
],
);
}
......
......@@ -35,6 +35,7 @@ if (!class_exists('\WPCT_HTTP\Wpct_Http_Bridge')) :
require_once 'includes/class-menu.php';
require_once 'includes/class-settings.php';
require_once 'includes/class-http-client.php';
require_once 'includes/class-http-backend.php';
require_once 'includes/class-jwt.php';
require_once 'includes/class-rest-controller.php';
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment