routerCachePath = getClassPath(self::ROUTER_CACHE_CLASS); } protected function routeExists($uid): bool { $sql = $this->user->getSQL(); $res = $sql->select($sql->count()) ->from("Route") ->where(new Compare("uid", $uid)) ->execute(); $this->success = ($res !== false); $this->lastError = $sql->getLastError(); if ($this->success) { if ($res[0]["count"] === 0) { return $this->createError("Route not found"); } } return $this->success; } protected function toggleRoute($uid, $active): bool { if (!$this->routeExists($uid)) { return false; } $sql = $this->user->getSQL(); $this->success = $sql->update("Route") ->set("active", $active) ->where(new Compare("uid", $uid)) ->execute(); $this->lastError = $sql->getLastError(); $this->success = $this->success && $this->regenerateCache(); return $this->success; } protected function regenerateCache(): bool { $req = new GenerateCache($this->user); $this->success = $req->execute(); $this->lastError = $req->getLastError(); return $this->success; } } } namespace Api\Routes { use Api\Parameter\Parameter; use Api\Parameter\StringType; use Api\RoutesAPI; use Driver\SQL\Condition\Compare; use Driver\SQL\Condition\CondBool; use Objects\Router; use Objects\User; class Fetch extends RoutesAPI { public function __construct($user, $externalCall = false) { parent::__construct($user, $externalCall, array()); } public function _execute(): bool { $sql = $this->user->getSQL(); $res = $sql ->select("uid", "request", "action", "target", "extra", "active", "exact") ->from("Route") ->orderBy("uid") ->ascending() ->execute(); $this->lastError = $sql->getLastError(); $this->success = ($res !== FALSE); if ($this->success) { $routes = array(); foreach ($res as $row) { $routes[] = array( "uid" => intval($row["uid"]), "request" => $row["request"], "action" => $row["action"], "target" => $row["target"], "extra" => $row["extra"] ?? "", "active" => intval($sql->parseBool($row["active"])), "exact" => intval($sql->parseBool($row["exact"])), ); } $this->result["routes"] = $routes; } return $this->success; } } class Save extends RoutesAPI { private array $routes; public function __construct($user, $externalCall = false) { parent::__construct($user, $externalCall, array( 'routes' => new Parameter('routes', Parameter::TYPE_ARRAY, false) )); } public function _execute(): bool { if (!$this->validateRoutes()) { return false; } $sql = $this->user->getSQL(); // DELETE old rules $this->success = ($sql->truncate("Route")->execute() !== FALSE); $this->lastError = $sql->getLastError(); // INSERT new routes if ($this->success) { $stmt = $sql->insert("Route", array("request", "action", "target", "extra", "active", "exact")); foreach ($this->routes as $route) { $stmt->addRow($route["request"], $route["action"], $route["target"], $route["extra"], $route["active"], $route["exact"]); } $this->success = ($stmt->execute() !== FALSE); $this->lastError = $sql->getLastError(); } $this->success = $this->success && $this->regenerateCache(); return $this->success; } private function validateRoutes(): bool { $this->routes = array(); $keys = array( "request" => [Parameter::TYPE_STRING, Parameter::TYPE_INT], "action" => Parameter::TYPE_STRING, "target" => Parameter::TYPE_STRING, "extra" => Parameter::TYPE_STRING, "active" => Parameter::TYPE_BOOLEAN, "exact" => Parameter::TYPE_BOOLEAN, ); foreach ($this->getParam("routes") as $index => $route) { foreach ($keys as $key => $expectedType) { if (!array_key_exists($key, $route)) { return $this->createError("Route $index missing key: $key"); } $value = $route[$key]; $type = Parameter::parseType($value); if (!is_array($expectedType)) { $expectedType = [$expectedType]; } if (!in_array($type, $expectedType)) { if (count($expectedType) > 0) { $expectedTypeName = "expected: " . Parameter::names[$expectedType]; } else { $expectedTypeName = "expected one of: " . implode(",", array_map( function ($type) { return Parameter::names[$type]; }, $expectedType)); } $gotTypeName = Parameter::names[$type]; return $this->createError("Route $index has invalid value for key: $key, $expectedTypeName, got: $gotTypeName"); } } $action = $route["action"]; if (!in_array($action, self::ACTIONS)) { return $this->createError("Invalid action: $action"); } if (empty($route["request"])) { return $this->createError("Request cannot be empty."); } if (empty($route["target"])) { return $this->createError("Target cannot be empty."); } $this->routes[] = $route; } return true; } } class Add extends RoutesAPI { public function __construct(User $user, bool $externalCall = false) { parent::__construct($user, $externalCall, array( "request" => new StringType("request", 128), "action" => new StringType("action"), "target" => new StringType("target", 128), "extra" => new StringType("extra", 64, true, ""), )); $this->isPublic = false; } public function _execute(): bool { $request = $this->getParam("request"); $action = $this->getParam("action"); $target = $this->getParam("target"); $extra = $this->getParam("extra"); if (!in_array($action, self::ACTIONS)) { return $this->createError("Invalid action: $action"); } $sql = $this->user->getSQL(); $this->success = $sql->insert("Route", ["request", "action", "target", "extra"]) ->addRow($request, $action, $target, $extra) ->execute(); $this->lastError = $sql->getLastError(); $this->success = $this->success && $this->regenerateCache(); return $this->success; } } class Update extends RoutesAPI { public function __construct(User $user, bool $externalCall = false) { parent::__construct($user, $externalCall, array( "uid" => new Parameter("uid", Parameter::TYPE_INT), "request" => new StringType("request", 128), "action" => new StringType("action"), "target" => new StringType("target", 128), "extra" => new StringType("extra", 64, true, ""), )); $this->isPublic = false; } public function _execute(): bool { $uid = $this->getParam("uid"); if (!$this->routeExists($uid)) { return false; } $request = $this->getParam("request"); $action = $this->getParam("action"); $target = $this->getParam("target"); $extra = $this->getParam("extra"); if (!in_array($action, self::ACTIONS)) { return $this->createError("Invalid action: $action"); } $sql = $this->user->getSQL(); $this->success = $sql->update("Route") ->set("request", $request) ->set("action", $action) ->set("target", $target) ->set("extra", $extra) ->where(new Compare("uid", $uid)) ->execute(); $this->lastError = $sql->getLastError(); $this->success = $this->success && $this->regenerateCache(); return $this->success; } } class Remove extends RoutesAPI { public function __construct(User $user, bool $externalCall = false) { parent::__construct($user, $externalCall, array( "uid" => new Parameter("uid", Parameter::TYPE_INT) )); $this->isPublic = false; } public function _execute(): bool { $uid = $this->getParam("uid"); if (!$this->routeExists($uid)) { return false; } $sql = $this->user->getSQL(); $this->success = $sql->delete("Route") ->where(new Compare("uid", $uid)) ->execute(); $this->lastError = $sql->getLastError(); $this->success = $this->success && $this->regenerateCache(); return $this->success; } } class Enable extends RoutesAPI { public function __construct(User $user, bool $externalCall = false) { parent::__construct($user, $externalCall, array( "uid" => new Parameter("uid", Parameter::TYPE_INT) )); $this->isPublic = false; } public function _execute(): bool { $uid = $this->getParam("uid"); return $this->toggleRoute($uid, true); } } class Disable extends RoutesAPI { public function __construct(User $user, bool $externalCall = false) { parent::__construct($user, $externalCall, array( "uid" => new Parameter("uid", Parameter::TYPE_INT) )); $this->isPublic = false; } public function _execute(): bool { $uid = $this->getParam("uid"); return $this->toggleRoute($uid, false); } } class GenerateCache extends RoutesAPI { private ?Router $router; public function __construct(User $user, bool $externalCall = false) { parent::__construct($user, $externalCall, []); $this->isPublic = false; $this->router = null; } protected function _execute(): bool { $sql = $this->user->getSQL(); $res = $sql ->select("uid", "request", "action", "target", "extra", "exact") ->from("Route") ->where(new CondBool("active")) ->orderBy("uid")->ascending() ->execute(); $this->success = $res !== false; $this->lastError = $sql->getLastError(); if (!$this->success) { return false; } $this->router = new Router($this->user); foreach ($res as $row) { $request = $row["request"]; $target = $row["target"]; $exact = $sql->parseBool($row["exact"]); switch ($row["action"]) { case "redirect_temporary": $this->router->addRoute(new Router\RedirectRoute($request, $exact, $target, 307)); break; case "redirect_permanently": $this->router->addRoute(new Router\RedirectRoute($request, $exact, $target, 308)); break; case "static": $this->router->addRoute(new Router\StaticFileRoute($request, $exact, $target)); break; case "dynamic": $extra = json_decode($row["extra"]) ?? []; $this->router->addRoute(new Router\DocumentRoute($request, $exact, $target, ...$extra)); break; default: break; } } $this->success = $this->router->writeCache($this->routerCachePath); if (!$this->success) { return $this->createError("Error saving router cache file: " . $this->routerCachePath); } return $this->success; } public function getRouter(): ?Router { return $this->router; } } }