update
This commit is contained in:
9
app/Views/pages/dsi.php
Normal file
9
app/Views/pages/dsi.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
/**
|
||||
* Vue de page : assemble l'en-tête commun, le partiel de contenu et le pied.
|
||||
* Toutes les variables utilisées viennent de PageController::renderPage(), qui
|
||||
* les a extraites dans la portée de ce fichier.
|
||||
*/
|
||||
require dirname(__DIR__) . '/layouts/header.php';
|
||||
require dirname(__DIR__) . '/partials/dsi.php';
|
||||
require dirname(__DIR__) . '/layouts/footer.php';
|
||||
9
app/Views/pages/manage_agents.php
Normal file
9
app/Views/pages/manage_agents.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
/**
|
||||
* Vue de page : assemble l'en-tête commun, le partiel de contenu et le pied.
|
||||
* Toutes les variables utilisées viennent de PageController::renderPage(), qui
|
||||
* les a extraites dans la portée de ce fichier.
|
||||
*/
|
||||
require dirname(__DIR__) . '/layouts/header.php';
|
||||
require dirname(__DIR__) . '/partials/manage_agents.php';
|
||||
require dirname(__DIR__) . '/layouts/footer.php';
|
||||
9
app/Views/pages/manage_structures.php
Normal file
9
app/Views/pages/manage_structures.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
/**
|
||||
* Vue de page : assemble l'en-tête commun, le partiel de contenu et le pied.
|
||||
* Toutes les variables utilisées viennent de PageController::renderPage(), qui
|
||||
* les a extraites dans la portée de ce fichier.
|
||||
*/
|
||||
require dirname(__DIR__) . '/layouts/header.php';
|
||||
require dirname(__DIR__) . '/partials/manage_structures.php';
|
||||
require dirname(__DIR__) . '/layouts/footer.php';
|
||||
@@ -1,131 +1,202 @@
|
||||
<?php
|
||||
/**
|
||||
* Sélecteur de rôle — SEULE vue à ne pas passer par layouts/header.php.
|
||||
* Écran de connexion — SEULE vue à ne pas passer par layouts/header.php.
|
||||
*
|
||||
* Elle s'affiche avant qu'un rôle soit choisi, donc avant tout contrôle
|
||||
* d'accès, et ne peut pas afficher la barre de navigation qui en dépend. Elle
|
||||
* porte donc son propre <head> : c'est le TROISIÈME endroit où incrémenter le
|
||||
* `?v=` des assets, avec layouts/header.php et layouts/footer.php.
|
||||
* Elle s'affiche avant qu'une session existe, donc avant tout contrôle d'accès,
|
||||
* et ne peut pas afficher la barre de navigation qui en dépend. Elle porte donc
|
||||
* son propre <head> : c'est le TROISIÈME endroit où incrémenter le `?v=` des
|
||||
* assets, avec layouts/header.php et layouts/footer.php.
|
||||
*
|
||||
* Rappel : sélection n'est pas authentification. L'utilisateur choisit
|
||||
* librement son identité (voir App\Core\Access::select()).
|
||||
* Depuis la V7.11, ce n'est plus un sélecteur de rôle mais une AUTHENTIFICATION :
|
||||
* l'utilisateur présente le jeton que la DSI lui a remis, et c'est son compte qui
|
||||
* détermine son rôle et son périmètre.
|
||||
*
|
||||
* La V7.12 ajoute un second facteur pour les comptes Service Enfance et DSI.
|
||||
* D'où les quatre états de cet écran, portés par $etape :
|
||||
* « jeton » — saisie du jeton ;
|
||||
* « definition » — premier accès d'un compte privilégié : choisir son mot de passe ;
|
||||
* « verification » — accès suivants : le saisir ;
|
||||
* « changement » — session ouverte, le titulaire remplace son mot de passe.
|
||||
* Un seul formulaire est affiché à la fois : les étapes sont exclusives.
|
||||
*/
|
||||
/** @var string $csrfToken */
|
||||
/** @var array $agents */
|
||||
/** @var array $structures */
|
||||
/** @var string $error */
|
||||
/** @var string $notice */
|
||||
/** @var string $etape */
|
||||
/** @var string $titulaire */
|
||||
/** @var int $longueurMinimale */
|
||||
/** @var array|null $currentProfile */
|
||||
/** @var string $homeUrl */
|
||||
/** @var string $cspNonce */
|
||||
|
||||
$titres = [
|
||||
'jeton' => 'Connexion',
|
||||
'definition' => 'Choisissez votre mot de passe',
|
||||
'verification' => 'Mot de passe',
|
||||
'changement' => 'Changer de mot de passe',
|
||||
];
|
||||
$titre = $titres[$etape] ?? 'Connexion';
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Choisir votre accès — PTA</title>
|
||||
<link rel="stylesheet" href="assets/style.css?v=7.16">
|
||||
<title><?= htmlspecialchars($titre) ?> — PTA</title>
|
||||
<link rel="stylesheet" href="assets/style.css?v=7.23">
|
||||
</head>
|
||||
<body class="role-page-body">
|
||||
<main class="role-page-shell">
|
||||
<section class="role-intro">
|
||||
<p class="eyebrow">PTA</p>
|
||||
<h1>Quel est votre rôle ?</h1>
|
||||
<p>Votre choix détermine les pages visibles et les actions autorisées pendant cette session.</p>
|
||||
<?php if ($currentProfile): ?>
|
||||
<h1><?= htmlspecialchars($titre) ?></h1>
|
||||
|
||||
<?php if ($etape === 'jeton'): ?>
|
||||
<p>Saisissez le jeton d’accès qui vous a été remis. Il détermine votre rôle, les pages visibles et les actions autorisées.</p>
|
||||
<?php elseif ($etape === 'definition'): ?>
|
||||
<p>
|
||||
Votre compte <strong><?= htmlspecialchars($titulaire) ?></strong> engage l’application :
|
||||
il demande un second secret, connu de vous seul. Choisissez-le maintenant, il vous sera demandé à chaque connexion.
|
||||
</p>
|
||||
<?php elseif ($etape === 'verification'): ?>
|
||||
<p>Jeton reconnu. Saisissez le mot de passe de <strong><?= htmlspecialchars($titulaire) ?></strong> pour ouvrir la session.</p>
|
||||
<?php else: ?>
|
||||
<p>Votre mot de passe actuel vous est demandé : sans lui, un poste laissé ouvert suffirait à confisquer votre compte.</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($currentProfile && $etape === 'jeton'): ?>
|
||||
<div class="role-current-profile">
|
||||
Profil actuel : <strong><?= htmlspecialchars((string) $currentProfile['role_label']) ?></strong>
|
||||
Session ouverte : <strong><?= htmlspecialchars((string) $currentProfile['role_label']) ?></strong>
|
||||
— <?= htmlspecialchars((string) $currentProfile['label']) ?>
|
||||
<a class="button button-primary role-resume-link" href="<?= htmlspecialchars($homeUrl) ?>">Reprendre</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($notice !== ''): ?>
|
||||
<div class="message message-success"><?= htmlspecialchars($notice) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($error !== ''): ?>
|
||||
<div class="message message-error"><?= htmlspecialchars($error) ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<form method="post" class="role-selection-form" id="role-selection-form">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>">
|
||||
|
||||
<div class="role-choice-grid">
|
||||
<label class="role-choice-card">
|
||||
<input type="radio" name="role" value="AGENT" required>
|
||||
<span class="role-choice-content">
|
||||
<strong>Agent</strong>
|
||||
<small>Lecture de mon planning et du planning de mon lieu de rattachement.</small>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="role-choice-card">
|
||||
<input type="radio" name="role" value="RESPONSABLE_STRUCTURE" required>
|
||||
<span class="role-choice-content">
|
||||
<strong>Responsable de structure</strong>
|
||||
<small>Saisie et modification des horaires et motifs de mon lieu, uniquement en brouillon.</small>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="role-choice-card">
|
||||
<input type="radio" name="role" value="SERVICE_ENFANCE" required>
|
||||
<span class="role-choice-content">
|
||||
<strong>Service Enfance</strong>
|
||||
<small>Contrôle complet, prévisionnel N+1, administration et validation des plannings.</small>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="agent-context" class="role-context-card" hidden>
|
||||
<?php if ($etape === 'jeton'): ?>
|
||||
<form method="post" class="role-selection-form role-token-form" id="role-token-form">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>">
|
||||
<label class="field">
|
||||
<span>Je suis l’agent</span>
|
||||
<select name="agent_id" id="role-agent-select">
|
||||
<option value="">Choisir mon identité</option>
|
||||
<?php foreach ($agents as $agent): ?>
|
||||
<option value="<?= (int) $agent['id_agent'] ?>">
|
||||
<?= htmlspecialchars($agent['prenom'] . ' ' . $agent['nom'] . ' (' . $agent['matricule'] . ')') ?>
|
||||
<?= !empty($agent['structure_nom']) ? ' — ' . htmlspecialchars((string) $agent['structure_nom']) : '' ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<span>Jeton d’accès</span>
|
||||
<?php /* autocomplete="off" : un jeton n'a pas à être retenu par le navigateur d'un poste partagé. */ ?>
|
||||
<input
|
||||
type="text"
|
||||
name="token"
|
||||
id="role-token-input"
|
||||
class="role-token-input"
|
||||
placeholder="PTA-XXXXXX-XXXXXX-XXXXXX-XXXXXX"
|
||||
autocomplete="off"
|
||||
autocapitalize="characters"
|
||||
spellcheck="false"
|
||||
required
|
||||
autofocus>
|
||||
</label>
|
||||
</div>
|
||||
<div class="role-form-actions">
|
||||
<button type="submit" class="button button-primary">Continuer</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php elseif ($etape === 'definition' || $etape === 'verification'): ?>
|
||||
<form method="post" class="role-selection-form role-token-form">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>">
|
||||
<input type="hidden" name="etape" value="mot_de_passe">
|
||||
|
||||
<div id="responsable-context" class="role-context-card" hidden>
|
||||
<label class="field">
|
||||
<span>Je suis responsable du lieu</span>
|
||||
<select name="structure_id" id="role-structure-select">
|
||||
<option value="">Choisir le lieu d’affectation</option>
|
||||
<?php foreach ($structures as $structure): ?>
|
||||
<option value="<?= (int) $structure['id_structure'] ?>">
|
||||
<?= htmlspecialchars((string) $structure['nom']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<span><?= $etape === 'definition' ? 'Nouveau mot de passe' : 'Mot de passe' ?></span>
|
||||
<input
|
||||
type="password"
|
||||
name="mot_de_passe"
|
||||
autocomplete="<?= $etape === 'definition' ? 'new-password' : 'current-password' ?>"
|
||||
minlength="<?= (int) $longueurMinimale ?>"
|
||||
required
|
||||
autofocus>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="role-form-actions">
|
||||
<button type="submit" class="button button-primary">Accéder à PTA</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php if ($etape === 'definition'): ?>
|
||||
<label class="field">
|
||||
<span>Confirmation</span>
|
||||
<input type="password" name="mot_de_passe_confirmation" autocomplete="new-password" minlength="<?= (int) $longueurMinimale ?>" required>
|
||||
</label>
|
||||
<p class="hint">
|
||||
Au moins <?= (int) $longueurMinimale ?> caractères. Aucune règle de composition :
|
||||
une phrase dont vous vous souvenez protège mieux qu’un mot court hérissé de symboles.
|
||||
Il ne sera affiché nulle part — la DSI elle-même ne peut que l’effacer, jamais le lire.
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="role-form-actions">
|
||||
<button type="submit" class="button button-primary"><?= $etape === 'definition' ? 'Enregistrer et se connecter' : 'Se connecter' ?></button>
|
||||
</div>
|
||||
<p class="hint role-step-back">
|
||||
<a href="role.php?action=reset&csrf_token=<?= htmlspecialchars(urlencode($csrfToken), ENT_QUOTES) ?>">Recommencer avec un autre jeton</a>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<?php else: ?>
|
||||
<form method="post" class="role-selection-form role-token-form">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>">
|
||||
<input type="hidden" name="etape" value="changement">
|
||||
|
||||
<label class="field">
|
||||
<span>Mot de passe actuel</span>
|
||||
<input type="password" name="mot_de_passe_actuel" autocomplete="current-password" required autofocus>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Nouveau mot de passe</span>
|
||||
<input type="password" name="mot_de_passe" autocomplete="new-password" minlength="<?= (int) $longueurMinimale ?>" required>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Confirmation</span>
|
||||
<input type="password" name="mot_de_passe_confirmation" autocomplete="new-password" minlength="<?= (int) $longueurMinimale ?>" required>
|
||||
</label>
|
||||
<p class="hint">Au moins <?= (int) $longueurMinimale ?> caractères.</p>
|
||||
|
||||
<div class="role-form-actions">
|
||||
<button type="submit" class="button button-primary">Changer le mot de passe</button>
|
||||
</div>
|
||||
<p class="hint role-step-back">
|
||||
<a href="<?= htmlspecialchars($homeUrl) ?>">Revenir à l’application</a>
|
||||
</p>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<aside class="role-security-note">
|
||||
<strong>Version de démonstration :</strong> ce sélecteur organise les droits dans l’application, mais ne remplace pas une authentification nominative. En production, le rôle devra être associé au compte connecté.
|
||||
<?php if ($etape === 'changement'): ?>
|
||||
<strong>Un mot de passe oublié ne se retrouve pas :</strong> la DSI l’efface, et vous en choisissez un nouveau à votre connexion suivante, avec votre jeton.
|
||||
<?php else: ?>
|
||||
<strong>Jeton perdu ou compromis ?</strong> Contactez la DSI : elle en génère un nouveau, ce qui révoque immédiatement l’ancien.
|
||||
Un jeton vaut pour une personne — il ne se prête pas, puisque toutes les actions sont journalisées à son nom.
|
||||
Les comptes Service Enfance et DSI y ajoutent un mot de passe, que vous seul connaissez.
|
||||
<?php endif; ?>
|
||||
</aside>
|
||||
</main>
|
||||
<script nonce="<?= htmlspecialchars($cspNonce, ENT_QUOTES) ?>">
|
||||
(() => {
|
||||
const agentContext = document.querySelector('#agent-context');
|
||||
const responsableContext = document.querySelector('#responsable-context');
|
||||
const agentSelect = document.querySelector('#role-agent-select');
|
||||
const structureSelect = document.querySelector('#role-structure-select');
|
||||
const input = document.querySelector('#role-token-input');
|
||||
if (!input) return;
|
||||
|
||||
function refresh() {
|
||||
const role = document.querySelector('input[name="role"]:checked')?.value || '';
|
||||
agentContext.hidden = role !== 'AGENT';
|
||||
responsableContext.hidden = role !== 'RESPONSABLE_STRUCTURE';
|
||||
agentSelect.required = role === 'AGENT';
|
||||
structureSelect.required = role === 'RESPONSABLE_STRUCTURE';
|
||||
}
|
||||
|
||||
document.querySelectorAll('input[name="role"]').forEach((input) => input.addEventListener('change', refresh));
|
||||
refresh();
|
||||
/**
|
||||
* Met en forme la saisie au fil de la frappe : majuscules et suppression des
|
||||
* espaces. Les jetons sont recopiés depuis un courriel ou un papier, et un
|
||||
* copier-coller ramène presque toujours une espace ou un retour à la ligne.
|
||||
* Le serveur normalise de toute façon (CompteModel::normaliser) ; ceci n'est
|
||||
* qu'un confort de saisie — et cela ne concerne QUE le jeton, jamais le mot
|
||||
* de passe, dont chaque caractère compte tel qu'il est frappé.
|
||||
*/
|
||||
input.addEventListener('input', () => {
|
||||
const position = input.selectionStart;
|
||||
const nettoye = input.value.toUpperCase().replace(/\s+/g, '');
|
||||
if (nettoye !== input.value) {
|
||||
input.value = nettoye;
|
||||
input.setSelectionRange(position, position);
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user