538 lines
17 KiB
PHP
538 lines
17 KiB
PHP
<?php
|
|
|
|
namespace classes;
|
|
|
|
require_once WD . '/modules/fxratesapi/fxratesapi_c.php';
|
|
/** Actions */
|
|
require_once WD . '/modules/fxratesapi/actions/convert_rate_a.php';
|
|
|
|
use Exception;
|
|
use fxratesapi\actions\convert_rate_a;
|
|
use fxratesapi\fxratesapi_c;
|
|
use interfaces\fxratesapi_i;
|
|
use objects\fxratesapi_conversion_rates_o;
|
|
use Throwable;
|
|
|
|
class fxratesapi implements fxratesapi_i
|
|
{
|
|
/**
|
|
* Configuration of the fxratesapi module
|
|
* @var fxratesapi_c
|
|
*/
|
|
public fxratesapi_c $config;
|
|
|
|
/**
|
|
* API URL
|
|
* @var string
|
|
*/
|
|
private string $api_url = 'https://api.fxratesapi.com/';
|
|
|
|
/**
|
|
* ACTION: CONVERT RATE
|
|
* @see convert_rate_a
|
|
* @notation This action is when a currency conversion request is made, and logs the request in the database
|
|
* @var convert_rate_a
|
|
*/
|
|
private convert_rate_a $convert_rate;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->config = new fxratesapi_c();
|
|
/** Actions */
|
|
$this->convert_rate = new convert_rate_a();
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
function isCached(string $base, string $target): bool
|
|
{
|
|
// Check if the base currency is valid
|
|
self::requireValidCurrency($base);
|
|
// Check if the target currency is valid
|
|
self::requireValidCurrency($base);
|
|
// Check if the request is cached
|
|
$fxratesapi_lookups = new fxratesapi_conversion_rates_o();
|
|
return $fxratesapi_lookups->isCached($base, $target);
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
function requireValidCurrency(string $currency): void
|
|
{
|
|
// Check if the currency is valid
|
|
if (!preg_match('/^[A-Z]{3}$/', $currency)) {
|
|
throw new Exception('Invalid currency');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
function getCachedResponse(string $base, string $target): object
|
|
{
|
|
// Check if the base and target currencies are valid
|
|
self::requireValidCurrency($base);
|
|
self::requireValidCurrency($target);
|
|
|
|
// Get the cached response from the fxratesapi request log
|
|
$fxratesapi_lookups = new fxratesapi_conversion_rates_o();
|
|
$fxratesapi_lookups->getCachedResponse($base, $target);
|
|
return $fxratesapi_lookups;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
* @throws Exception If the base or target currency is invalid
|
|
* @throws Exception If the endpoint is invalid
|
|
* @throws Exception If the response is invalid
|
|
* @throws Exception If the response is not valid JSON
|
|
* @throws Exception If the request fails
|
|
*/
|
|
function getConversionRate(string $base, string $target, string $endpoint, array $data): object
|
|
{
|
|
// Check if the base and target currencies are valid
|
|
self::requireValidCurrency($base);
|
|
self::requireValidCurrency($target);
|
|
|
|
// Send the request
|
|
// Return the conversion rate
|
|
return self::sendRequest($base, $target, $endpoint, $data);
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
* @throws Exception If the module is not enabled, the license plate is invalid, the daily limit is exceeded, or the secret key is invalid
|
|
*/
|
|
function sendRequest(
|
|
string $base,
|
|
string $target,
|
|
string $endpoint,
|
|
array $data = [],
|
|
string $method = 'GET'
|
|
): object
|
|
{
|
|
// Validate the module is enabled
|
|
self::requireModuleEnabled();
|
|
// Validate the base and target currencies
|
|
self::requireValidCurrency($base);
|
|
self::requireValidCurrency($target);
|
|
// Validate the secret key
|
|
self::requireValidSecretKey();
|
|
// Reserve quota for the outbound provider call. Cached conversion reads return before this point.
|
|
$this->reserveRateFetchQuota($base, $target, $endpoint, $method);
|
|
// Send the request
|
|
$response = match ($method) {
|
|
'GET' => self::sendGetRequest($base, $target, $endpoint, $data),
|
|
'POST' => self::sendPostRequest($base, $target, $endpoint, $data),
|
|
'PUT' => self::sendPutRequest($base, $target, $endpoint, $data),
|
|
'DELETE' => self::sendDeleteRequest($base, $target, $endpoint, $data),
|
|
default => self::exception(
|
|
$base,
|
|
$target,
|
|
[
|
|
'method' => $method,
|
|
'endpoint' => $endpoint,
|
|
'data' => $data,
|
|
'license_plate' => $base,
|
|
'response' => null,
|
|
'status_code' => 400,
|
|
'error' => 'Invalid request method',
|
|
],
|
|
500
|
|
),
|
|
};
|
|
// Add the usage to the request log
|
|
$this->convert_rate->convert_rate($base, $target, $response, 200);
|
|
// Add the request to the log
|
|
self::addRequestToLog($base, $target, $endpoint, $response);
|
|
// Return the response
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
function requireModuleEnabled(): void
|
|
{
|
|
// Check if the module is enabled
|
|
if (!$this->config->enabled->isTrue()) {
|
|
throw new Exception('The fxratesapi module is not enabled');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
function requireDailyLimitNotExceeded(): void
|
|
{
|
|
// Check if the daily limit is exceeded
|
|
if ($this->getDailyRequestCounter() >= (int)$this->config->daily_limit->getVariableValue()) {
|
|
throw new Exception('Daily limit exceeded');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
function getDailyRequestCounter(): int
|
|
{
|
|
try {
|
|
return (new module_usage_service())->currentUsedQuantity('fxratesapi', 'rate_fetch_calls');
|
|
} catch (Throwable) {
|
|
}
|
|
|
|
// Count the rows from the fxratesapi request log that was made today
|
|
$fxratesapi_lookups = new fxratesapi_conversion_rates_o();
|
|
$fxratesapi_lookups->getTodayCount();
|
|
return $fxratesapi_lookups->getTodayCount();
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function reserveRateFetchQuota(string $base, string $target, string $endpoint, string $method): void
|
|
{
|
|
(new module_usage_service())->reserveOrFail('fxratesapi', 'rate_fetch_calls', 1, [
|
|
'base' => $base,
|
|
'target' => $target,
|
|
'endpoint' => $endpoint,
|
|
'method' => strtoupper($method),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
function requireValidSecretKey(): void
|
|
{
|
|
// Check if the secret key is valid
|
|
if ($this->config->secret_key->getVariableValue() === null) {
|
|
self::exception(
|
|
'',
|
|
'',
|
|
[
|
|
'status_code' => 500,
|
|
'error' => 'Invalid secret key defined in the config (fxratesapi_secret_key_c)',
|
|
],
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
function exception(string $base, string $target, array $data, int $status_code = 500): object
|
|
{
|
|
// Check if the response is valid JSON
|
|
if (!json_decode($data['response'])) {
|
|
throw new Exception('Invalid response');
|
|
}
|
|
// Add the request to the log
|
|
$this->convert_rate->convert_rate($base, $target, $data, $status_code);
|
|
return throw new Exception($data['error'] ?? 'An error occurred while processing the request, in ' . $this->config->getModuleName() . ' module');
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
* @throws Exception
|
|
*/
|
|
function sendGetRequest(string $base, string $target, string $endpoint, array $data): object
|
|
{
|
|
// Check if the base and target currencies are valid
|
|
self::requireValidCurrency($base);
|
|
self::requireValidCurrency($target);
|
|
|
|
// Add the base and target currencies to the data array
|
|
$data['base'] = $base;
|
|
|
|
// Build the query string if any data is provided
|
|
$query_string = '';
|
|
if (!empty($data)) {
|
|
$query_string = '?' . http_build_query($data);
|
|
}
|
|
|
|
// Initialize cURL session
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_URL, $this->api_url . $endpoint . '/' . $query_string);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
|
|
// Set HTTP headers including the X-AUTH-TOKEN
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
'X-AUTH-TOKEN: ' . $this->config->secret_key->getVariableValue(),
|
|
]);
|
|
|
|
// Execute the request and handle response
|
|
$output = curl_exec($ch);
|
|
|
|
// Check for errors
|
|
if (curl_errno($ch)) {
|
|
self::exception(
|
|
$base,
|
|
$target,
|
|
[
|
|
'method' => 'GET',
|
|
'endpoint' => $endpoint,
|
|
'data' => $data,
|
|
'response' => null,
|
|
'status_code' => 500,
|
|
'error' => curl_error($ch),
|
|
],
|
|
500
|
|
);
|
|
}
|
|
|
|
// Get the status code
|
|
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
|
curl_close($ch);
|
|
// Check if the response is valid JSON
|
|
if (!json_decode($output)) {
|
|
$exception_message = match ($status_code) {
|
|
401 => 'Unauthorized',
|
|
404 => 'Not found',
|
|
429 => 'Too many requests',
|
|
default => 'Invalid response',
|
|
};
|
|
self::exception(
|
|
$base,
|
|
$target,
|
|
[
|
|
'method' => 'GET',
|
|
'endpoint' => $endpoint,
|
|
'data' => $data,
|
|
'response' => null,
|
|
'status_code' => $status_code,
|
|
'error' => $exception_message,
|
|
],
|
|
$status_code
|
|
);
|
|
}
|
|
return json_decode($output);
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
* @throws Exception If the request fails
|
|
* @throws Exception If the response is invalid
|
|
* @throws Exception If the response is not valid JSON
|
|
* @throws Exception If the base or target currency is invalid
|
|
* @throws Exception If the endpoint is invalid
|
|
*/
|
|
function sendPostRequest(string $base, string $target, string $endpoint, array $data): object
|
|
{
|
|
// Check if the base and target currencies are valid
|
|
self::requireValidCurrency($base);
|
|
self::requireValidCurrency($target);
|
|
|
|
// Initialize cURL session
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_URL, $this->api_url . $endpoint . '/' . $base . '/' . $target);
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
|
|
// Set HTTP headers including the X-AUTH-TOKEN
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
'X-AUTH-TOKEN: ' . $this->config->secret_key->getVariableValue(),
|
|
]);
|
|
|
|
// Execute the request and handle response
|
|
$output = curl_exec($ch);
|
|
|
|
// Check for errors
|
|
if (curl_errno($ch)) {
|
|
self::exception(
|
|
$base,
|
|
$target,
|
|
[
|
|
'method' => 'POST',
|
|
'endpoint' => $endpoint,
|
|
'data' => $data,
|
|
'response' => null,
|
|
'status_code' => 500,
|
|
'error' => curl_error($ch),
|
|
],
|
|
500
|
|
);
|
|
}
|
|
|
|
// Get the status code
|
|
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
|
curl_close($ch);
|
|
// Check if the response is valid JSON
|
|
if (!json_decode($output)) {
|
|
self::exception(
|
|
$base,
|
|
$target,
|
|
[
|
|
'method' => 'POST',
|
|
'endpoint' => $endpoint,
|
|
'data' => $data,
|
|
'response' => null,
|
|
'status_code' => $status_code,
|
|
'error' => 'Invalid response',
|
|
],
|
|
500
|
|
);
|
|
}
|
|
return json_decode($output);
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
* @throws Exception If the request fails
|
|
* @throws Exception If the response is invalid
|
|
* @throws Exception If the response is not valid JSON
|
|
* @throws Exception If the base or target currency is invalid
|
|
* @throws Exception If the endpoint is invalid
|
|
*/
|
|
function sendPutRequest(string $base, string $target, string $endpoint, array $data): object
|
|
{
|
|
// Check if the base and target currencies are valid
|
|
self::requireValidCurrency($base);
|
|
self::requireValidCurrency($target);
|
|
|
|
// Initialize cURL session
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_URL, $this->api_url . $endpoint . '/' . $base . '/' . $target);
|
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
|
|
// Set HTTP headers including the X-AUTH-TOKEN
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
'X-AUTH-TOKEN: ' . $this->config->secret_key->getVariableValue(),
|
|
]);
|
|
|
|
// Execute the request and handle response
|
|
$output = curl_exec($ch);
|
|
|
|
// Check for errors
|
|
if (curl_errno($ch)) {
|
|
self::exception(
|
|
$base,
|
|
$target,
|
|
[
|
|
'method' => 'PUT',
|
|
'endpoint' => $endpoint,
|
|
'data' => $data,
|
|
'response' => null,
|
|
'status_code' => 500,
|
|
'error' => curl_error($ch),
|
|
],
|
|
500
|
|
);
|
|
}
|
|
|
|
// Get the status code
|
|
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
|
curl_close($ch);
|
|
// Check if the response is valid JSON
|
|
if (!json_decode($output)) {
|
|
self::exception(
|
|
$base,
|
|
$target,
|
|
[
|
|
'method' => 'PUT',
|
|
'endpoint' => $endpoint,
|
|
'data' => $data,
|
|
'response' => null,
|
|
'status_code' => $status_code,
|
|
'error' => 'Invalid response',
|
|
],
|
|
500
|
|
);
|
|
}
|
|
return json_decode($output);
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
* @throws Exception If the request fails
|
|
* @throws Exception If the response is invalid
|
|
* @throws Exception If the response is not valid JSON
|
|
* @throws Exception If the base or target currency is invalid
|
|
* @throws Exception If the endpoint is invalid
|
|
*/
|
|
function sendDeleteRequest(string $base, string $target, string $endpoint, array $data): object
|
|
{
|
|
// Check if the base and target currencies are valid
|
|
self::requireValidCurrency($base);
|
|
self::requireValidCurrency($target);
|
|
|
|
// Initialize cURL session
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_URL, $this->api_url . $endpoint . '/' . $base . '/' . $target);
|
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
|
|
// Set HTTP headers including the X-AUTH-TOKEN
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
'X-AUTH-TOKEN: ' . $this->config->secret_key->getVariableValue(),
|
|
]);
|
|
|
|
// Execute the request and handle response
|
|
$output = curl_exec($ch);
|
|
|
|
// Check for errors
|
|
if (curl_errno($ch)) {
|
|
self::exception(
|
|
$base,
|
|
$target,
|
|
[
|
|
'method' => 'DELETE',
|
|
'endpoint' => $endpoint,
|
|
'data' => $data,
|
|
'response' => null,
|
|
'status_code' => 500,
|
|
'error' => curl_error($ch),
|
|
],
|
|
500
|
|
);
|
|
}
|
|
|
|
// Get the status code
|
|
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
|
curl_close($ch);
|
|
// Check if the response is valid JSON
|
|
if (!json_decode($output)) {
|
|
self::exception(
|
|
$base,
|
|
$target,
|
|
[
|
|
'method' => 'DELETE',
|
|
'endpoint' => $endpoint,
|
|
'data' => $data,
|
|
'response' => null,
|
|
'status_code' => $status_code,
|
|
'error' => 'Invalid response',
|
|
],
|
|
500
|
|
);
|
|
}
|
|
return json_decode($output);
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
* @throws Exception
|
|
*/
|
|
function addRequestToLog(string $base, string $target, string $endpoint, object $response): void
|
|
{
|
|
// Add the request to the fxratesapi request log
|
|
$fxratesapi_lookups = new fxratesapi_conversion_rates_o();
|
|
$fxratesapi_lookups->add($base, $target, json_encode($response), $endpoint);
|
|
// Check if the request was added successfully
|
|
if (!$fxratesapi_lookups->id) {
|
|
throw new Exception('Failed to add request to log');
|
|
}
|
|
}
|
|
}
|