route = $_SERVER['REQUEST_URI']; } public function run(): void { // Add the routes here } /** * Require type * @param mixed $value * @param string $type * @return bool */ public function requireType(mixed $value, string $type): bool { // Get the type of the value $value_type = gettype($value); // Check if the value is of the required type if ($value_type !== $type) { global $response; $response->error('Invalid type. Expected: ' . $type . ' Got: ' . $value_type . ' Value: ' . $value, 400); } return true; } /** * Require parameter to be one of the given types * @param mixed $value The value to check * @param array $types The types to check against * @return bool */ public function requireTypeIn(mixed $value, array $types): bool { // Get the type of the value $value_type = gettype($value); // Check if the value is of the required type if (!in_array($value_type, $types)) { global $response; $response->error('Invalid type. Expected: ' . implode(', ', $types) . ' Got: ' . $value_type . ' Value: ' . $value, 400); } return true; } /** * TYPE_NULL * @note This is used to check if the value is null, this is used in routes to validate input * @return string */ public function TYPE_NULL(): string { return 'NULL'; } /** * The default date format (Y-m-d) Example: 2023-01-01 * @note This is used to format dates in the API * @return string */ public function FORMAT_DATE(): string { return 'Y-m-d'; } public function requireDateFormat(string $date, string $format): void { global $response; $d = \DateTime::createFromFormat($format, $date); if (!$d || $d->format($format) !== $date) { $response->error('Invalid date format. Expected: ' . $format . ' Got: ' . $date, 400); } } public function requireMinValue(int $value, int $min): void { global $response; if ($value < $min) { $response->error('Parameter must be at least ' . $min, 400); } } public function type_int(): string { return 'integer'; } public function type_string(): string { return 'string'; } public function requireParameters(array $parameters): void { global $response; $missing_parameters = []; // Check if all required parameters are set foreach ( $parameters as $parameter ) { if (!$response->getRequestParameter($parameter) && !$response->isRequestParameterSet($parameter)) { $missing_parameters[] = $parameter; } } if (count($missing_parameters) > 0) { $response->error('Missing required parameters: ' . implode(', ', $missing_parameters), 400); } } /** * Require department access * @note This checks if the user has access to the department by checking if the user has the permission department_access_{department} (_{permission} if provided) * @param string $department * @param string|null $permission */ public function requireDepartmentAccess(string $department, string|null $permission = null): void { self::requirePermission('department_access_' . $department . ($permission ? '_' . $permission : '')); } /** * Require permission * @param string $permission * @return bool */ public function requirePermission(string $permission): bool { global $response; // Check if the users authorization token has the required permission try { $user = (new authentication())->get_user(); // If there is no user, return an error if (!$user) { (new logs_o())->add('global', 'global', 1, 0, 'AUTHENTICATION_FAILED', 'Authentication failed. Invalid, or missing token'); $response->error('Authentication failed. Invalid or missing token.', 401); } if (!$user->hasPermission($permission)) { (new logs_o())->add('global', 'global', 1, $user->id, 'PERMISSION_DENIED', 'Permission denied. Missing permission: ' . $permission); $response->error('Permission denied. Missing permission: ' . $permission . ' for user: ' . $user->id . ' In group: ' . $user->group_id->value(), 403); } } catch (\Exception $e) { $response->error($e->getMessage(), 400); } return true; } /** * Require parameter to be a positive integer * @param int $value The value to check * @param string|null $parameter The name of the parameter * @return void */ public function requireParameterIntPositive(int $value, string $parameter = null): void { global $response; if ($value <= 0) { $response->error('Parameter ' . $parameter . ' must be a positive integer', 400); } } public function requireMinLength(string $parameter, int $length): void { global $response; $value = self::getParameter($parameter); if (strlen($value) < $length) { $response->error('Parameter ' . $parameter . ' must be at least ' . $length . ' characters long', 400); } } /** * Get the parameter from the request by name * This is a shorthand for the response class method * @param string $parameter * @return mixed|null */ public function getParameter(string $parameter): mixed { global $response; return $response->getRequestParameter($parameter); } public function requireMaxLength(string $parameter, int $length): void { global $response; $value = self::getParameter($parameter); if (strlen($value) > $length) { $response->error('Parameter ' . $parameter . ' must be at most ' . $length . ' characters long', 400); } } public function isParametersSet(array $parameters): bool { global $response; // Check if all required parameters are set foreach ( $parameters as $parameter ) { if (!$response->isRequestParameterSet($parameter)) { return false; } } return true; } /** * GET route * @param string $route Example: /home, /home/{id} * @param callable $callback * @param array $permissions * @return void */ public function get(string $route, callable $callback, array $permissions = []): void { $this->registerRoute($route, 'GET', $callback, $permissions); } private function registerRoute($route, $method, $callback, $permissions = []): void { global $router; $router->add($route, $method, $callback, self::registerPermissions($permissions, $route, $method)); } /** * Register permissions for the route * @param array $permissions The permissions to register * @param null $endpoint * @param null $method * @return array */ public function registerPermissions(array $permissions, $endpoint = null, $method = null): array { foreach ( $permissions as $permission => $description ) { $this->registerPermission($permission, $description, $endpoint, $method); } return $this->permissions; } /** * Register a permission * @param string $permission The permission to register (Example: 'modules_motorapi_lookup') * @param string $description The description of the permission (Example: 'Lookup license plate information') * @param null $endpoint The endpoint of the permission (Example: '/department/license-plate/lookup') * @param null $method The method of the permission (Example: 'GET') * @return void */ public function registerPermission(string $permission, string $description = 'No description provided.', $endpoint = null, $method = null): void { $this->permissions[$endpoint ?? $this->route][$method ?? 'UNKNOWN_METHOD'][$permission] = $description; } /** * POST route * @param string $route Example: /home, /home/{id} */ public function post(string $route, callable $callback, array $permissions = []): void { $this->registerRoute($route, 'POST', $callback, $permissions); } /** * PUT route * @param string $route Example: /home, /home/{id} */ public function put(string $route, callable $callback, array $permissions = []): void { $this->registerRoute($route, 'PUT', $callback, $permissions); } /** * DELETE route * @param string $route Example: /home, /home/{id} */ public function delete(string $route, callable $callback, array $permissions = []): void { $this->registerRoute($route, 'DELETE', $callback, $permissions); } /** * OPTIONS route * @param string $route Example: /home, /home/{id} */ public function options(string $route, callable $callback, array $permissions = []): void { $this->registerRoute($route, 'OPTIONS', $callback, $permissions); } /** * PATCH route * @param string $route Example: /home, /home/{id} */ public function patch(string $route, callable $callback, array $permissions = []): void { $this->registerRoute($route, 'PATCH', $callback, $permissions); } /** * Get the parameter from the route URL by index * @param string $index * @return string|null */ public function fromRoute(string $index): ?string { $params = explode('/', $this->route); $index = array_search($index, $params); return $params[$index] ?? null; } /** * Require plate scanner authentication * @return bool */ public function requirePlateScannerAuth(): bool { global $response; // Check if the plate scanner has a valid API key try { $plate_scanner = (new authentication())->get_plate_scanner(); if (!$plate_scanner) { (new logs_o())->add('global', 'global', 1, 0, 'AUTHENTICATION_FAILED', 'Authentication failed. Invalid, or missing API key'); $response->error('Authentication failed. Invalid or missing API key.', 401); } } catch (\Exception $e) { $response->error($e->getMessage(), 400); } return true; } /** * Require order access * @param int $order_id The order ID * @param string|null $permission The permission to check * @return bool */ public function requireOrderAccess(int $order_id, string|null $permission = null): bool { // TODO: Implement indirect order access, where the user has access to the department, and the department has access to the order self::requirePermission('order_access_' . $order_id . ($permission ? '_' . $permission : '')); return true; } /** * Require reCAPTCHA for the request * @return bool */ public function requireRecaptcha(): bool { global $response; // Check if the reCAPTCHA is valid try { $recaptcha_response = $response->getRequestParameter('g_recaptcha_response'); $recaptcha = (new recaptcha())->validate($recaptcha_response); if (!$recaptcha) { (new logs_o())->add('global', 'global', 1, 0, 'AUTHENTICATION_FAILED', 'Authentication failed. Invalid, or missing reCAPTCHA'); $response->error('Authentication failed. Invalid or missing reCAPTCHA.', 401); } } catch (\Exception $e) { $response->error($e->getMessage(), 400); } return true; } /** * Get data from the request body or query string by name * @param string $name * @return string|null */ public function fromRequest(string $name): ?string { // If the $_POST variable is set, return the value from the POST variable, otherwise return the value from the query string if (isset($_POST)) { $data = json_decode(file_get_contents('php://input'), true); if (isset($data[$name])) { return $data[$name]; } } return (isset($_POST[$name])) ? $_POST[$name] : $this->fromQuery($name); } /** * Get the parameter from the query string by name * @param string $name * @return string|null */ public function fromQuery(string $name): ?string { return (isset($_GET[$name])) ? $_GET[$name] : null; } /** * Match route * @param string $route Example: /home, /home/{id} */ private function match_route(string $route): bool { // Check if route is the same, or if it matches the regex pattern return $route === $this->route || preg_match($route, $this->route); } }