Files
pta/app/Core/Access.php
Loic Masi 5fbf76868f pour prod
2026-08-07 16:13:43 +02:00

369 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace App\Core;
use PDO;
final class Access
{
public const ROLE_AGENT = 'AGENT';
public const ROLE_RESPONSABLE = 'RESPONSABLE_STRUCTURE';
public const ROLE_SERVICE = 'SERVICE_ENFANCE';
private const SESSION_KEY = 'pta_access';
public function __construct(private PDO $pdo)
{
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
}
public function isConfigured(): bool
{
$role = $this->role();
if ($role === self::ROLE_AGENT) {
return $this->agentId() !== null && $this->agentRecord() !== null;
}
if ($role === self::ROLE_RESPONSABLE) {
return $this->structureId() !== null && $this->structureRecord() !== null;
}
return $role === self::ROLE_SERVICE;
}
public function role(): ?string
{
$role = strtoupper(trim((string) ($_SESSION[self::SESSION_KEY]['role'] ?? '')));
return in_array($role, [self::ROLE_AGENT, self::ROLE_RESPONSABLE, self::ROLE_SERVICE], true)
? $role
: null;
}
public function agentId(): ?int
{
$value = filter_var($_SESSION[self::SESSION_KEY]['agent_id'] ?? null, FILTER_VALIDATE_INT, [
'options' => ['min_range' => 1],
]);
return $value === false ? null : (int) $value;
}
public function structureId(): ?int
{
if ($this->role() === self::ROLE_AGENT) {
$agent = $this->agentRecord();
return isset($agent['id_structure']) && $agent['id_structure'] !== null
? (int) $agent['id_structure']
: null;
}
$value = filter_var($_SESSION[self::SESSION_KEY]['structure_id'] ?? null, FILTER_VALIDATE_INT, [
'options' => ['min_range' => 1],
]);
return $value === false ? null : (int) $value;
}
public function select(string $role, ?int $agentId = null, ?int $structureId = null): void
{
$role = strtoupper(trim($role));
if (!in_array($role, [self::ROLE_AGENT, self::ROLE_RESPONSABLE, self::ROLE_SERVICE], true)) {
throw new \DomainException('Rôle invalide.');
}
$selection = ['role' => $role, 'agent_id' => null, 'structure_id' => null];
if ($role === self::ROLE_AGENT) {
if (!$agentId || !$this->activeAgentExists($agentId)) {
throw new \DomainException('Sélectionnez un agent actif.');
}
$selection['agent_id'] = $agentId;
} elseif ($role === self::ROLE_RESPONSABLE) {
if (!$structureId || !$this->activeStructureExists($structureId)) {
throw new \DomainException('Sélectionnez un lieu daffectation actif.');
}
$selection['structure_id'] = $structureId;
}
$_SESSION[self::SESSION_KEY] = $selection;
session_regenerate_id(true);
}
public function clear(): void
{
unset($_SESSION[self::SESSION_KEY]);
session_regenerate_id(true);
}
public function profile(): array
{
$role = $this->role();
$permissions = $this->permissions();
$profile = [
'role' => $role,
'role_label' => $this->roleLabel(),
'label' => $this->roleLabel(),
'agent_id' => $this->agentId(),
'structure_id' => $this->structureId(),
'permissions' => $permissions,
];
if ($role === self::ROLE_AGENT) {
$agent = $this->agentRecord();
if ($agent !== null) {
$profile['label'] = trim((string) $agent['prenom'] . ' ' . (string) $agent['nom']);
$profile['matricule'] = $agent['matricule'];
$profile['structure_name'] = $agent['structure_nom'];
$profile['structure_id'] = $agent['id_structure'] !== null ? (int) $agent['id_structure'] : null;
}
} elseif ($role === self::ROLE_RESPONSABLE) {
$structure = $this->structureRecord();
if ($structure !== null) {
$profile['label'] = (string) $structure['nom'];
$profile['structure_name'] = (string) $structure['nom'];
$profile['structure_code'] = (string) $structure['code'];
}
}
return $profile;
}
public function permissions(): array
{
$role = $this->role();
$service = $role === self::ROLE_SERVICE;
$responsable = $role === self::ROLE_RESPONSABLE;
return [
'read_only' => $role === self::ROLE_AGENT,
'can_edit_draft' => $service || $responsable,
'can_edit_cross_structure' => $service,
'can_validate' => $service,
'can_reopen' => $service || $responsable,
'can_use_templates' => $service,
'can_copy_full_week' => $service,
'can_view_pending' => $service,
'can_manage_coverage' => $service,
'can_manage_agents' => $service,
'can_manage_structures' => $service,
'can_manage_vacations' => $service,
'can_manage_teams' => $service,
'can_manage_pta' => $service,
'can_forecast_next_year' => $service,
];
}
public function allowedPages(): array
{
return match ($this->role()) {
self::ROLE_AGENT => ['agents', 'structures'],
self::ROLE_RESPONSABLE => ['planning', 'structures', 'agents', 'coverage'],
self::ROLE_SERVICE => ['planning', 'pending', 'coverage', 'structures', 'agents', 'annual', 'pta', 'teams', 'vacations', 'administration'],
default => [],
};
}
public function canAccessPage(string $page): bool
{
return in_array($page, $this->allowedPages(), true);
}
public function homeUrl(): string
{
return match ($this->role()) {
self::ROLE_AGENT => 'agents.php?agent_id=' . (int) $this->agentId(),
self::ROLE_RESPONSABLE => 'planning.php?structure_id=' . (int) $this->structureId(),
self::ROLE_SERVICE => 'planning.php',
default => 'role.php',
};
}
public function requirePage(string $page): void
{
if (!$this->isConfigured()) {
header('Location: role.php', true, 302);
exit;
}
if (!$this->canAccessPage($page)) {
header('Location: ' . $this->homeUrl() . (str_contains($this->homeUrl(), '?') ? '&' : '?') . 'access_denied=1', true, 302);
exit;
}
}
public function requireRoles(array $roles): void
{
if (!$this->isConfigured()) {
JsonResponse::send(401, ['error' => 'Choisissez votre rôle avant de continuer.', 'code' => 'ROLE_REQUIRED']);
}
if (!in_array($this->role(), $roles, true)) {
JsonResponse::send(403, ['error' => 'Votre rôle ne permet pas cette action.', 'code' => 'ACCESS_DENIED']);
}
}
public function requireService(): void
{
$this->requireRoles([self::ROLE_SERVICE]);
}
public function requireDraftEditor(): void
{
$this->requireRoles([self::ROLE_RESPONSABLE, self::ROLE_SERVICE]);
}
public function requireStructureView(int $structureId): void
{
$this->requireRoles([self::ROLE_AGENT, self::ROLE_RESPONSABLE, self::ROLE_SERVICE]);
if ($this->role() !== self::ROLE_SERVICE && $this->structureId() !== $structureId) {
JsonResponse::send(403, ['error' => 'Vous ne pouvez consulter que le planning de votre lieu de rattachement.', 'code' => 'STRUCTURE_SCOPE']);
}
}
public function requireStructureEdit(int $structureId): void
{
$this->requireDraftEditor();
if ($this->role() === self::ROLE_RESPONSABLE && $this->structureId() !== $structureId) {
JsonResponse::send(403, ['error' => 'Vous ne pouvez modifier que les brouillons de votre lieu.', 'code' => 'STRUCTURE_SCOPE']);
}
}
public function requireAgentView(int $agentId): void
{
$this->requireRoles([self::ROLE_AGENT, self::ROLE_RESPONSABLE, self::ROLE_SERVICE]);
if ($this->role() === self::ROLE_SERVICE) {
return;
}
if ($this->role() === self::ROLE_AGENT && $this->agentId() === $agentId) {
return;
}
if ($this->role() === self::ROLE_RESPONSABLE && $this->responsibleCanViewAgent($agentId)) {
return;
}
JsonResponse::send(403, ['error' => 'Vous ne pouvez pas consulter le planning de cet agent.', 'code' => 'AGENT_SCOPE']);
}
public function requireEntryEdit(int $entryId): void
{
$this->requireDraftEditor();
if ($this->role() === self::ROLE_SERVICE) {
return;
}
$stmt = $this->pdo->prepare(
'SELECT p.id_structure
FROM creneau_horaire c
INNER JOIN planning p ON p.id_planning = c.id_planning
WHERE c.id_creneau = :id LIMIT 1'
);
$stmt->execute(['id' => $entryId]);
$structureId = $stmt->fetchColumn();
if ($structureId === false) {
JsonResponse::send(404, ['error' => 'Créneau introuvable.']);
}
$this->requireStructureEdit((int) $structureId);
}
public function requirePlanningEdit(int $planningId): void
{
$this->requireDraftEditor();
if ($this->role() === self::ROLE_SERVICE) {
return;
}
$stmt = $this->pdo->prepare('SELECT id_structure FROM planning WHERE id_planning = :id LIMIT 1');
$stmt->execute(['id' => $planningId]);
$structureId = $stmt->fetchColumn();
if ($structureId === false) {
JsonResponse::send(404, ['error' => 'Planning introuvable.']);
}
$this->requireStructureEdit((int) $structureId);
}
public function editableStructureId(): ?int
{
return $this->role() === self::ROLE_RESPONSABLE ? $this->structureId() : null;
}
private function roleLabel(): string
{
return match ($this->role()) {
self::ROLE_AGENT => 'Agent',
self::ROLE_RESPONSABLE => 'Responsable de structure',
self::ROLE_SERVICE => 'Service Enfance',
default => 'Rôle non sélectionné',
};
}
private function activeAgentExists(int $agentId): bool
{
$stmt = $this->pdo->prepare('SELECT 1 FROM agent WHERE id_agent = :id AND actif = TRUE LIMIT 1');
$stmt->execute(['id' => $agentId]);
return $stmt->fetchColumn() !== false;
}
private function activeStructureExists(int $structureId): bool
{
$stmt = $this->pdo->prepare('SELECT 1 FROM structure WHERE id_structure = :id AND actif = TRUE LIMIT 1');
$stmt->execute(['id' => $structureId]);
return $stmt->fetchColumn() !== false;
}
private function agentRecord(): ?array
{
$agentId = $this->agentId();
if ($agentId === null) {
return null;
}
$stmt = $this->pdo->prepare(
'SELECT a.id_agent, a.matricule, a.nom, a.prenom, a.id_structure,
s.nom AS structure_nom
FROM agent a
LEFT JOIN structure s ON s.id_structure = a.id_structure
WHERE a.id_agent = :id AND a.actif = TRUE LIMIT 1'
);
$stmt->execute(['id' => $agentId]);
return $stmt->fetch() ?: null;
}
private function structureRecord(): ?array
{
$structureId = $this->structureId();
if ($structureId === null) {
return null;
}
$stmt = $this->pdo->prepare(
'SELECT id_structure, code, nom
FROM structure
WHERE id_structure = :id AND actif = TRUE LIMIT 1'
);
$stmt->execute(['id' => $structureId]);
return $stmt->fetch() ?: null;
}
private function responsibleCanViewAgent(int $agentId): bool
{
$structureId = $this->structureId();
if ($structureId === null) {
return false;
}
$stmt = $this->pdo->prepare(
'SELECT 1
FROM agent a
WHERE a.id_agent = :agent_id
AND a.actif = TRUE
AND (
a.id_structure = :structure_id_default
OR EXISTS (
SELECT 1 FROM planning p
WHERE p.id_agent = a.id_agent
AND p.id_structure = :structure_id_planning
)
)
LIMIT 1'
);
$stmt->execute([
'agent_id' => $agentId,
'structure_id_default' => $structureId,
'structure_id_planning' => $structureId,
]);
return $stmt->fetchColumn() !== false;
}
}