config = new motorapi_c(); $this->vehicle_types = new motorapi_vehicle_types(); /** Actions */ $this->license_plate_lookup = new license_plate_lookup_a(); } private static function cleanJSON(mixed $value): string { if (is_string($value)) { // Remove backslashes $value = stripslashes($value); $value = stripslashes($value); // Ensure the string is valid UTF-8 $value = mb_convert_encoding($value, 'UTF-8', 'UTF-8'); // Optionally, you can use regex to remove any non-printable characters $value = preg_replace('/[^\x20-\x7E]/', '', $value); } else { echo 'Unable to perform cleaning on value of type ' . gettype($value) . ': ' . print_r($value, true) . PHP_EOL; } // Remove any invalid UTF-8 characters return $value; } /** * Get the recommended products for a license plate * @param string $plate The license plate * @return array The recommended products * @throws Exception If the module is not enabled * @throws Exception If the license plate is invalid * @throws Exception If the daily limit is exceeded * @throws Exception If the secret key is invalid */ public function getRecommendedProducts(string $plate): array { self::requireModuleEnabled(); self::requireValidLicensePlate($plate); $data = self::getLicensePlateInformation($plate, false); // Get the recommended products for the license plate return match ($data->type) { // TODO: Add recommended products for each vehicle type, and convert this into an object type. $this->vehicle_types->PLATE_TYPE_CAR => [ 1, 2, ], $this->vehicle_types->PLATE_TYPE_TRUCK => [ 3, 4, ], default => [], }; } /** * @inheritDoc */ function requireModuleEnabled(): void { // Check if the module is enabled if (!$this->config->enabled->isTrue()) { throw new Exception('The motorapi module is not enabled'); } } /** * @inheritDoc */ function requireValidLicensePlate(string $licensePlate): void { // Check if the license plate is valid if (!preg_match('/^[A-Z0-9]{1,10}$/', $licensePlate)) { throw new Exception('Invalid license plate'); } } /** * @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 getLicensePlateInformation(string $licensePlate, bool $ignoreCache = true): object { // Check if the license plate information is cached if (!$ignoreCache && self::isCached($licensePlate)) { // Get the cached result $tmp_res = self::getCachedResult($licensePlate); if ($tmp_res !== null) { return $tmp_res; } } // Get the license plate information from the motorapi return $this->sendRequest($licensePlate, 'vehicles', [], 'GET'); } /** * @inheritDoc */ function isCached(string $licensePlate): bool { // Check if the license plate response is stored in the log/local database/cache $motorapi_lookups = new motorapi_lookups_o(); return $motorapi_lookups->isCached($licensePlate); } /** * @inheritDoc */ function getCachedResult(string $licensePlate): ?object { global /** @var response $response */ $response; // Get the cached result from the log/local database/cache $motorapi_lookups = new motorapi_lookups_o(); // Add the cached value to the meta self::addCachedMetaIfPossible($response); $cleaned_result = self::cleanJSON($motorapi_lookups->getCachedResult($licensePlate)->result->value()); $object = json_decode($cleaned_result); if ($object === null) { print_r($cleaned_result); throw new Exception('Cached result is not valid JSON'); } return json_decode($cleaned_result); } public static function addCachedMetaIfPossible(mixed $response): void { if (is_object($response) && method_exists($response, 'add_meta')) { $response->add_meta('cached', true); } } /** * @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 $licensePlate, string $endpoint, array $data = [], string $method = 'GET'): object { // Validate the module is enabled self::requireModuleEnabled(); // Validate the license plate self::requireValidLicensePlate($licensePlate); // Validate the daily limit self::requireDailyLimitNotExceeded(); // Validate the secret key self::requireValidSecretKey(); // Send the request $response = match ($method) { 'GET' => self::sendGetRequest($licensePlate, $endpoint, $data), 'POST' => self::sendPostRequest($licensePlate, $endpoint, $data), 'PUT' => self::sendPutRequest($licensePlate, $endpoint, $data), 'DELETE' => self::sendDeleteRequest($licensePlate, $endpoint, $data), default => self::exception( $licensePlate, [ 'method' => $method, 'endpoint' => $endpoint, 'data' => $data, 'license_plate' => $licensePlate, 'response' => null, 'status_code' => 400, 'error' => 'Invalid request method', ], 500 ), }; // Add the request to the log self::addRequestToLog($licensePlate, $endpoint, $response); // Return the response return $response; } /** * @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 motorapi request log that was made today $motorapi_lookups = new motorapi_lookups_o(); $motorapi_lookups->getTodayCount(); return $motorapi_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 (motorapi_secret_key_c)', ], 500 ); } } /** * @throws Exception */ function exception(string $licensePlate, array $data = [], int $status_code = 500): exception { // Add the request to the log $this->license_plate_lookup->license_plate_lookup($licensePlate, $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 $licensePlate, string $endpoint, array $data): object { // 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 . '/' . $licensePlate . $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( $licensePlate, [ 'method' => 'GET', 'endpoint' => $endpoint, 'data' => $data, 'license_plate' => $licensePlate, '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( $licensePlate, [ 'method' => 'GET', 'endpoint' => $endpoint, 'data' => $data, 'license_plate' => $licensePlate, 'response' => null, 'status_code' => $status_code, 'error' => $exception_message, ], $status_code ); } $this->license_plate_lookup->license_plate_lookup($licensePlate, json_decode($output), $status_code); return json_decode($output); } /** * @inheritDoc */ function sendPostRequest(string $licensePlate, string $endpoint, array $data): object { // Send a POST request to the motorapi $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $this->api_url . $endpoint); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $output = curl_exec($ch); curl_close($ch); $this->license_plate_lookup->license_plate_lookup($licensePlate, json_decode($output), 200); return json_decode($output); } /** * @inheritDoc */ function sendPutRequest(string $licensePlate, string $endpoint, array $data): object { // Send a PUT request to the motorapi $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $this->api_url . $endpoint); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $output = curl_exec($ch); curl_close($ch); $this->license_plate_lookup->license_plate_lookup($licensePlate, json_decode($output), 200); return json_decode($output); } /** * @inheritDoc */ function sendDeleteRequest(string $licensePlate, string $endpoint, array $data): object { // Send a DELETE request to the motorapi $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $this->api_url . $endpoint); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $output = curl_exec($ch); curl_close($ch); $this->license_plate_lookup->license_plate_lookup($licensePlate, json_decode($output), 200); return json_decode($output); } /** * @inheritDoc * @throws Exception */ function addRequestToLog(string $licensePlate, string $endpoint, object $response): void { // Add the request to the motorapi request log $motorapi_lookups = new motorapi_lookups_o(); $motorapi_lookups->add($licensePlate, json_encode($response), $endpoint); } }