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 daily limit self::requireDailyLimitNotExceeded(); // Validate the secret key self::requireValidSecretKey(); // 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() >= $this->config->daily_limit->getVariableValue()) { throw new Exception('Daily limit exceeded'); } } /** * @inheritDoc */ function getDailyRequestCounter(): int { // 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(); } /** * @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'); } } }