Ajout des commentaires dans le code
This commit is contained in:
@@ -15,6 +15,27 @@ use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* Rendu de toutes les pages HTML de l'application.
|
||||
*
|
||||
* Chaque fichier .php de la racine appelle runPage('<nom>'), qui appelle la
|
||||
* méthode homonyme de cette classe. Le NOM DE LA MÉTHODE fait donc trois choses
|
||||
* à la fois : il est la route, la clé de page dans acces_role_page, et la valeur
|
||||
* de $activePage qui surligne l'onglet courant.
|
||||
*
|
||||
* Ajouter une page suppose donc, ensemble :
|
||||
* 1. un fichier mince à la racine ;
|
||||
* 2. une méthode ici ;
|
||||
* 3. une vue app/Views/pages/ et son partiel app/Views/partials/ ;
|
||||
* 4. un module JS ;
|
||||
* 5. l'inscription de la clé dans $navigation / $navigationGroups
|
||||
* (Views/layouts/header.php) ;
|
||||
* 6. une migration SQL insérant la ligne dans acces_role_page — les droits
|
||||
* sont lus en base, jamais écrits en PHP. Sans elle, la page est refusée.
|
||||
*
|
||||
* Chaque méthode déclare les scripts et modules JS à charger ; footer.php les
|
||||
* injecte et page-bootstrap.js appelle init() sur chaque module.
|
||||
*/
|
||||
final class PageController extends Controller
|
||||
{
|
||||
private Access $access;
|
||||
@@ -24,6 +45,10 @@ final class PageController extends Controller
|
||||
$this->access = new Access($pdo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tableau de bord du Service Enfance (V7.9) — page d'accueil de ce rôle.
|
||||
* Voir Access::homeUrl() pour le repli si la migration n'est pas jouée.
|
||||
*/
|
||||
public function dashboard(): void
|
||||
{
|
||||
$this->access->requirePage('dashboard');
|
||||
@@ -38,6 +63,20 @@ final class PageController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Écran de saisie — cœur de l'application.
|
||||
*
|
||||
* Le lieu est VERROUILLÉ pour un responsable de structure
|
||||
* (`structureLocked`) : il ne peut saisir que chez lui. Le verrou n'est
|
||||
* qu'un confort d'interface, le serveur retranche de toute façon.
|
||||
*
|
||||
* Les scripts de semaine type ne sont chargés que si la permission
|
||||
* can_use_templates est accordée : inutile d'envoyer 1 300 lignes de JS à un
|
||||
* utilisateur qui n'y a pas droit. La page ouvre alors deux modes exclusifs
|
||||
* (éditeur de semaine type / saisie semaine par semaine, voir
|
||||
* README_V7_10.md) ; `focus` et `week` forcent le second mode, car ce sont
|
||||
* les paramètres des liens profonds venus des autres écrans.
|
||||
*/
|
||||
public function planning(): void
|
||||
{
|
||||
$this->access->requirePage('planning');
|
||||
@@ -71,6 +110,7 @@ final class PageController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/** File des plannings en attente de validation (Service Enfance). */
|
||||
public function pending(): void
|
||||
{
|
||||
$this->access->requirePage('pending');
|
||||
@@ -81,6 +121,7 @@ final class PageController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/** Contrôle de couverture : plages où l'effectif passe sous le minimum du lieu. */
|
||||
public function coverage(): void
|
||||
{
|
||||
$this->access->requirePage('coverage');
|
||||
@@ -98,6 +139,7 @@ final class PageController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/** Planning d'un lieu sur une semaine, en lecture seule. */
|
||||
public function structures(): void
|
||||
{
|
||||
$this->access->requirePage('structures');
|
||||
@@ -115,6 +157,12 @@ final class PageController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Planning d'un agent, mois par mois et tous lieux confondus.
|
||||
*
|
||||
* Un AGENT est verrouillé sur lui-même (`agentLocked`) et le titre bascule
|
||||
* sur « Mon planning » : la page sert alors de vue personnelle.
|
||||
*/
|
||||
public function agents(): void
|
||||
{
|
||||
$this->access->requirePage('agents');
|
||||
@@ -136,6 +184,7 @@ final class PageController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/** Vue annuelle (janvier -> décembre) et validation de l'année entière. */
|
||||
public function annual(): void
|
||||
{
|
||||
$this->access->requirePage('annual');
|
||||
@@ -151,6 +200,7 @@ final class PageController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/** PTA détaillé d'un agent : souhaits de lieux, contraintes, répartition. */
|
||||
public function pta(): void
|
||||
{
|
||||
$this->access->requirePage('pta');
|
||||
@@ -167,6 +217,7 @@ final class PageController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/** Composition des équipes sur les mercredis et les vacances. */
|
||||
public function teams(): void
|
||||
{
|
||||
$this->access->requirePage('teams');
|
||||
@@ -183,6 +234,11 @@ final class PageController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Périodes de vacances scolaires : elles déterminent le contexte
|
||||
* périscolaire / extrascolaire de chaque journée, et sont importables depuis
|
||||
* l'API data.education.gouv.fr (SchoolHolidayApiService).
|
||||
*/
|
||||
public function vacations(): void
|
||||
{
|
||||
$this->access->requirePage('vacations');
|
||||
@@ -193,6 +249,7 @@ final class PageController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/** Référentiels : agents, lieux, postes, règles de couverture. */
|
||||
public function administration(): void
|
||||
{
|
||||
$this->access->requirePage('administration');
|
||||
@@ -220,6 +277,21 @@ final class PageController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fabrique commune à toutes les pages : en-têtes de sécurité puis rendu.
|
||||
*
|
||||
* Pose les clés attendues par header.php et footer.php avec des valeurs par
|
||||
* défaut, que $data écrase. Une page qui oublie 'scripts' ou 'motifs' rendra
|
||||
* donc correctement au lieu de lever une notice sur variable indéfinie.
|
||||
*
|
||||
* Security::sendHtmlHeaders() DOIT être appelé avant tout rendu : il pose la
|
||||
* CSP à nonce, et $cspNonce est ce que chaque <script> inline d'une vue doit
|
||||
* porter pour être exécuté.
|
||||
*
|
||||
* @param string $view Vue de pages/, sans extension.
|
||||
* @param string $activePage Clé de l'onglet à surligner.
|
||||
* @param array<string, mixed> $data Surcharges spécifiques à la page.
|
||||
*/
|
||||
private function renderPage(string $view, string $title, string $activePage, array $data = []): void
|
||||
{
|
||||
Security::sendHtmlHeaders();
|
||||
@@ -243,6 +315,15 @@ final class PageController extends Controller
|
||||
], $data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Lieux proposés dans les sélecteurs, restreints au périmètre du rôle.
|
||||
*
|
||||
* Ce filtrage est du confort d'interface, pas une sécurité : les endpoints
|
||||
* appliquent leurs propres gardes. Un lieu absent d'ici est simplement
|
||||
* inaffichable dans la liste déroulante.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function loadStructures(): array
|
||||
{
|
||||
$model = new StructureModel($this->pdo);
|
||||
@@ -257,6 +338,15 @@ final class PageController extends Controller
|
||||
return $structure ? [$structure] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Variante pour l'éditeur de la fiche agent.
|
||||
*
|
||||
* Un créneau d'agent peut porter sur n'importe quel lieu ; le Service
|
||||
* Enfance a donc besoin de la liste complète même s'il consulte un agent
|
||||
* rattaché ailleurs.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function loadStructuresForAgentEditor(): array
|
||||
{
|
||||
if ($this->access->role() === Access::ROLE_SERVICE) {
|
||||
@@ -265,6 +355,13 @@ final class PageController extends Controller
|
||||
return $this->loadStructures();
|
||||
}
|
||||
|
||||
/**
|
||||
* Agents proposés dans les sélecteurs, selon le rôle : tous (Service),
|
||||
* soi-même (Agent), ou ceux visibles depuis le lieu (Responsable — ce qui
|
||||
* inclut les agents rattachés ailleurs mais planifiés sur place.)
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function loadAgents(): array
|
||||
{
|
||||
$model = new AgentModel($this->pdo);
|
||||
@@ -279,28 +376,53 @@ final class PageController extends Controller
|
||||
return $structureId ? $model->allVisibleForStructure($structureId) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tous les motifs actifs, sans filtrage par agent : c'est l'écran qui
|
||||
* masque « Formation » quand l'agent n'y a pas droit, et le serveur qui
|
||||
* refuse (PlanningController::assertMotifsAllowedForAgent()).
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function loadMotifs(): array
|
||||
{
|
||||
return (new MotifModel($this->pdo))->allActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Semaine courante au format « 2026-W08 ».
|
||||
*
|
||||
* Le format 'o-\WW' associe bien l'ANNÉE ISO ('o') au numéro de semaine ISO
|
||||
* ('W'). Utiliser 'Y' donnerait un couple faux fin décembre / début janvier,
|
||||
* quand la semaine ISO appartient à l'année civile voisine.
|
||||
* Fuseau explicite : le conteneur PHP tourne en UTC.
|
||||
*/
|
||||
private function currentWeek(): string
|
||||
{
|
||||
return (new DateTimeImmutable('now', new DateTimeZone('Europe/Paris')))->format('o-\\WW');
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Assainissement des paramètres d'URL. Ces valeurs partent dans
|
||||
// window.PTA_CONFIG et pilotent les filtres appliqués par
|
||||
// page-bootstrap.js ; toute valeur non conforme est ramenée à null plutôt
|
||||
// que corrigée, pour qu'un lien tronqué ouvre la page par défaut.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/** Identifiant d'entité : strictement positif, ou null. */
|
||||
private function positiveInt(mixed $value): ?int
|
||||
{
|
||||
$number = filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
|
||||
return $number === false ? null : (int) $number;
|
||||
}
|
||||
|
||||
/** Semaine « AAAA-Www ». La validité du numéro est vérifiée côté API. */
|
||||
private function weekValue(mixed $value): ?string
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
return preg_match('/^\\d{4}-W\\d{2}$/', $value) === 1 ? $value : null;
|
||||
}
|
||||
|
||||
/** Date « AAAA-MM-JJ » réellement existante (le 31/02 est rejeté). */
|
||||
private function dateValue(mixed $value): ?string
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
@@ -308,12 +430,14 @@ final class PageController extends Controller
|
||||
return $date && $date->format('Y-m-d') === $value ? $value : null;
|
||||
}
|
||||
|
||||
/** Année bornée à [2000, 2100] : garde-fou contre les valeurs absurdes. */
|
||||
private function yearValue(mixed $value): ?int
|
||||
{
|
||||
$number = filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => 2000, 'max_range' => 2100]]);
|
||||
return $number === false ? null : (int) $number;
|
||||
}
|
||||
|
||||
/** Mois « AAAA-MM », le motif interdisant les mois 00 et 13+. */
|
||||
private function monthValue(mixed $value): ?string
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
|
||||
Reference in New Issue
Block a user