module, $this->view] = $this->resolveRoute(); } // ─── API pública ──────────────────────────────────────────────────────── public function getModule(): string { return $this->module; } public function getView(): string { return $this->view; } /** * Indica si la ruta actual es pública (no requiere login). */ public function isPublic(): bool { return in_array($this->module . '/' . $this->view, self::PUBLIC_ROUTES, true); } /** * Resuelve la ruta y devuelve el archivo de vista correspondiente. * Lanza una excepción si el archivo no existe. * * @return string Ruta absoluta al archivo de vista. * @throws RuntimeException Si el módulo/vista no se encuentran. */ public function resolveFile(): string { $file = APP_ROOT . "/modules/{$this->module}/views/{$this->view}.php"; if (!file_exists($file)) { throw new RuntimeException( "Vista no encontrada: {$this->module}/{$this->view}", 404 ); } return $file; } // ─── Internos ─────────────────────────────────────────────────────────── /** * Determina módulo y vista a partir de la URL. * Prioridad: PATH_INFO limpio → parámetros GET ?m= ?v= * * @return array{string, string} */ private function resolveRoute(): array { // 1. Intentar ruta limpia desde PATH_INFO o REQUEST_URI // Formato: /modulo/vista → e.g. /turnero/dashboard $path = $this->cleanPath(); if ($path !== null) { [$m, $v] = $path; if ($this->isValidSlug($m) && $this->isValidSlug($v)) { return [$m, $v]; } } // 2. Parámetros GET ?m=module&v=view $m = $_GET['m'] ?? self::DEFAULT_MODULE; $v = $_GET['v'] ?? self::DEFAULT_VIEW; // Sanitizar: solo caracteres seguros $m = preg_replace('/[^a-zA-Z0-9_]/', '', $m); $v = preg_replace('/[^a-zA-Z0-9_]/', '', $v); if (!$this->isValidSlug($m)) $m = self::DEFAULT_MODULE; if (!$this->isValidSlug($v)) $v = self::DEFAULT_VIEW; return [$m, $v]; } /** * Extrae módulo y vista de la REQUEST_URI limpia. * Retorna null si la URI no corresponde al formato /m/v. * * @return array{string,string}|null */ private function cleanPath(): ?array { $uri = $_SERVER['REQUEST_URI'] ?? ''; // Eliminar query string $pos = strpos($uri, '?'); if ($pos !== false) { $uri = substr($uri, 0, $pos); } // Eliminar el subdirectorio base si el proyecto no está en la raíz $scriptDir = rtrim(dirname($_SERVER['SCRIPT_NAME'] ?? ''), '/'); if ($scriptDir !== '' && str_starts_with($uri, $scriptDir)) { $uri = substr($uri, strlen($scriptDir)); } // Normalizar y dividir $parts = array_values(array_filter(explode('/', trim($uri, '/')))); if (count($parts) >= 2) { return [$parts[0], $parts[1]]; } return null; } private function isValidSlug(string $slug): bool { return (bool) preg_match(self::SLUG_PATTERN, $slug); } }