up
This commit is contained in:
+145
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/**
|
||||
* core/Router.php
|
||||
* Enrutador central del ERP.
|
||||
*
|
||||
* Mapea la URL entrante a modules/{m}/views/{v}.php.
|
||||
* Soporta rutas limpias (/turnero/dashboard) y parámetros explícitos (?m=&v=).
|
||||
*
|
||||
* RUTAS PÚBLICAS (sin autenticación):
|
||||
* turnero/display → pantalla TV para sala de espera
|
||||
* turnero/kiosko → pantalla táctil para tomar turno
|
||||
*/
|
||||
|
||||
class Router
|
||||
{
|
||||
/** Módulo por defecto cuando no se especifica ?m= */
|
||||
private const DEFAULT_MODULE = 'dashboard';
|
||||
|
||||
/** Vista por defecto cuando no se especifica ?v= */
|
||||
private const DEFAULT_VIEW = 'index';
|
||||
|
||||
/** Rutas que NO requieren sesión iniciada. Formato: 'modulo/vista' */
|
||||
private const PUBLIC_ROUTES = [
|
||||
'turnero/display',
|
||||
'turnero/kiosko',
|
||||
];
|
||||
|
||||
/** Patrón permitido para módulo y vista: solo letras, números y guión bajo */
|
||||
private const SLUG_PATTERN = '/^[a-zA-Z0-9_]{1,64}$/';
|
||||
|
||||
private string $module;
|
||||
private string $view;
|
||||
|
||||
// ─── Constructor ────────────────────────────────────────────────────────
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
[$this->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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user