Files
ms1inscription-v5/superadm/php/inc_fx_eve_acces_admin.php

1222 lines
54 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
/**
* Super admin — gestion des kits d'acces v2 (roles + matrice permissions).
*/
require_once dirname(__DIR__, 2) . '/php/inc_fx_eve_acces.php';
function fxEveAccesAdminHasDelegableColumn()
{
global $objDatabase;
static $blnHas = null;
if ($blnHas !== null) {
return $blnHas;
}
if (!fxEveAccesIsEnabled()) {
$blnHas = false;
return $blnHas;
}
$tab = $objDatabase->fxGetResults("SHOW COLUMNS FROM inscriptions_eve_roles LIKE 'role_delegable'");
$blnHas = ($tab != null && count($tab) > 0);
return $blnHas;
}
function fxEveAccesAdminHasGrantsAllColumn()
{
global $objDatabase;
static $blnHas = null;
if ($blnHas !== null) {
return $blnHas;
}
if (!fxEveAccesIsEnabled()) {
$blnHas = false;
return $blnHas;
}
$tab = $objDatabase->fxGetResults("SHOW COLUMNS FROM inscriptions_eve_roles LIKE 'role_grants_all'");
$blnHas = ($tab != null && count($tab) > 0);
return $blnHas;
}
/** MSIN-4401 — Colonne kit : forcer sections fiche inscriptions ouvertes. */
function fxEveAccesAdminHasFicheSectionsOpenColumn()
{
global $objDatabase;
static $blnHas = null;
if ($blnHas !== null) {
return $blnHas;
}
if (!fxEveAccesIsEnabled()) {
$blnHas = false;
return $blnHas;
}
$tab = $objDatabase->fxGetResults("SHOW COLUMNS FROM inscriptions_eve_roles LIKE 'role_fiche_sections_open'");
$blnHas = ($tab != null && count($tab) > 0);
return $blnHas;
}
function fxEveAccesAdminPermGroupLabels()
{
// MSIN-4401 — niveau 1 = pages hub ; niveau 2 = détails (pas de jargon API mobile)
return array(
'page_hub' => '1. Pages du hub (accès menu)',
'inscriptions_v2' => '2. Inscriptions V2 — détails',
'dossards_detail' => '2. Dossards / quantités — détails',
'rabais_detail' => '2. Rabais — détails',
'acces_detail' => '2. Accès V2 — détails',
'rapports_autres' => 'Rapports (autres)',
'autre' => 'Autres',
// Anciens groupes (si données pas encore migrées)
'api' => 'Ancien (inactif)',
'inscriptions' => 'Inscriptions',
'dossards' => 'Dossards',
'epreuves' => 'Épreuves',
'rabais' => 'Rabais',
'emails' => 'Courriels',
'reports' => 'Rapports',
'team' => 'Équipe',
'tools' => 'Outils',
'action' => 'Actions',
'general' => 'Général',
);
}
/** Ordre d'affichage des groupes dans le formulaire kit. */
function fxEveAccesAdminPermGroupOrder()
{
return array(
'page_hub',
'inscriptions_v2',
'dossards_detail',
'rabais_detail',
'acces_detail',
'rapports_autres',
'autre',
);
}
function fxEveAccesAdminNormalizeRoleCode($strCode)
{
$strCode = strtolower(trim((string)$strCode));
$strCode = preg_replace('/[^a-z0-9_]+/', '_', $strCode);
$strCode = preg_replace('/_+/', '_', $strCode);
$strCode = trim($strCode, '_');
return substr($strCode, 0, 32);
}
function fxEveAccesAdminRoleCodeExists($strCode, $intExcludeRoleId = 0)
{
global $objDatabase;
$sql = "SELECT COUNT(*) FROM inscriptions_eve_roles
WHERE role_code = '" . $objDatabase->fxEscape($strCode) . "'";
if (intval($intExcludeRoleId) > 0) {
$sql .= ' AND role_id <> ' . intval($intExcludeRoleId);
}
return intval($objDatabase->fxGetVar($sql)) > 0;
}
function fxEveAccesAdminGetRolesForList($blnActifOnly = false)
{
global $objDatabase;
if (!fxEveAccesIsEnabled()) {
return array();
}
$strDelegable = fxEveAccesAdminHasDelegableColumn() ? ', r.role_delegable' : ', 0 AS role_delegable';
$strGrantsAll = fxEveAccesAdminHasGrantsAllColumn() ? ', r.role_grants_all' : ', 0 AS role_grants_all';
$strFicheOpen = fxEveAccesAdminHasFicheSectionsOpenColumn()
? ', r.role_fiche_sections_open'
: ', 0 AS role_fiche_sections_open';
$sql = "SELECT r.role_id, r.role_code, r.role_label_fr, r.role_label_en,
r.role_description_fr, r.role_icone, r.role_systeme, r.role_actif, r.role_tri
{$strDelegable}{$strGrantsAll}{$strFicheOpen},
(SELECT COUNT(*) FROM inscriptions_eve_role_permissions erp WHERE erp.role_id = r.role_id) AS perm_count,
(SELECT COUNT(*) FROM inscriptions_eve_acces ea
WHERE ea.role_id = r.role_id AND ea.ea_statut = 'actif') AS assign_count,
(SELECT COUNT(*) FROM inscriptions_eve_acces ea
WHERE ea.role_id = r.role_id AND ea.ea_statut <> 'actif') AS history_assign_count
FROM inscriptions_eve_roles r";
if ($blnActifOnly) {
$sql .= ' WHERE r.role_actif = 1';
}
$sql .= ' ORDER BY r.role_tri ASC, r.role_label_fr ASC';
return $objDatabase->fxGetResults($sql);
}
function fxEveAccesAdminGetRoleById($intRoleId)
{
global $objDatabase;
if (!fxEveAccesIsEnabled() || intval($intRoleId) <= 0) {
return null;
}
$strDelegable = fxEveAccesAdminHasDelegableColumn() ? ', role_delegable' : ', 0 AS role_delegable';
$strGrantsAll = fxEveAccesAdminHasGrantsAllColumn() ? ', role_grants_all' : ', 0 AS role_grants_all';
$strFicheOpen = fxEveAccesAdminHasFicheSectionsOpenColumn()
? ', role_fiche_sections_open'
: ', 0 AS role_fiche_sections_open';
$sql = "SELECT role_id, role_code, role_label_fr, role_label_en,
role_description_fr, role_description_en, role_icone,
role_systeme, role_actif, role_tri {$strDelegable}{$strGrantsAll}{$strFicheOpen}
FROM inscriptions_eve_roles
WHERE role_id = " . intval($intRoleId) . "
LIMIT 1";
return $objDatabase->fxGetRow($sql);
}
function fxEveAccesAdminGetRolePermKeys($intRoleId)
{
global $objDatabase;
if (!fxEveAccesIsEnabled() || intval($intRoleId) <= 0) {
return array();
}
$sql = "SELECT perm_key FROM inscriptions_eve_role_permissions
WHERE role_id = " . intval($intRoleId) . "
ORDER BY perm_key ASC";
$tab = $objDatabase->fxGetResults($sql);
$arrKeys = array();
if ($tab != null) {
foreach ($tab as $row) {
$arrKeys[] = $row['perm_key'];
}
}
return $arrKeys;
}
function fxEveAccesAdminCountActiveAssignments($intRoleId)
{
global $objDatabase;
$sql = "SELECT COUNT(*) FROM inscriptions_eve_acces
WHERE role_id = " . intval($intRoleId) . "
AND ea_statut = 'actif'";
return intval($objDatabase->fxGetVar($sql));
}
function fxEveAccesAdminCountAllAssignments($intRoleId)
{
global $objDatabase;
$sql = "SELECT COUNT(*) FROM inscriptions_eve_acces
WHERE role_id = " . intval($intRoleId);
return intval($objDatabase->fxGetVar($sql));
}
function fxEveAccesAdminCanDeleteRole($intRoleId)
{
$row = fxEveAccesAdminGetRoleById($intRoleId);
if ($row === null) {
return array('can' => false, 'message' => 'Kit introuvable.');
}
$intActive = fxEveAccesAdminCountActiveAssignments($intRoleId);
if ($intActive > 0) {
return array(
'can' => false,
'message' => 'Ce kit est assigné à ' . $intActive . ' compte(s). Retirez les assignations sur la fiche compte avant de supprimer.',
);
}
return array('can' => true, 'message' => '');
}
function fxEveAccesAdminDeleteRole($intRoleId)
{
global $objDatabase;
$arrCheck = fxEveAccesAdminCanDeleteRole($intRoleId);
if (!$arrCheck['can']) {
return array('state' => 'error', 'message' => $arrCheck['message']);
}
$intRoleId = intval($intRoleId);
$objDatabase->fxQuery('DELETE FROM inscriptions_eve_acces WHERE role_id = ' . $intRoleId);
$objDatabase->fxQuery('DELETE FROM inscriptions_eve_role_permissions WHERE role_id = ' . $intRoleId);
$objDatabase->fxQuery('DELETE FROM inscriptions_eve_roles WHERE role_id = ' . $intRoleId);
return array('state' => 'ok', 'message' => 'Kit supprimé.');
}
/**
* Assignations d'un kit (actives et historique).
*
* @return array<int, array<string, mixed>>
*/
function fxEveAccesAdminGetRoleAssignments($intRoleId, $strStatut = null)
{
global $objDatabase;
if (!fxEveAccesIsEnabled() || intval($intRoleId) <= 0) {
return array();
}
$sql = "SELECT ea.ea_id, ea.com_id, ea.eve_id, ea.ea_statut, ea.ea_created_at, ea.ea_revoked_at,
c.com_nom, c.com_prenom, c.com_courriel, c.com_lastvisit,
e.eve_nom_fr, e.eve_date_fin
FROM inscriptions_eve_acces ea
INNER JOIN inscriptions_comptes c ON c.com_id = ea.com_id
INNER JOIN inscriptions_evenements e ON e.eve_id = ea.eve_id
WHERE ea.role_id = " . intval($intRoleId);
if ($strStatut === 'actif') {
$sql .= " AND ea.ea_statut = 'actif'";
} elseif ($strStatut !== null && $strStatut !== '') {
$sql .= " AND ea.ea_statut = '" . $objDatabase->fxEscape($strStatut) . "'";
}
$sql .= " ORDER BY FIELD(ea.ea_statut, 'actif', 'invite', 'revoke'),
e.eve_date_fin DESC, c.com_nom ASC, c.com_prenom ASC";
$tab = $objDatabase->fxGetResults($sql);
return ($tab != null) ? $tab : array();
}
/**
* Tableau des comptes assignés à un kit (réutilisé fiche kit + page dédiée).
*/
function fxEveAccesAdminRenderRoleAssignmentsBlock($intRoleId, $arrOpts = array())
{
$blnShowFilters = !empty($arrOpts['filters']);
$strAnchorId = isset($arrOpts['anchor_id']) ? (string)$arrOpts['anchor_id'] : '';
$strFilter = $arrOpts['statut'] ?? 'actif';
if (!in_array($strFilter, array('actif', 'all', 'revoke', 'invite'), true)) {
$strFilter = 'actif';
}
$intActive = fxEveAccesAdminCountActiveAssignments($intRoleId);
$intTotal = fxEveAccesAdminCountAllAssignments($intRoleId);
$strStatutSql = ($strFilter === 'all') ? null : $strFilter;
$arrRows = fxEveAccesAdminGetRoleAssignments($intRoleId, $strStatutSql);
?>
<div class="card mb-3"<?= $strAnchorId !== '' ? ' id="' . htmlspecialchars($strAnchorId, ENT_QUOTES, 'UTF-8') . '"' : '' ?>>
<div class="card-header d-flex justify-content-between align-items-center flex-wrap">
<span><i class="fa fa-users"></i> Personnes assignées</span>
<span class="small text-muted">
<?= intval($intActive) ?> actif(s)
<?php if ($intTotal > $intActive) { ?>
— <?= intval($intTotal - $intActive) ?> dans l'historique
<?php } ?>
</span>
</div>
<div class="card-body py-3">
<p class="small text-muted mb-2">
Pour <strong>ajouter ou retirer</strong> un kit, utilisez la fiche compte (section Accès événement v2).
</p>
<?php if ($blnShowFilters) { ?>
<p class="mb-2">
<?php
$arrFilters = array(
'actif' => 'Actifs (' . intval($intActive) . ')',
'all' => 'Tous (' . intval($intTotal) . ')',
'revoke' => 'Révoqués',
'invite' => 'Invitations',
);
foreach ($arrFilters as $strKey => $strLabel) {
$blnOn = ($strFilter === $strKey);
echo '<a href="eve_acces.php?p=assignations&amp;id=' . intval($intRoleId)
. '&amp;statut=' . urlencode($strKey) . '" class="btn btn-sm btn-outline-secondary'
. ($blnOn ? ' active' : '') . ' mr-1">' . htmlspecialchars($strLabel, ENT_QUOTES, 'UTF-8') . '</a>';
}
?>
</p>
<?php } elseif ($intTotal > $intActive) { ?>
<p class="mb-2">
<a href="eve_acces.php?p=assignations&amp;id=<?= intval($intRoleId) ?>&amp;statut=all" class="btn btn-sm btn-outline-secondary">
Voir l'historique complet
</a>
</p>
<?php } ?>
<div class="table-responsive mb-0">
<table class="table table-sm table-striped table-hover mb-0">
<thead class="thead-light">
<tr>
<th>Compte</th>
<th>Événement</th>
<th class="text-center">Statut</th>
<th>Assigné le</th>
<th>Dernière connexion</th>
<th></th>
</tr>
</thead>
<tbody>
<?php
if (count($arrRows) === 0) {
echo '<tr><td colspan="6" class="text-muted">Aucune assignation.</td></tr>';
} else {
foreach ($arrRows as $arrAssign) {
$strCompte = trim($arrAssign['com_prenom'] . ' ' . $arrAssign['com_nom']);
$strStatut = (string)$arrAssign['ea_statut'];
$strBadge = 'secondary';
if ($strStatut === 'actif') {
$strBadge = 'success';
} elseif ($strStatut === 'invite') {
$strBadge = 'info';
} elseif ($strStatut === 'revoke') {
$strBadge = 'warning';
}
$strCompteUrl = fxEveAccesCompteAdminUrl(intval($arrAssign['com_id']), intval($arrAssign['eve_id']));
?>
<tr>
<td>
<div><?= htmlspecialchars($strCompte, ENT_QUOTES, 'UTF-8') ?></div>
<div class="small text-muted"><?= htmlspecialchars($arrAssign['com_courriel'], ENT_QUOTES, 'UTF-8') ?></div>
</td>
<td>
<div><?= htmlspecialchars($arrAssign['eve_nom_fr'], ENT_QUOTES, 'UTF-8') ?></div>
<div class="small text-muted">#<?= intval($arrAssign['eve_id']) ?></div>
</td>
<td class="text-center">
<span class="badge badge-<?= $strBadge ?>"><?= htmlspecialchars($strStatut, ENT_QUOTES, 'UTF-8') ?></span>
</td>
<td class="text-nowrap small"><?= htmlspecialchars(fxEveAccesAdminFormatReportDatetime($arrAssign['ea_created_at']), ENT_QUOTES, 'UTF-8') ?></td>
<td class="text-nowrap small"><?= htmlspecialchars(fxEveAccesAdminFormatReportDatetime($arrAssign['com_lastvisit']), ENT_QUOTES, 'UTF-8') ?></td>
<td class="text-right text-nowrap">
<a class="btn btn-sm btn-outline-primary" href="<?= htmlspecialchars($strCompteUrl, ENT_QUOTES, 'UTF-8') ?>">
Fiche compte
</a>
</td>
</tr>
<?php
}
}
?>
</tbody>
</table>
</div>
</div>
</div>
<?php
}
function fxEveAccesAdminRenderKitAssignmentsPage($intRoleId, $strFlash = '')
{
if (!fxEveAccesIsEnabled()) {
echo '<div class="alert alert-warning">Tables acces v2 non installees.</div>';
return;
}
$row = fxEveAccesAdminGetRoleById($intRoleId);
if ($row === null) {
echo '<div class="alert alert-danger">Kit introuvable.</div>';
return;
}
$strFilter = $_GET['statut'] ?? 'actif';
if (!in_array($strFilter, array('actif', 'all', 'revoke', 'invite'), true)) {
$strFilter = 'actif';
}
if ($strFlash !== '') {
echo '<div class="alert alert-success">' . htmlspecialchars($strFlash, ENT_QUOTES, 'UTF-8') . '</div>';
}
?>
<div class="mb-3">
<a href="eve_acces.php?p=kits" class="btn btn-sm btn-outline-secondary">
<i class="fa fa-chevron-left"></i> Retour aux kits
</a>
<a href="eve_acces.php?p=kit&amp;id=<?= intval($intRoleId) ?>" class="btn btn-sm btn-outline-primary ml-1">
<i class="fa fa-pencil"></i> Modifier le kit
</a>
</div>
<h1 class="mb-2">Assignations — <?= htmlspecialchars($row['role_label_fr'], ENT_QUOTES, 'UTF-8') ?></h1>
<p class="text-muted mb-3">
Code <code><?= htmlspecialchars($row['role_code'], ENT_QUOTES, 'UTF-8') ?></code>.
<a href="eve_acces.php?p=kit&amp;id=<?= intval($intRoleId) ?>#kit-assignations">Retour à la fiche kit</a>
</p>
<?php
fxEveAccesAdminRenderRoleAssignmentsBlock($intRoleId, array(
'filters' => true,
'statut' => $strFilter,
));
}
function fxEveAccesAdminSaveRole($arrData, $arrPermKeys)
{
global $objDatabase;
if (!fxEveAccesIsEnabled()) {
return array('state' => 'error', 'message' => 'Tables acces v2 non installees.');
}
$intRoleId = intval($arrData['role_id'] ?? 0);
$rowExisting = ($intRoleId > 0) ? fxEveAccesAdminGetRoleById($intRoleId) : null;
$strLabelFr = trim((string)($arrData['role_label_fr'] ?? ''));
$strLabelEn = trim((string)($arrData['role_label_en'] ?? ''));
if ($strLabelFr === '') {
return array('state' => 'error', 'message' => 'Le libelle FR est obligatoire.');
}
if ($strLabelEn === '') {
$strLabelEn = $strLabelFr;
}
$strCode = fxEveAccesAdminNormalizeRoleCode($arrData['role_code'] ?? '');
if ($strCode === '' || !preg_match('/^[a-z][a-z0-9_]{0,31}$/', $strCode)) {
return array('state' => 'error', 'message' => 'Code invalide (a-z, 0-9, _, min. 2 caracteres).');
}
$blnCodeLocked = ($rowExisting !== null && ($rowExisting['role_code'] ?? '') === 'migration_legacy');
if ($blnCodeLocked) {
$strCode = $rowExisting['role_code'];
} elseif (fxEveAccesAdminRoleCodeExists($strCode, $intRoleId)) {
return array('state' => 'error', 'message' => 'Ce code de kit existe deja.');
}
$strDescFr = trim((string)($arrData['role_description_fr'] ?? ''));
$strDescEn = trim((string)($arrData['role_description_en'] ?? ''));
$strIcone = trim((string)($arrData['role_icone'] ?? ''));
if ($strIcone !== '' && !preg_match('/^fa-[a-z0-9-]+$/', $strIcone)) {
$strIcone = '';
}
$intTri = intval($arrData['role_tri'] ?? 0);
$blnActif = !empty($arrData['role_actif']) ? 1 : 0;
$blnDelegable = !empty($arrData['role_delegable']) ? 1 : 0;
$blnGrantsAll = !empty($arrData['role_grants_all']) ? 1 : 0;
$blnFicheOpen = !empty($arrData['role_fiche_sections_open']) ? 1 : 0;
if ($blnCodeLocked) {
$blnGrantsAll = 1;
}
// MSIN-4478 — kit total + kits systeme : jamais redistribuables par un promoteur
// (évite qu'une invitation donne rapports / argent / tout).
if ($blnCodeLocked || $blnGrantsAll || in_array($strCode, array('owner', 'qr_debug'), true)) {
$blnDelegable = 0;
}
$arrValidKeys = array();
if (!$blnGrantsAll && !empty($arrPermKeys)) {
$arrCatalog = fxEveAccesGetCatalogPermissions(null, true);
$arrCatalogKeys = array();
if ($arrCatalog != null) {
foreach ($arrCatalog as $rowPerm) {
$arrCatalogKeys[$rowPerm['perm_key']] = true;
}
}
foreach ($arrPermKeys as $strKey) {
$strKey = trim((string)$strKey);
if ($strKey !== '' && isset($arrCatalogKeys[$strKey])) {
$arrValidKeys[$strKey] = true;
}
}
}
if ($intRoleId > 0 && $rowExisting === null) {
return array('state' => 'error', 'message' => 'Kit introuvable.');
}
if ($intRoleId <= 0) {
$sqlInsert = "INSERT INTO inscriptions_eve_roles SET
role_code = '" . $objDatabase->fxEscape($strCode) . "',
role_label_fr = '" . $objDatabase->fxEscape($strLabelFr) . "',
role_label_en = '" . $objDatabase->fxEscape($strLabelEn) . "',
role_description_fr = " . ($strDescFr !== '' ? "'" . $objDatabase->fxEscape($strDescFr) . "'" : 'NULL') . ",
role_description_en = " . ($strDescEn !== '' ? "'" . $objDatabase->fxEscape($strDescEn) . "'" : 'NULL') . ",
role_icone = " . ($strIcone !== '' ? "'" . $objDatabase->fxEscape($strIcone) . "'" : 'NULL') . ",
role_systeme = 0,
role_actif = " . intval($blnActif) . ",
role_tri = " . intval($intTri);
if (fxEveAccesAdminHasDelegableColumn()) {
$sqlInsert .= ', role_delegable = ' . intval($blnDelegable);
}
if (fxEveAccesAdminHasGrantsAllColumn()) {
$sqlInsert .= ', role_grants_all = ' . intval($blnGrantsAll);
}
if (fxEveAccesAdminHasFicheSectionsOpenColumn()) {
$sqlInsert .= ', role_fiche_sections_open = ' . intval($blnFicheOpen);
}
$objDatabase->fxQuery($sqlInsert);
$intRoleId = intval($objDatabase->fxGetVar('SELECT LAST_INSERT_ID()'));
} else {
$sqlUpdate = "UPDATE inscriptions_eve_roles SET
role_code = '" . $objDatabase->fxEscape($strCode) . "',
role_label_fr = '" . $objDatabase->fxEscape($strLabelFr) . "',
role_label_en = '" . $objDatabase->fxEscape($strLabelEn) . "',
role_description_fr = " . ($strDescFr !== '' ? "'" . $objDatabase->fxEscape($strDescFr) . "'" : 'NULL') . ",
role_description_en = " . ($strDescEn !== '' ? "'" . $objDatabase->fxEscape($strDescEn) . "'" : 'NULL') . ",
role_icone = " . ($strIcone !== '' ? "'" . $objDatabase->fxEscape($strIcone) . "'" : 'NULL') . ",
role_actif = " . intval($blnActif) . ",
role_tri = " . intval($intTri);
if (fxEveAccesAdminHasDelegableColumn()) {
$sqlUpdate .= ', role_delegable = ' . intval($blnDelegable);
}
if (fxEveAccesAdminHasGrantsAllColumn()) {
$sqlUpdate .= ', role_grants_all = ' . intval($blnGrantsAll);
}
if (fxEveAccesAdminHasFicheSectionsOpenColumn()) {
$sqlUpdate .= ', role_fiche_sections_open = ' . intval($blnFicheOpen);
}
$sqlUpdate .= ' WHERE role_id = ' . intval($intRoleId);
$objDatabase->fxQuery($sqlUpdate);
}
$objDatabase->fxQuery('DELETE FROM inscriptions_eve_role_permissions WHERE role_id = ' . intval($intRoleId));
if (!$blnGrantsAll) {
foreach (array_keys($arrValidKeys) as $strKey) {
$objDatabase->fxQuery(
"INSERT INTO inscriptions_eve_role_permissions (role_id, perm_key)
VALUES (" . intval($intRoleId) . ", '" . $objDatabase->fxEscape($strKey) . "')"
);
}
}
return array(
'state' => 'ok',
'message' => 'Kit enregistre.',
'role_id' => $intRoleId,
);
}
function fxEveAccesAdminDuplicateRole($intRoleId)
{
global $objDatabase;
$row = fxEveAccesAdminGetRoleById($intRoleId);
if ($row === null) {
return array('state' => 'error', 'message' => 'Kit introuvable.');
}
$strBase = fxEveAccesAdminNormalizeRoleCode($row['role_code'] . '_copy');
$strCode = $strBase;
$intSuffix = 2;
while (fxEveAccesAdminRoleCodeExists($strCode)) {
$strCode = substr($strBase, 0, 28) . '_' . $intSuffix;
$intSuffix++;
}
$arrSave = array(
'role_id' => 0,
'role_code' => $strCode,
'role_label_fr' => $row['role_label_fr'] . ' (copie)',
'role_label_en' => ($row['role_label_en'] !== '' ? $row['role_label_en'] : $row['role_label_fr']) . ' (copy)',
'role_description_fr' => $row['role_description_fr'] ?? '',
'role_description_en' => $row['role_description_en'] ?? '',
'role_icone' => $row['role_icone'] ?? '',
'role_tri' => intval($row['role_tri']) + 1,
'role_actif' => 1,
'role_delegable' => !empty($row['role_delegable']) ? 1 : 0,
'role_grants_all' => !empty($row['role_grants_all']) ? 1 : 0,
'role_fiche_sections_open' => !empty($row['role_fiche_sections_open']) ? 1 : 0,
);
return fxEveAccesAdminSaveRole(
$arrSave,
!empty($row['role_grants_all']) ? array() : fxEveAccesAdminGetRolePermKeys($intRoleId)
);
}
function fxEveAccesAdminToggleRoleActif($intRoleId)
{
global $objDatabase;
$row = fxEveAccesAdminGetRoleById($intRoleId);
if ($row === null) {
return array('state' => 'error', 'message' => 'Kit introuvable.');
}
$blnNew = intval($row['role_actif']) === 1 ? 0 : 1;
$objDatabase->fxQuery(
'UPDATE inscriptions_eve_roles SET role_actif = ' . intval($blnNew)
. ' WHERE role_id = ' . intval($intRoleId)
);
return array(
'state' => 'ok',
'message' => $blnNew ? 'Kit reactive.' : 'Kit desactive.',
);
}
function fxEveAccesAdminRenderKitsList($strFlash = '')
{
if (!fxEveAccesIsEnabled()) {
echo '<div class="alert alert-warning">Tables acces v2 non installees. Executer les SQL MSIN-eve-acces-v2-phase1.</div>';
return;
}
if (!fxEveAccesAdminHasDelegableColumn()) {
echo '<div class="alert alert-info">Executer <code>sql/MSIN-eve-acces-v2-phase3-role-delegable.sql</code> pour activer le flag « delegable par promoteur ».</div>';
}
if (!fxEveAccesAdminHasGrantsAllColumn()) {
echo '<div class="alert alert-info">Executer <code>sql/MSIN-eve-acces-v2-phase4-role-grants-all.sql</code> pour activer les kits « tous les droits ».</div>';
}
$blnActifOnly = isset($_GET['actif']) && $_GET['actif'] === '1';
$arrRoles = fxEveAccesAdminGetRolesForList($blnActifOnly);
$blnCanEditKits = fxAdminCanEditEveAccesKits();
if ($strFlash !== '') {
echo '<div class="alert alert-success">' . htmlspecialchars($strFlash, ENT_QUOTES, 'UTF-8') . '</div>';
}
?>
<div class="d-flex justify-content-between align-items-center mb-3">
<h1 class="mb-0">Kits d'accès</h1>
<?php if ($blnCanEditKits) { ?>
<a class="btn btn-success" href="eve_acces.php?p=kit">
<i class="fa fa-plus"></i> Nouveau kit
</a>
<?php } ?>
</div>
<?php if (!$blnCanEditKits) { ?>
<div class="alert alert-info py-2">
Lecture seule : vous pouvez consulter les kits. Création / modification réservées.
Lassignation aux comptes reste disponible sur la fiche compte.
</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">
<p class="mb-2">
Un <strong>kit</strong> accorde des droits à un compte sur un événement. Deux modes :
</p>
<ul class="mb-2 pl-3">
<li><strong>Droits à la pièce</strong> — cochez les permissions dans la matrice.</li>
<li><strong>Tous les droits</strong> — switch « Kit total » (toutes les permissions actives, présentes et futures).</li>
<li><strong>Assigner</strong> → fiche compte, section <em>Accès événement v2</em>.</li>
<li><strong>Supprimer un kit</strong> → possible dès qu'<em>aucun compte</em> ne l'a actuellement (colonne Assign. = 0).</li>
<li><strong>Qui a ce kit ?</strong> → ouvrez le kit : section « Personnes assignées » en bas de fiche.</li>
</ul>
<p class="mb-0 text-muted">
Un kit « délégable » peut être proposé par un promoteur à son équipe (invitation).
Le super admin décide kit par kit — y compris pour un kit total.
</p>
</div>
</div>
<p>
<a href="eve_acces.php?p=kits" class="btn btn-sm btn-outline-secondary<?= !$blnActifOnly ? ' active' : '' ?>">Tous</a>
<a href="eve_acces.php?p=kits&amp;actif=1" class="btn btn-sm btn-outline-secondary<?= $blnActifOnly ? ' active' : '' ?>">Actifs seulement</a>
</p>
<div class="table-responsive">
<table class="table table-striped table-sm table-hover mb-0 align-middle">
<thead class="thead-dark">
<tr>
<th class="text-center" style="width:2.5rem" aria-label="Icône"></th>
<th>Kit</th>
<th class="text-center text-nowrap" style="width:1%">Perms</th>
<th class="text-center text-nowrap" style="width:1%">Assign.</th>
<th class="text-center text-nowrap" style="width:1%">Délég.</th>
<th class="text-center text-nowrap" style="width:1%">Statut</th>
<th class="text-right text-nowrap" style="width:1%"></th>
</tr>
</thead>
<tbody>
<?php
if ($arrRoles === null || count($arrRoles) === 0) {
echo '<tr><td colspan="7" class="text-muted">Aucun kit.</td></tr>';
} else {
foreach ($arrRoles as $row) {
$strIcon = trim((string)($row['role_icone'] ?? ''));
$arrDelete = fxEveAccesAdminCanDeleteRole(intval($row['role_id']));
?>
<tr>
<td class="text-center text-muted">
<?php if ($strIcon !== '') { ?>
<i class="fa fa-lg <?= htmlspecialchars($strIcon, ENT_QUOTES, 'UTF-8') ?>" aria-hidden="true"></i>
<?php } else { ?>
<span class="small">&ndash;</span>
<?php } ?>
</td>
<td>
<div class="font-weight-bold"><?= htmlspecialchars($row['role_label_fr'], ENT_QUOTES, 'UTF-8') ?></div>
<div class="small text-muted font-monospace eve-acces-kit-code"><?= htmlspecialchars($row['role_code'], ENT_QUOTES, 'UTF-8') ?></div>
<?php if (!empty($row['role_grants_all'])) { ?>
<span class="badge badge-warning mt-1">Tous les droits</span>
<?php } ?>
<?php if (!empty($row['role_fiche_sections_open'])) { ?>
<span class="badge badge-info mt-1">Sections ouvertes</span>
<?php } ?>
</td>
<td class="text-center"><?php
if (!empty($row['role_grants_all'])) {
echo '<span class="text-muted" title="Toutes les permissions actives">∞</span>';
} else {
echo intval($row['perm_count']);
}
?></td>
<td class="text-center"><?php
$intAssign = intval($row['assign_count']);
if ($intAssign > 0) {
echo '<a href="eve_acces.php?p=kit&amp;id=' . intval($row['role_id'])
. '#kit-assignations" class="badge badge-primary" title="Voir les comptes assignés">' . $intAssign . '</a>';
} else {
echo '<span class="text-muted">0</span>';
}
?></td>
<td class="text-center"><?= !empty($row['role_delegable']) ? '<span class="badge badge-info">oui</span>' : '<span class="text-muted">—</span>' ?></td>
<td class="text-center text-nowrap">
<?php if (intval($row['role_actif']) === 1) { ?>
<span class="badge badge-success">actif</span>
<?php } else { ?>
<span class="badge badge-warning">inactif</span>
<?php } ?>
</td>
<td class="text-right text-nowrap">
<a class="btn btn-sm btn-primary" href="eve_acces.php?p=kit&amp;id=<?= intval($row['role_id']) ?>"
title="<?= $blnCanEditKits ? 'Modifier' : 'Voir' ?>">
<i class="fa <?= $blnCanEditKits ? 'fa-pencil' : 'fa-eye' ?>"></i>
</a>
<?php if ($blnCanEditKits) { ?>
<a class="btn btn-sm btn-secondary" href="eve_acces.php?action=duplicate&amp;id=<?= intval($row['role_id']) ?>"
title="Dupliquer">
<i class="fa fa-copy"></i>
</a>
<a class="btn btn-sm btn-outline-secondary" href="eve_acces.php?action=toggle&amp;id=<?= intval($row['role_id']) ?>"
title="<?= intval($row['role_actif']) === 1 ? 'Desactiver' : 'Activer' ?>"
onclick="return confirm('Changer le statut de ce kit ?');">
<i class="fa fa-power-off"></i>
</a>
<?php if ($arrDelete['can']) { ?>
<a class="btn btn-sm btn-outline-danger" href="eve_acces.php?action=delete&amp;id=<?= intval($row['role_id']) ?>"
title="Supprimer"
onclick="return confirm('Supprimer définitivement ce kit ?');">
<i class="fa fa-trash"></i>
</a>
<?php } elseif ($arrDelete['message'] !== '') { ?>
<span class="btn btn-sm btn-outline-secondary disabled" style="opacity:.45;cursor:help"
title="<?= htmlspecialchars($arrDelete['message'], ENT_QUOTES, 'UTF-8') ?>">
<i class="fa fa-trash"></i>
</span>
<?php } ?>
<?php } ?>
</td>
</tr>
<?php
}
}
?>
</tbody>
</table>
</div>
<?php
}
function fxEveAccesAdminRenderKitForm($intRoleId, $strError = '')
{
if (!fxEveAccesIsEnabled()) {
echo '<div class="alert alert-warning">Tables acces v2 non installees.</div>';
return;
}
$row = null;
$arrSelected = array();
if ($intRoleId > 0) {
$row = fxEveAccesAdminGetRoleById($intRoleId);
if ($row === null) {
echo '<div class="alert alert-danger">Kit introuvable.</div>';
return;
}
foreach (fxEveAccesAdminGetRolePermKeys($intRoleId) as $strKey) {
$arrSelected[$strKey] = true;
}
}
$blnCodeLocked = ($row !== null && ($row['role_code'] ?? '') === 'migration_legacy');
$blnCanEditKits = fxAdminCanEditEveAccesKits();
$strDisabled = $blnCanEditKits ? '' : ' disabled';
$arrDelete = ($intRoleId > 0) ? fxEveAccesAdminCanDeleteRole($intRoleId) : array('can' => false, 'message' => '');
$arrCatalog = fxEveAccesGetCatalogPermissions(null, true);
$arrByGroup = array();
if ($arrCatalog != null) {
foreach ($arrCatalog as $rowPerm) {
$strGroup = $rowPerm['perm_group'] ?? 'general';
if (!isset($arrByGroup[$strGroup])) {
$arrByGroup[$strGroup] = array();
}
$arrByGroup[$strGroup][] = $rowPerm;
}
}
$arrGroupLabels = fxEveAccesAdminPermGroupLabels();
$arrGroupOrder = fxEveAccesAdminPermGroupOrder();
$arrOrderedGroups = array();
foreach ($arrGroupOrder as $strG) {
if (isset($arrByGroup[$strG])) {
$arrOrderedGroups[$strG] = $arrByGroup[$strG];
unset($arrByGroup[$strG]);
}
}
ksort($arrByGroup);
foreach ($arrByGroup as $strG => $arrP) {
$arrOrderedGroups[$strG] = $arrP;
}
if ($strError !== '') {
echo '<div class="alert alert-danger">' . htmlspecialchars($strError, ENT_QUOTES, 'UTF-8') . '</div>';
}
?>
<div class="mb-3">
<a href="eve_acces.php?p=kits" class="btn btn-sm btn-outline-secondary">
<i class="fa fa-chevron-left"></i> Retour à la liste
</a>
</div>
<h1><?php
if (!$blnCanEditKits) {
echo 'Voir le kit';
} else {
echo $intRoleId > 0 ? 'Modifier le kit' : 'Nouveau kit';
}
?></h1>
<?php if (!$blnCanEditKits) { ?>
<div class="alert alert-info py-2">Lecture seule — vous ne pouvez pas modifier ce kit.</div>
<?php } ?>
<form method="post" action="eve_acces.php?p=kit<?= $intRoleId > 0 ? '&amp;id=' . intval($intRoleId) : '' ?>"<?= $blnCanEditKits ? '' : ' onsubmit="return false;"' ?>>
<input type="hidden" name="action" value="save_kit">
<input type="hidden" name="role_id" value="<?= intval($intRoleId) ?>">
<fieldset<?= $strDisabled ?>>
<div class="card mb-3">
<div class="card-header">Identité</div>
<div class="card-body">
<div class="form-row">
<div class="form-group col-md-4">
<label for="role_code">Code <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="role_code" name="role_code" maxlength="32"
value="<?= htmlspecialchars($row['role_code'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
<?= $blnCodeLocked ? 'readonly' : '' ?>
placeholder="ex. gestion_statut">
<?php if ($blnCodeLocked) { ?>
<small class="text-muted">Code verrouillé (kit migration legacy).</small>
<?php } ?>
</div>
<div class="form-group col-md-4">
<label for="role_label_fr">Libellé FR <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="role_label_fr" name="role_label_fr" maxlength="80"
value="<?= htmlspecialchars($row['role_label_fr'] ?? '', ENT_QUOTES, 'UTF-8') ?>" required>
</div>
<div class="form-group col-md-4">
<label for="role_label_en">Libellé EN</label>
<input type="text" class="form-control" id="role_label_en" name="role_label_en" maxlength="80"
value="<?= htmlspecialchars($row['role_label_en'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
</div>
</div>
<div class="form-row">
<div class="form-group col-md-8">
<label for="role_description_fr">Description FR</label>
<input type="text" class="form-control" id="role_description_fr" name="role_description_fr" maxlength="255"
value="<?= htmlspecialchars($row['role_description_fr'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
</div>
<div class="form-group col-md-2">
<label for="role_icone">Icône FA</label>
<input type="text" class="form-control" id="role_icone" name="role_icone" maxlength="32"
placeholder="fa-mobile"
value="<?= htmlspecialchars($row['role_icone'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
</div>
<div class="form-group col-md-2">
<label for="role_tri">Ordre</label>
<input type="number" class="form-control" id="role_tri" name="role_tri" min="0" max="9999"
value="<?= intval($row['role_tri'] ?? 50) ?>">
</div>
</div>
<div class="form-row">
<div class="form-group col-md-4">
<div class="custom-control custom-checkbox mt-2">
<input type="checkbox" class="custom-control-input" id="role_actif" name="role_actif" value="1"
<?= ($row === null || intval($row['role_actif']) === 1) ? 'checked' : '' ?>>
<label class="custom-control-label" for="role_actif">Kit actif (proposable à l'assignation)</label>
</div>
</div>
<?php if (fxEveAccesAdminHasDelegableColumn()) { ?>
<div class="form-group col-md-8">
<div class="custom-control custom-checkbox mt-2">
<input type="checkbox" class="custom-control-input" id="role_delegable" name="role_delegable" value="1"
<?= !empty($row['role_delegable']) ? 'checked' : '' ?>
<?= in_array($row['role_code'] ?? '', array('owner', 'qr_debug', 'migration_legacy'), true) ? 'disabled' : '' ?>>
<label class="custom-control-label" for="role_delegable">
Délégable par le promoteur (invitation équipe)
</label>
</div>
<small class="text-muted">Le promoteur pourra assigner ce kit à un membre invité. Impossible si « Kit total » est coché (MSIN-4478).</small>
</div>
<?php } ?>
<?php if (fxEveAccesAdminHasGrantsAllColumn()) { ?>
<div class="form-group col-md-12">
<div class="custom-control custom-checkbox mt-2">
<input type="checkbox" class="custom-control-input" id="role_grants_all" name="role_grants_all" value="1"
<?= !empty($row['role_grants_all']) ? 'checked' : '' ?>
<?= $blnCodeLocked ? 'disabled' : '' ?>>
<?php if ($blnCodeLocked) { ?>
<input type="hidden" name="role_grants_all" value="1">
<?php } ?>
<label class="custom-control-label" for="role_grants_all">
<strong>Kit total</strong> — tous les droits (présents et futurs)
</label>
</div>
<small class="text-muted">
Si coché : aucune matrice à maintenir. Sinon : choisissez les droits un par un ci-dessous.
</small>
</div>
<?php } ?>
<?php if (fxEveAccesAdminHasFicheSectionsOpenColumn()) { ?>
<div class="form-group col-md-12">
<div class="custom-control custom-checkbox mt-2">
<input type="checkbox" class="custom-control-input" id="role_fiche_sections_open" name="role_fiche_sections_open" value="1"
<?= !empty($row['role_fiche_sections_open']) ? 'checked' : '' ?>>
<label class="custom-control-label" for="role_fiche_sections_open">
<strong>Sections fiche ouvertes</strong> — Inscriptions V2
</label>
</div>
<small class="text-muted">
Si coché : sur la fiche inscription, toutes les sections (détails, gestion, dossard/statut) souvrent automatiquement.
Sinon : comportement par défaut (détails ouverts, le reste fermé).
</small>
</div>
<?php } ?>
</div>
</div>
</div>
<div class="card mb-3" id="card-kit-permissions"<?= !empty($row['role_grants_all']) ? ' style="display:none;"' : '' ?>>
<div class="card-header d-flex justify-content-between align-items-center">
<span>Permissions incluses</span>
<span>
<button type="button" class="btn btn-sm btn-outline-primary" id="btn-perm-all">Tout cocher</button>
<button type="button" class="btn btn-sm btn-outline-secondary" id="btn-perm-none">Tout décocher</button>
</span>
</div>
<div class="card-body">
<p class="small text-muted mb-3">
<strong>Niveau 1</strong> = accès aux menus du hub.
<strong>Niveau 2</strong> = actions dans la page (ex. Inscriptions V2).
La page <code>/mobile</code> est la même gestion inscriptions en affichage mobile — pas une API.
</p>
<?php
foreach ($arrOrderedGroups as $strGroup => $arrPerms) {
$strGroupLabel = $arrGroupLabels[$strGroup] ?? ucfirst($strGroup);
$strGroupId = preg_replace('/[^a-z0-9_]/', '_', $strGroup);
?>
<div class="border rounded p-3 mb-3 perm-group-block" data-group="<?= htmlspecialchars($strGroupId, ENT_QUOTES, 'UTF-8') ?>">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5 class="mb-0"><?= htmlspecialchars($strGroupLabel, ENT_QUOTES, 'UTF-8') ?></h5>
<span>
<button type="button" class="btn btn-xs btn-link btn-group-all p-0 mr-2">Tout</button>
<button type="button" class="btn btn-xs btn-link btn-group-none p-0">Aucun</button>
</span>
</div>
<div class="row">
<?php foreach ($arrPerms as $rowPerm) {
$strKey = $rowPerm['perm_key'];
$strId = 'perm_' . preg_replace('/[^a-z0-9_]/', '_', $strKey);
$strScope = (string)($rowPerm['perm_scope'] ?? '');
$strBadge = ($strScope === 'page')
? '<span class="badge badge-primary ml-1">page</span>'
: (($strScope === 'action')
? '<span class="badge badge-secondary ml-1">détail</span>'
: '');
$strPad = ($strScope === 'action') ? ' pl-3' : '';
?>
<div class="col-md-6 col-lg-4 mb-2<?= $strPad ?>">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input perm-checkbox"
id="<?= htmlspecialchars($strId, ENT_QUOTES, 'UTF-8') ?>"
name="perm_keys[]"
value="<?= htmlspecialchars($strKey, ENT_QUOTES, 'UTF-8') ?>"
<?= isset($arrSelected[$strKey]) ? 'checked' : '' ?>>
<label class="custom-control-label" for="<?= htmlspecialchars($strId, ENT_QUOTES, 'UTF-8') ?>">
<?= htmlspecialchars($rowPerm['perm_label_fr'], ENT_QUOTES, 'UTF-8') ?>
<?= $strBadge ?>
<br><small class="eve-acces-perm-key"><code><?= htmlspecialchars($strKey, ENT_QUOTES, 'UTF-8') ?></code></small>
</label>
</div>
</div>
<?php } ?>
</div>
</div>
<?php
}
?>
</div>
</div>
<?php if ($blnCanEditKits) { ?>
<button type="submit" class="btn btn-success">
<i class="fa fa-save"></i> Enregistrer
</button>
<?php if ($arrDelete['can']) { ?>
<a class="btn btn-outline-danger ml-2"
href="eve_acces.php?action=delete&amp;id=<?= intval($intRoleId) ?>"
onclick="return confirm('Supprimer définitivement ce kit ?');">
<i class="fa fa-trash"></i> Supprimer
</a>
<?php } elseif ($intRoleId > 0 && $arrDelete['message'] !== '') { ?>
<span class="text-muted small ml-2" title="<?= htmlspecialchars($arrDelete['message'], ENT_QUOTES, 'UTF-8') ?>">
<i class="fa fa-info-circle"></i> <?= htmlspecialchars($arrDelete['message'], ENT_QUOTES, 'UTF-8') ?>
</span>
<?php } ?>
<?php } ?>
</fieldset>
</form>
<?php if ($intRoleId > 0) {
fxEveAccesAdminRenderRoleAssignmentsBlock($intRoleId, array(
'anchor_id' => 'kit-assignations',
'statut' => 'actif',
));
} ?>
<script>
(function () {
function syncGrantsAllUi() {
var $chk = $('#role_grants_all');
if (!$chk.length) {
return;
}
var blnAll = $chk.is(':checked');
$('#card-kit-permissions').toggle(!blnAll);
if (blnAll) {
$('.perm-checkbox').prop('checked', false);
}
// MSIN-4478 — Kit total = jamais délégable (invitation ne doit pas donner tout).
var $deleg = $('#role_delegable');
if ($deleg.length && !$deleg.prop('disabled')) {
if (blnAll) {
$deleg.prop('checked', false).prop('disabled', true);
} else {
$deleg.prop('disabled', false);
}
}
}
$('#role_grants_all').on('change', syncGrantsAllUi);
syncGrantsAllUi();
$('#btn-perm-all').on('click', function () {
$('.perm-checkbox').prop('checked', true);
});
$('#btn-perm-none').on('click', function () {
$('.perm-checkbox').prop('checked', false);
});
$('.perm-group-block').each(function () {
var $block = $(this);
$block.find('.btn-group-all').on('click', function () {
$block.find('.perm-checkbox').prop('checked', true);
});
$block.find('.btn-group-none').on('click', function () {
$block.find('.perm-checkbox').prop('checked', false);
});
});
$('#role_label_fr').on('blur', function () {
var $code = $('#role_code');
if ($code.length && !$code.prop('readonly') && $.trim($code.val()) === '') {
var slug = $.trim($(this).val()).toLowerCase()
.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
.replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').substr(0, 32);
if (slug.length >= 2) {
$code.val(slug);
}
}
});
})();
</script>
<?php
}
function fxEveAccesAdminRenderMigrationPage($strFlash = '')
{
if (!fxEveAccesIsEnabled()) {
echo '<div class="alert alert-warning">Tables acces v2 non installees.</div>';
return;
}
$intRoleId = fxEveAccesGetMigrationLegacyRoleId();
$arrStats = fxEveAccesMigrationGetStats();
$intTotal = intval($arrStats['total_legacy']);
$intPendingEvents = intval($arrStats['pending_events']);
if ($strFlash !== '') {
echo '<div class="alert alert-success">' . htmlspecialchars($strFlash, ENT_QUOTES, 'UTF-8') . '</div>';
}
if (!empty($_GET['err'])) {
echo '<div class="alert alert-danger">' . htmlspecialchars((string)$_GET['err'], ENT_QUOTES, 'UTF-8') . '</div>';
}
?>
<h1 class="mb-3">Migration promoteur legacy → v2</h1>
<p class="text-muted mb-3">
Action <strong>unique et globale</strong> : parcourt <strong>tous</strong> les comptes avec
<code>com_eve_promoteur</code>, assigne le kit <code>migration_legacy</code> une fois par événement
(tampon journal), puis ne redemande plus. Le champ legacy n'est pas modifié.
Gérer les kits ensuite sur chaque <a href="index.php?t=<?= urlencode(base64_encode('2')) ?>&amp;a=list">fiche compte</a>.
</p>
<div class="card mb-3">
<div class="card-body py-3">
<div class="row text-center">
<div class="col-md-6">
<div class="h4 mb-0"><?= $intTotal ?></div>
<div class="small text-muted">Comptes promoteur legacy</div>
</div>
<div class="col-md-6">
<div class="h4 mb-0<?= $intPendingEvents > 0 ? ' text-warning' : ' text-success' ?>"><?= $intPendingEvents ?></div>
<div class="small text-muted">Événements sans tampon migration</div>
</div>
</div>
</div>
</div>
<?php if ($intRoleId <= 0) { ?>
<div class="alert alert-warning mb-0">
Kit <code>migration_legacy</code> introuvable —
exécuter <code>sql/MSIN-eve-acces-v2-phase4-role-grants-all.sql</code>.
</div>
<?php } elseif ($intPendingEvents > 0) { ?>
<p class="mb-0">
<a class="btn btn-warning btn-lg"
href="index.php?action=migrateveacceslegacyall"
onclick="return confirm('Migrer <?= $intPendingEvents ?> evenement(s) sur <?= $intTotal ?> compte(s) promoteur legacy ?');">
<i class="fa fa-exchange"></i> Migrer tous les promoteurs legacy
</a>
</p>
<?php } else { ?>
<div class="alert alert-success mb-0">
<i class="fa fa-check"></i> Migration terminée — aucun événement legacy en attente.
</div>
<?php } ?>
<?php
}
/** Affichage date/heure rapport migration (vide → tiret). */
function fxEveAccesAdminFormatReportDatetime($strDt)
{
$strDt = trim((string)$strDt);
if ($strDt === '' || strpos($strDt, '0000-00-00') === 0) {
return '—';
}
return substr($strDt, 0, 16);
}