Files
ms1inscription-v5/superadm/php/inc_fx_eve_acces_admin.php
stephan 5936cdb61c Add legacy promoteur functionality and migration enhancements
This commit introduces several enhancements related to the legacy promoteur system. It adds a new function to check if the legacy promoteur site is enabled and updates various functions to utilize this check, ensuring that legacy features are only accessible when appropriate. Additionally, it refines the migration process for legacy accounts, including improved handling of migration statistics and event access. The admin interface is updated to reflect these changes, enhancing the overall management of legacy promoteur accounts. The version code is updated to reflect these changes.
2026-07-01 14:14:25 -04:00

1089 lines
48 KiB
PHP

<?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;
}
function fxEveAccesAdminPermGroupLabels()
{
return array(
'api' => 'API mobile',
'inscriptions' => 'Inscriptions',
'dossards' => 'Dossards',
'epreuves' => 'Épreuves',
'rabais' => 'Rabais',
'emails' => 'Courriels',
'reports' => 'Rapports',
'team' => 'Équipe',
'tools' => 'Outils',
'action' => 'Actions mobile',
'general' => 'Général',
);
}
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';
$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},
(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';
$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}
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;
if ($blnCodeLocked) {
$blnGrantsAll = 1;
}
if ($blnGrantsAll) {
$blnDelegable = 0;
}
if ($blnCodeLocked || 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);
}
$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);
}
$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,
);
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);
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>
<a class="btn btn-success" href="eve_acces.php?p=kit">
<i class="fa fa-plus"></i> Nouveau kit
</a>
</div>
<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">
Les kits « délégables » pourront plus tard être proposés par un promoteur à son équipe.
</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"><?= 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 } ?>
</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="Modifier">
<i class="fa fa-pencil"></i>
</a>
<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 } ?>
</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');
$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();
ksort($arrByGroup);
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><?= $intRoleId > 0 ? 'Modifier le kit' : 'Nouveau kit' ?></h1>
<form method="post" action="eve_acces.php?p=kit<?= $intRoleId > 0 ? '&amp;id=' . intval($intRoleId) : '' ?>">
<input type="hidden" name="action" value="save_kit">
<input type="hidden" name="role_id" value="<?= intval($intRoleId) ?>">
<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 — phase suivante)
</label>
</div>
<small class="text-muted">Le promoteur pourra assigner ce kit à un membre invité, dans les limites définies par l'admin.</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 } ?>
</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">
<?php
foreach ($arrByGroup 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);
?>
<div class="col-md-6 col-lg-4 mb-2">
<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') ?>
<br><small class="text-muted"><code><?= htmlspecialchars($strKey, ENT_QUOTES, 'UTF-8') ?></code></small>
</label>
</div>
</div>
<?php } ?>
</div>
</div>
<?php
}
?>
</div>
</div>
<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 } ?>
</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);
$('#role_delegable').prop('checked', false).prop('disabled', true);
} else {
$('#role_delegable').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);
}