277 lines
14 KiB
PHP
277 lines
14 KiB
PHP
<?php
|
||
/**
|
||
* MSIN-4464 — Super admin : gestion whitelist + rétention traçabilité fiche.
|
||
*/
|
||
|
||
require_once dirname(__DIR__, 2) . '/php/inc_fx_fiche_audit.php';
|
||
|
||
/**
|
||
* @param string $strFlash
|
||
* @param string $strError
|
||
*/
|
||
function fxFicheAuditAdminRenderPage($strFlash = '', $strError = '')
|
||
{
|
||
$blnCanEdit = fxAdminCanEditFicheAudit();
|
||
$blnTablesOk = fxFicheAuditTablesReady();
|
||
$arrSettings = fxFicheAuditGetSettings();
|
||
$arrFields = fxFicheAuditGetFieldsForAdmin();
|
||
$strDisabled = ($blnCanEdit && $blnTablesOk) ? '' : ' disabled';
|
||
$intEveLogs = isset($_GET['eve_id']) ? intval($_GET['eve_id']) : 0;
|
||
$intLimitLogs = (isset($_GET['limit']) && intval($_GET['limit']) === 200) ? 200 : 100;
|
||
$blnShowLogs = ($blnTablesOk && $intEveLogs > 0 && isset($_GET['voir_logs']));
|
||
$arrEvents = $blnTablesOk ? fxFicheAuditAdminListEvents() : array();
|
||
$arrLogs = $blnShowLogs ? fxFicheAuditAdminListRecentByEve($intEveLogs, $intLimitLogs) : array();
|
||
|
||
if ($strFlash !== '') {
|
||
echo '<div class="alert alert-success">' . htmlspecialchars($strFlash, ENT_QUOTES, 'UTF-8') . '</div>';
|
||
}
|
||
if ($strError !== '') {
|
||
echo '<div class="alert alert-danger">' . htmlspecialchars($strError, ENT_QUOTES, 'UTF-8') . '</div>';
|
||
}
|
||
if (!$blnTablesOk) {
|
||
echo '<div class="alert alert-warning">'
|
||
. 'Tables absentes. Exécuter <code>sql/MSIN-4464-fiche-audit-config.sql</code> puis recharger.'
|
||
. '</div>';
|
||
}
|
||
?>
|
||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||
<h1 class="mb-0">Traçabilité fiche</h1>
|
||
</div>
|
||
|
||
<?php if (!$blnCanEdit) { ?>
|
||
<div class="alert alert-info py-2">
|
||
Lecture seule : vous pouvez consulter la configuration et les logs. Modification réservée.
|
||
</div>
|
||
<?php } ?>
|
||
|
||
<div class="card mb-3 border-info">
|
||
<div class="card-header py-2 bg-light">
|
||
<strong><i class="fa fa-info-circle text-info"></i> Comment ça marche ?</strong>
|
||
</div>
|
||
<div class="card-body py-3 small">
|
||
<ul class="mb-2 pl-3">
|
||
<li><strong>Whitelist</strong> — seuls les champs cochés seront historisés en détail (ancienne → nouvelle valeur + personne).</li>
|
||
<li><strong>Dernière intervention</strong> — si activée, on note aussi qui a touché la fiche, même pour un champ hors whitelist.</li>
|
||
<li><strong>Rétention</strong> — on ne garde pas un log sans fin : durée max + nombre max d'entrées par champ × participant.</li>
|
||
</ul>
|
||
<p class="mb-0 text-muted">
|
||
Points branchés : statut, dossard manuel, dossard remis, annulation/rétablissement, édition fiche,
|
||
batch dossards, surplus changement d'épreuve, transfert/upgrade.
|
||
L'affichage « ? » sur la fiche et le droit promoteur associé viennent ensuite.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<form method="post" action="fiche_audit.php"<?= ($blnCanEdit && $blnTablesOk) ? '' : ' onsubmit="return false;"' ?>>
|
||
<input type="hidden" name="action" value="save_fiche_audit">
|
||
|
||
<div class="card mb-3">
|
||
<div class="card-header py-2">
|
||
<strong>Rétention</strong>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="form-row">
|
||
<div class="form-group col-md-4">
|
||
<label for="retention_days">Conserver pendant (jours)</label>
|
||
<input type="number" class="form-control" id="retention_days" name="retention_days"
|
||
min="1" max="3650" value="<?= intval($arrSettings['retention_days']) ?>"<?= $strDisabled ?>>
|
||
</div>
|
||
<div class="form-group col-md-4">
|
||
<label for="retention_max_per_field">Max d'entrées / champ / participant</label>
|
||
<input type="number" class="form-control" id="retention_max_per_field" name="retention_max_per_field"
|
||
min="1" max="100" value="<?= intval($arrSettings['retention_max_per_field']) ?>"<?= $strDisabled ?>>
|
||
</div>
|
||
</div>
|
||
<div class="custom-control custom-checkbox">
|
||
<input type="checkbox" class="custom-control-input" id="track_last_touch" name="track_last_touch" value="1"
|
||
<?= intval($arrSettings['track_last_touch']) === 1 ? ' checked' : '' ?><?= $strDisabled ?>>
|
||
<label class="custom-control-label" for="track_last_touch">
|
||
Noter aussi la dernière personne qui a touché la fiche (sans détail de champ)
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card mb-3">
|
||
<div class="card-header py-2">
|
||
<strong>Whitelist — champs à tracer en détail</strong>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
<div class="table-responsive">
|
||
<table class="table table-sm table-hover mb-0">
|
||
<thead class="thead-light">
|
||
<tr>
|
||
<th style="width:4rem;">Actif</th>
|
||
<th>Champ</th>
|
||
<th>Clé technique</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($arrFields as $arrField) {
|
||
$strKey = (string)$arrField['field_key'];
|
||
$strId = 'field_' . preg_replace('/[^a-z0-9_]/i', '_', $strKey);
|
||
$blnActif = (intval($arrField['field_actif']) === 1);
|
||
?>
|
||
<tr>
|
||
<td class="text-center align-middle">
|
||
<input type="checkbox" id="<?= htmlspecialchars($strId, ENT_QUOTES, 'UTF-8') ?>"
|
||
name="field_actif[]"
|
||
value="<?= htmlspecialchars($strKey, ENT_QUOTES, 'UTF-8') ?>"
|
||
<?= $blnActif ? ' checked' : '' ?><?= $strDisabled ?>>
|
||
</td>
|
||
<td>
|
||
<label class="mb-0" for="<?= htmlspecialchars($strId, ENT_QUOTES, 'UTF-8') ?>">
|
||
<?= htmlspecialchars($arrField['field_label_fr'], ENT_QUOTES, 'UTF-8') ?>
|
||
</label>
|
||
<div class="text-muted small">
|
||
<?= htmlspecialchars($arrField['field_label_en'], ENT_QUOTES, 'UTF-8') ?>
|
||
</div>
|
||
</td>
|
||
<td class="align-middle"><code><?= htmlspecialchars($strKey, ENT_QUOTES, 'UTF-8') ?></code></td>
|
||
</tr>
|
||
<?php } ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<?php if ($blnCanEdit && $blnTablesOk) { ?>
|
||
<button type="submit" class="btn btn-primary">
|
||
<i class="fa fa-save" aria-hidden="true"></i> Enregistrer
|
||
</button>
|
||
<?php } ?>
|
||
</form>
|
||
|
||
<hr class="my-4">
|
||
|
||
<div class="card mb-3">
|
||
<div class="card-header py-2">
|
||
<strong><i class="fa fa-list" aria-hidden="true"></i> Contrôle des logs (admin)</strong>
|
||
</div>
|
||
<div class="card-body">
|
||
<p class="small text-muted mb-3">
|
||
Affiche les dernières entrées enregistrées pour un événement — utile pour valider que ça logue bien.
|
||
</p>
|
||
<?php if (!$blnTablesOk) { ?>
|
||
<p class="text-muted mb-0">Tables absentes — impossible d'afficher les logs.</p>
|
||
<?php } else { ?>
|
||
<form method="get" action="fiche_audit.php" class="form-row align-items-end">
|
||
<input type="hidden" name="voir_logs" value="1">
|
||
<div class="form-group col-md-6 mb-2">
|
||
<label for="eve_id_logs">Événement</label>
|
||
<select class="form-control" id="eve_id_logs" name="eve_id" required>
|
||
<option value="">— choisir —</option>
|
||
<?php foreach ($arrEvents as $arrEve) {
|
||
$intEveOpt = intval($arrEve['eve_id'] ?? 0);
|
||
if ($intEveOpt <= 0) {
|
||
continue;
|
||
}
|
||
$strNomEve = trim((string)($arrEve['eve_nom_fr'] ?? ''));
|
||
if ($strNomEve === '') {
|
||
$strNomEve = 'Événement #' . $intEveOpt;
|
||
}
|
||
$intNb = intval($arrEve['fal_nb'] ?? 0);
|
||
$strDate = trim((string)($arrEve['eve_date_debut'] ?? ''));
|
||
$strLabel = $strNomEve
|
||
. ($strDate !== '' && $strDate !== '0000-00-00' ? ' (' . $strDate . ')' : '')
|
||
. ' — #' . $intEveOpt
|
||
. ($intNb > 0 ? ' · ' . $intNb . ' log(s)' : '');
|
||
?>
|
||
<option value="<?= $intEveOpt ?>"<?= $intEveLogs === $intEveOpt ? ' selected' : '' ?>>
|
||
<?= htmlspecialchars($strLabel, ENT_QUOTES, 'UTF-8') ?>
|
||
</option>
|
||
<?php } ?>
|
||
</select>
|
||
</div>
|
||
<div class="form-group col-md-6 mb-2">
|
||
<button type="submit" name="limit" value="100" class="btn btn-outline-primary mr-2">
|
||
100 derniers
|
||
</button>
|
||
<button type="submit" name="limit" value="200" class="btn btn-outline-primary">
|
||
200 derniers
|
||
</button>
|
||
</div>
|
||
</form>
|
||
<?php } ?>
|
||
</div>
|
||
</div>
|
||
|
||
<?php if ($blnShowLogs) {
|
||
fxFicheAuditAdminRenderLogsTable($intEveLogs, $intLimitLogs, $arrLogs);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param array[] $arrLogs
|
||
*/
|
||
function fxFicheAuditAdminRenderLogsTable($intEveId, $intLimit, $arrLogs)
|
||
{
|
||
$intEveId = intval($intEveId);
|
||
$intLimit = intval($intLimit);
|
||
$intCount = is_array($arrLogs) ? count($arrLogs) : 0;
|
||
?>
|
||
<div class="card mb-3" id="fiche-audit-logs">
|
||
<div class="card-header py-2 d-flex justify-content-between align-items-center">
|
||
<strong>
|
||
Logs — événement #<?= $intEveId ?>
|
||
<span class="text-muted font-weight-normal">(<?= $intCount ?> / <?= $intLimit ?> max)</span>
|
||
</strong>
|
||
<a class="btn btn-sm btn-outline-secondary" href="fiche_audit.php">Fermer</a>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
<?php if ($intCount === 0) { ?>
|
||
<p class="p-3 mb-0 text-muted">Aucun log pour cet événement.</p>
|
||
<?php } else { ?>
|
||
<div class="table-responsive">
|
||
<table class="table table-sm table-striped table-hover mb-0 small">
|
||
<thead class="thead-light">
|
||
<tr>
|
||
<th>Quand</th>
|
||
<th>Participant</th>
|
||
<th>Champ</th>
|
||
<th>Avant</th>
|
||
<th>Après</th>
|
||
<th>Par</th>
|
||
<th>Source</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($arrLogs as $row) {
|
||
if (!is_array($row)) {
|
||
continue;
|
||
}
|
||
$strNom = trim(
|
||
(string)($row['par_prenom'] ?? '') . ' ' . (string)($row['par_nom'] ?? '')
|
||
);
|
||
if ($strNom === '') {
|
||
$strNom = 'par#' . intval($row['par_id'] ?? 0);
|
||
}
|
||
$strBib = trim((string)($row['no_bib'] ?? ''));
|
||
if ($strBib !== '') {
|
||
$strNom .= ' · #' . $strBib;
|
||
}
|
||
$strField = (string)($row['field_key'] ?? '');
|
||
if ($strField === 'last_touch') {
|
||
$strField = 'dernière intervention';
|
||
}
|
||
?>
|
||
<tr>
|
||
<td class="text-nowrap"><?= htmlspecialchars((string)($row['created_at'] ?? ''), ENT_QUOTES, 'UTF-8') ?></td>
|
||
<td><?= htmlspecialchars($strNom, ENT_QUOTES, 'UTF-8') ?></td>
|
||
<td><code><?= htmlspecialchars($strField, ENT_QUOTES, 'UTF-8') ?></code></td>
|
||
<td><?= htmlspecialchars((string)($row['old_value'] ?? ''), ENT_QUOTES, 'UTF-8') ?></td>
|
||
<td><?= htmlspecialchars((string)($row['new_value'] ?? ''), ENT_QUOTES, 'UTF-8') ?></td>
|
||
<td><?= htmlspecialchars((string)($row['changed_by_label'] ?? ''), ENT_QUOTES, 'UTF-8') ?></td>
|
||
<td><code><?= htmlspecialchars((string)($row['source'] ?? ''), ENT_QUOTES, 'UTF-8') ?></code></td>
|
||
</tr>
|
||
<?php } ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<?php } ?>
|
||
</div>
|
||
</div>
|
||
<?php
|
||
}
|