JERARQUÍA DE ROLES
admin = programador. Modo Dios. Oculto de dropdowns y del Panel
de Control para non-admins. Acceso total.
socio = modo Dios visible. Acceso al Panel, ve y edita todo.
abogado = crea casos, ve los suyos + donde es involucrado.
usuario = solo ve/edita donde es involucrado.
* Migración 008: agrega 'admin' al ENUM users.role. Promueve a
ozerpa@aal-law.com como admin.
* Auth.php: isAdmin() nuevo. isSocio() y isAbogado() ahora incluyen
admin (admin hereda todos los poderes de socio). canCreate/canDelete
también heredan.
* Lookups.php:
- abogadoNames() ya filtraba a role IN (socio, abogado) → admins no
aparecen en el dropdown "Abogado Solicitante".
- involucrablesUsers() agrega filtro role != 'admin' → admins no
aparecen en el picker de Involucrados.
- allUsers($includeAdmins) parametrizada: socios reciben la lista
sin admins, admins reciben todo.
* Bootstrap.php: window.DONNA.isAdmin disponible al frontend.
* admin/api.php:
- list_users pasa Auth::isAdmin() para filtrar.
- create_user / update_user: rechazan role='admin' si quien llama no
es admin. Solo admins pueden editar a otros admins.
- delete_user: protege admins (extends al check existente de socio).
- Self-protection extendida: admin no puede demoverse por debajo de
socio (queda en god-mode tier).
* admin.html: opción 'admin' en role dropdown solo visible si el
usuario actual es admin. Role pill navy/blanca para admin.
48 lines
1.8 KiB
PHP
48 lines
1.8 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/Auth.php';
|
|
require_once __DIR__ . '/Lookups.php';
|
|
|
|
/**
|
|
* Bootstrap — sirve una vista HTML estática con `window.DONNA` inyectado
|
|
* en <head> para que el JS tenga rol, lista de abogados, lista de
|
|
* usuarios involucrables, etc. sin hacer un fetch extra al cargar.
|
|
*/
|
|
final class Bootstrap {
|
|
public static function inject(string $viewPath): void {
|
|
$user = Auth::user();
|
|
$payload = [
|
|
'me' => $user['email'],
|
|
'name' => $user['name'],
|
|
'role' => Auth::role(),
|
|
'isAdmin' => Auth::isAdmin(),
|
|
'isSocio' => Auth::isSocio(),
|
|
'isAbogado' => Auth::isAbogado(),
|
|
'canCreate' => Auth::canCreate(),
|
|
'canDelete' => Auth::canDelete(),
|
|
'abogados' => Lookups::abogadoNames(),
|
|
'users' => Lookups::involucrablesUsers(),
|
|
];
|
|
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP);
|
|
$inject = '<script>window.DONNA = ' . $json . ';</script>';
|
|
|
|
$html = file_get_contents($viewPath);
|
|
if ($html === false) {
|
|
http_response_code(500);
|
|
exit('Vista no disponible.');
|
|
}
|
|
// Inyectar SOLO antes de la primera ocurrencia de </head>. str_replace
|
|
// remplazaba todas — incluyendo las que viven dentro de template
|
|
// literals de JS (ej. en exportPDF), lo cual metía un </script>
|
|
// espurio que cortaba el <script> exterior del view.
|
|
$pos = strpos($html, '</head>');
|
|
if ($pos !== false) {
|
|
$out = substr($html, 0, $pos) . $inject . "\n" . substr($html, $pos);
|
|
} else {
|
|
$out = $inject . "\n" . $html;
|
|
}
|
|
echo $out;
|
|
}
|
|
}
|