Enhance event access management for promoters by adding invitation and revocation functionalities. Update the UI to handle success and error messages, and improve session handling for actor identification. This aligns with MSIN-4401 requirements for a more robust event management experience.

This commit is contained in:
2026-07-13 10:08:25 -04:00
parent fdf34afcd3
commit 5cd00a1ff1
4 changed files with 614 additions and 10 deletions

View File

@ -16,16 +16,54 @@ if (isset($_GET['promoteur_eve_id'])) {
$intEveId = intval(base64_decode(urldecode($_GET['promoteur_eve_id'])));
}
$strRedirect = $vDomaine . (($strLangue === 'en') ? '/account' : '/compte');
$strLangue = ($strLangue === 'en') ? 'en' : 'fr';
$strBasePath = ($strLangue === 'en') ? '/account' : '/compte';
$strRedirect = $vDomaine . $strBasePath;
if (!empty($_GET['promoteur_eve_id'])) {
$strRedirect .= '/inc_tableau_promoteur?promoteur_eve_id=' . urlencode($_GET['promoteur_eve_id']);
}
$intActor = intval($_SESSION['com_id'] ?? 0);
if (!empty($_SESSION['usa_id'])) {
$intActor = intval($_SESSION['usa_id']);
}
if ($intEveId <= 0
|| (empty($_SESSION['com_id']) && empty($_SESSION['usa_id']))
|| !fxEveAccesCanTeamInvite($_SESSION['com_id'] ?? 0, $intEveId)) {
|| !fxEveAccesCanTeamInvite($intActor, $intEveId)) {
header('Location: ' . $strRedirect);
exit;
}
fxEveAccesPromoteurRenderPage($intEveId, $strLangue);
$strFlash = '';
$strError = '';
$strSelfUrl = $vDomaine . $strBasePath . '/inc_tableau_eve_acces?promoteur_eve_id='
. urlencode(base64_encode($intEveId)) . '&lng=' . $strLangue;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$strAction = (string)($_POST['eve_acces_action'] ?? '');
if ($strAction === 'invite') {
$arrResult = fxEveAccesPromoteurInvite($intEveId, $intActor, $_POST, $strLangue);
if (($arrResult['state'] ?? '') === 'success') {
header('Location: ' . $strSelfUrl . '&ok=' . urlencode($arrResult['message']));
exit;
}
$strError = $arrResult['message'] ?? fxEveAccesPromoteurT('eve_acces_promoteur_err_generic', $strLangue);
} elseif ($strAction === 'revoke') {
$arrResult = fxEveAccesPromoteurRevoke(intval($_POST['ea_id'] ?? 0), $intActor, $intEveId);
if (($arrResult['state'] ?? '') === 'success') {
header('Location: ' . $strSelfUrl . '&ok=' . urlencode(
fxEveAccesPromoteurT('eve_acces_promoteur_ok_revoked', $strLangue)
));
exit;
}
$strError = fxEveAccesPromoteurT('eve_acces_promoteur_err_revoke', $strLangue);
}
}
if (!empty($_GET['ok'])) {
$strFlash = (string)$_GET['ok'];
}
fxEveAccesPromoteurRenderPage($intEveId, $strLangue, $strFlash, $strError);

View File

@ -93,6 +93,74 @@ function fxEveAccesGetRoles($blnActifOnly = true)
return $objDatabase->fxGetResults($sql);
}
/** Kits proposables par un promoteur (role_delegable = 1, hors migration_legacy). */
function fxEveAccesGetDelegableRoles()
{
global $objDatabase;
if (!fxEveAccesIsEnabled()) {
return array();
}
$blnHasDelegable = false;
$tabCol = $objDatabase->fxGetResults("SHOW COLUMNS FROM inscriptions_eve_roles LIKE 'role_delegable'");
if ($tabCol != null && count($tabCol) > 0) {
$blnHasDelegable = true;
}
if (!$blnHasDelegable) {
return array();
}
$sql = "SELECT role_id, role_code, role_label_fr, role_label_en, role_description_fr
FROM inscriptions_eve_roles
WHERE role_actif = 1
AND role_delegable = 1
AND role_code <> 'migration_legacy'
ORDER BY role_tri ASC, role_label_fr ASC";
$tab = $objDatabase->fxGetResults($sql);
return ($tab != null) ? $tab : array();
}
/** Le kit est-il délégable (invite promoteur) ? */
function fxEveAccesRoleIsDelegable($intRoleId)
{
global $objDatabase;
$intRoleId = intval($intRoleId);
if ($intRoleId <= 0 || !fxEveAccesIsEnabled()) {
return false;
}
$tabCol = $objDatabase->fxGetResults("SHOW COLUMNS FROM inscriptions_eve_roles LIKE 'role_delegable'");
if ($tabCol == null || count($tabCol) === 0) {
return false;
}
$row = $objDatabase->fxGetRow(
"SELECT role_id, role_code, role_delegable, role_actif
FROM inscriptions_eve_roles WHERE role_id = $intRoleId LIMIT 1"
);
if ($row === null || intval($row['role_actif']) !== 1) {
return false;
}
if (($row['role_code'] ?? '') === 'migration_legacy') {
return false;
}
return intval($row['role_delegable'] ?? 0) === 1;
}
function fxEveAccesCanTeamManage($intComId, $intEveId)
{
if (fxEveAccesIsSuperAdminSession()) {
return true;
}
return fxEveAccesHasPermission(intval($intComId), intval($intEveId), 'team.manage');
}
/** Promoteur peut gérer / inviter des accès v2 sur cet événement ? */
function fxEveAccesCanTeamInvite($intComId, $intEveId)
{
@ -121,7 +189,7 @@ function fxEveAccesListByEveId($intEveId, $blnActifOnly = true)
}
$sql = "SELECT ea.ea_id, ea.com_id, ea.eve_id, ea.role_id, ea.ea_statut,
ea.ea_created_at, ea.ea_expires_at, ea.ea_expire_days,
ea.ea_created_at, ea.ea_expires_at, ea.ea_expire_days, ea.ea_granted_by,
r.role_code, r.role_label_fr, r.role_label_en,
c.com_prenom, c.com_nom, c.com_courriel, c.com_lastvisit
FROM inscriptions_eve_acces ea
@ -697,9 +765,19 @@ function fxEveAccesGrant($intComId, $intEveId, $intRoleId, $intExpireDays, $intG
$intEaId = intval($objDatabase->fxGetVar(
"SELECT ea_id FROM inscriptions_eve_acces
WHERE com_id = $intComId AND eve_id = $intEveId LIMIT 1"
WHERE com_id = $intComId AND eve_id = $intEveId AND role_id = $intRoleId
LIMIT 1"
));
if ($intEaId <= 0) {
// Repli si unique legacy (com+eve) encore en place sur certains environnements
$intEaId = intval($objDatabase->fxGetVar(
"SELECT ea_id FROM inscriptions_eve_acces
WHERE com_id = $intComId AND eve_id = $intEveId
ORDER BY ea_id DESC LIMIT 1"
));
}
if ($intEaId <= 0) {
return array('state' => 'error', 'message' => 'Acces non enregistre');
}

View File

@ -33,6 +33,43 @@ function fxEveAccesPromoteurT($strClef, $strLangue = 'fr')
'eve_acces_promoteur_col_statut' => array('fr' => 'Statut', 'en' => 'Status'),
'eve_acces_promoteur_col_depuis' => array('fr' => 'Depuis', 'en' => 'Since'),
'eve_acces_promoteur_col_expire' => array('fr' => 'Expire', 'en' => 'Expires'),
'eve_acces_promoteur_col_action' => array('fr' => 'Action', 'en' => 'Action'),
'eve_acces_promoteur_field_courriel' => array('fr' => 'Courriel', 'en' => 'Email'),
'eve_acces_promoteur_field_prenom' => array('fr' => 'Prénom', 'en' => 'First name'),
'eve_acces_promoteur_field_nom' => array('fr' => 'Nom', 'en' => 'Last name'),
'eve_acces_promoteur_field_kit' => array('fr' => 'Kit d\'accès', 'en' => 'Access kit'),
'eve_acces_promoteur_field_jours' => array('fr' => 'Durée (jours)', 'en' => 'Duration (days)'),
'eve_acces_promoteur_jours_help' => array(
'fr' => '0 = sans expiration. Utilisé surtout pour les nouveaux accès.',
'en' => '0 = no expiration. Mainly for new access grants.',
),
'eve_acces_promoteur_btn_invite' => array('fr' => 'Assigner l\'accès', 'en' => 'Grant access'),
'eve_acces_promoteur_btn_revoke' => array('fr' => 'Retirer', 'en' => 'Revoke'),
'eve_acces_promoteur_no_kits' => array(
'fr' => 'Aucun kit délégable disponible. Demandez à un super admin d\'activer « délégable » sur un kit.',
'en' => 'No delegable kit available. Ask a super admin to mark a kit as delegable.',
),
'eve_acces_promoteur_ok_existing' => array(
'fr' => 'Accès assigné au compte existant. Un courriel a été envoyé.',
'en' => 'Access granted to the existing account. An email was sent.',
),
'eve_acces_promoteur_ok_created' => array(
'fr' => 'Compte créé et accès assigné. Un courriel avec le lien de mot de passe a été envoyé.',
'en' => 'Account created and access granted. An email with the password link was sent.',
),
'eve_acces_promoteur_ok_revoked' => array('fr' => 'Accès retiré.', 'en' => 'Access revoked.'),
'eve_acces_promoteur_err_courriel' => array('fr' => 'Courriel invalide.', 'en' => 'Invalid email.'),
'eve_acces_promoteur_err_kit' => array('fr' => 'Kit non autorisé.', 'en' => 'Kit not allowed.'),
'eve_acces_promoteur_err_nom' => array(
'fr' => 'Prénom et nom requis pour créer un nouveau compte.',
'en' => 'First and last name required to create a new account.',
),
'eve_acces_promoteur_err_inactive' => array(
'fr' => 'Un compte existe avec ce courriel mais il est inactif. Contactez le support.',
'en' => 'An account exists with this email but it is inactive. Contact support.',
),
'eve_acces_promoteur_err_revoke' => array('fr' => 'Retrait impossible.', 'en' => 'Unable to revoke.'),
'eve_acces_promoteur_err_generic' => array('fr' => 'Une erreur est survenue.', 'en' => 'An error occurred.'),
);
$strLang = ($strLangue === 'en') ? 'en' : 'fr';
if (isset($arrFallback[$strClef][$strLang])) {
@ -63,8 +100,361 @@ function fxEveAccesPromoteurMenuLabel($strLangue = 'fr')
return fxEveAccesPromoteurT('tableau_promoteur_menu_eve_acces', $strLangue);
}
/** MSIN-4401 — rendu page promoteur (liste + emplacement invitation). */
function fxEveAccesPromoteurRenderPage($intEveId, $strLangue = 'fr')
function fxEveAccesPromoteurHasInviteTeamColumn()
{
global $objDatabase;
static $blnHas = null;
if ($blnHas !== null) {
return $blnHas;
}
$tab = $objDatabase->fxGetResults("SHOW COLUMNS FROM inscriptions_comptes LIKE 'com_invite_team'");
$blnHas = ($tab != null && count($tab) > 0);
return $blnHas;
}
/**
* MSIN-4401 — crée un compte MS1 minimal pour invitation promoteur.
*
* @return array{state:string,message?:string,com_id?:int,reset_token?:string}
*/
function fxEveAccesPromoteurCreateCompteInvite($strCourriel, $strPrenom, $strNom, $strLangue, $intGrantedBy)
{
global $objDatabase;
$strCourriel = trim(strtolower((string)$strCourriel));
$strPrenom = trim((string)$strPrenom);
$strNom = trim((string)$strNom);
$strLangue = ($strLangue === 'en') ? 'en' : 'fr';
if ($strCourriel === '' || !filter_var($strCourriel, FILTER_VALIDATE_EMAIL)) {
return array('state' => 'error', 'message' => 'courriel');
}
if ($strPrenom === '' || $strNom === '') {
return array('state' => 'error', 'message' => 'nom');
}
$strToken = uniqid('inv', true);
$strPasswordHash = password_hash(bin2hex(random_bytes(16)), PASSWORD_DEFAULT);
$strNow = fxGetDateTime();
$strInviteFlag = fxEveAccesPromoteurHasInviteTeamColumn()
? ', com_invite_team = 1'
: '';
$blnHasConfirme = false;
$tabConfirme = $objDatabase->fxGetResults("SHOW COLUMNS FROM inscriptions_comptes LIKE 'com_confirme'");
if ($tabConfirme != null && count($tabConfirme) > 0) {
$blnHasConfirme = true;
}
$blnHasResetDate = false;
$tabResetDate = $objDatabase->fxGetResults("SHOW COLUMNS FROM inscriptions_comptes LIKE 'com_reset_date'");
if ($tabResetDate != null && count($tabResetDate) > 0) {
$blnHasResetDate = true;
}
$blnHasNoteInterne = false;
$tabNote = $objDatabase->fxGetResults("SHOW COLUMNS FROM inscriptions_comptes LIKE 'com_note_interne'");
if ($tabNote != null && count($tabNote) > 0) {
$blnHasNoteInterne = true;
}
$blnHasResetToken = false;
$tabResetTok = $objDatabase->fxGetResults("SHOW COLUMNS FROM inscriptions_comptes LIKE 'com_reset_token'");
if ($tabResetTok != null && count($tabResetTok) > 0) {
$blnHasResetToken = true;
}
$sql = "INSERT INTO inscriptions_comptes SET
com_prenom = '" . $objDatabase->fxEscape($strPrenom) . "',
com_nom = '" . $objDatabase->fxEscape($strNom) . "',
com_courriel = '" . $objDatabase->fxEscape($strCourriel) . "',
com_login = '" . $objDatabase->fxEscape($strCourriel) . "',
com_password = '" . $objDatabase->fxEscape($strPasswordHash) . "',
com_langue = '" . $objDatabase->fxEscape($strLangue) . "',
com_actif = 1,
com_creation = '" . $objDatabase->fxEscape($strNow) . "'
$strInviteFlag";
if ($blnHasConfirme) {
$sql .= ', com_confirme = 1';
}
if ($blnHasResetToken) {
$sql .= ", com_reset_token = '" . $objDatabase->fxEscape($strToken) . "'";
}
if ($blnHasResetDate) {
$sql .= ", com_reset_date = '" . $objDatabase->fxEscape($strNow) . "'";
}
if ($blnHasNoteInterne) {
$sql .= ", com_note_interne = 'MSIN-4401 invitation promoteur (par com_id "
. intval($intGrantedBy) . ")'";
}
if ($objDatabase->fxQuery($sql) === false) {
return array('state' => 'error', 'message' => 'insert');
}
$intComId = intval($objDatabase->fxGetVar(
"SELECT com_id FROM inscriptions_comptes
WHERE com_courriel = '" . $objDatabase->fxEscape($strCourriel) . "'
ORDER BY com_id DESC LIMIT 1"
));
if ($intComId <= 0) {
return array('state' => 'error', 'message' => 'insert');
}
return array(
'state' => 'success',
'com_id' => $intComId,
'reset_token' => $strToken,
);
}
function fxEveAccesPromoteurSendInviteMail($arrCompte, $arrEve, $arrRole, $strLangue, $blnNewAccount, $strResetToken = '')
{
global $vDomaine, $vEmail, $vClient;
if (!function_exists('fxSendMail')) {
require_once __DIR__ . '/inc_mail.php';
}
if (!function_exists('fxGetMailFormat')) {
require_once __DIR__ . '/inc_fonctions.php';
}
$strLangue = ($strLangue === 'en') ? 'en' : 'fr';
$strEveNom = fxUnescape($arrEve['eve_nom_' . $strLangue] ?? ($arrEve['eve_nom_fr'] ?? ''));
$strKit = ($strLangue === 'en')
? ($arrRole['role_label_en'] ?? $arrRole['role_label_fr'] ?? '')
: ($arrRole['role_label_fr'] ?? '');
$strPrenom = fxUnescape($arrCompte['com_prenom'] ?? '');
$strLogin = $arrCompte['com_login'] ?? $arrCompte['com_courriel'];
$intEveId = intval($arrEve['eve_id'] ?? 0);
$strEveParam = urlencode(base64_encode($intEveId));
$strComptePath = ($strLangue === 'en') ? '/account' : '/compte';
$strAccessUrl = $vDomaine . $strComptePath . '/inc_tableau_eve_acces?promoteur_eve_id=' . $strEveParam;
if ($blnNewAccount && $strResetToken !== '') {
if ($strLangue === 'fr') {
$subject = 'Accès promoteur — créez votre mot de passe';
$message = 'Bonjour ' . $strPrenom . ',<br><br>'
. 'Un accès vous a été accordé pour <strong>' . htmlspecialchars($strEveNom, ENT_QUOTES, 'UTF-8') . '</strong>'
. ' (kit : ' . htmlspecialchars($strKit, ENT_QUOTES, 'UTF-8') . ').<br><br>'
. 'Un compte a été créé avec le courriel <strong>' . htmlspecialchars($strLogin, ENT_QUOTES, 'UTF-8') . '</strong>.<br>'
. 'Cliquez ci-dessous pour choisir votre mot de passe, puis connectez-vous.';
$attachment_label = 'Choisir mon mot de passe';
} else {
$subject = 'Promoter access — set your password';
$message = 'Hello ' . $strPrenom . ',<br><br>'
. 'You were granted access to <strong>' . htmlspecialchars($strEveNom, ENT_QUOTES, 'UTF-8') . '</strong>'
. ' (kit: ' . htmlspecialchars($strKit, ENT_QUOTES, 'UTF-8') . ').<br><br>'
. 'An account was created with <strong>' . htmlspecialchars($strLogin, ENT_QUOTES, 'UTF-8') . '</strong>.<br>'
. 'Click below to set your password, then sign in.';
$attachment_label = 'Set my password';
}
$attachment_url = $vDomaine . $strComptePath . '/reset?t=' . urlencode($strResetToken)
. '&m=' . urlencode($arrCompte['com_courriel'])
. '&id=' . intval($arrCompte['com_id']);
} else {
if ($strLangue === 'fr') {
$subject = 'Nouvel accès — ' . $strEveNom;
$message = 'Bonjour ' . $strPrenom . ',<br><br>'
. 'Un accès vous a été accordé pour <strong>' . htmlspecialchars($strEveNom, ENT_QUOTES, 'UTF-8') . '</strong>'
. ' (kit : ' . htmlspecialchars($strKit, ENT_QUOTES, 'UTF-8') . ').<br><br>'
. 'Connectez-vous avec votre compte existant (<strong>' . htmlspecialchars($strLogin, ENT_QUOTES, 'UTF-8') . '</strong>).';
$attachment_label = 'Ouvrir mon compte';
} else {
$subject = 'New access — ' . $strEveNom;
$message = 'Hello ' . $strPrenom . ',<br><br>'
. 'You were granted access to <strong>' . htmlspecialchars($strEveNom, ENT_QUOTES, 'UTF-8') . '</strong>'
. ' (kit: ' . htmlspecialchars($strKit, ENT_QUOTES, 'UTF-8') . ').<br><br>'
. 'Sign in with your existing account (<strong>' . htmlspecialchars($strLogin, ENT_QUOTES, 'UTF-8') . '</strong>).';
$attachment_label = 'Open my account';
}
$attachment_url = $vDomaine . $strComptePath;
}
$body = fxGetMailFormat(false, false, true, false, $strLangue);
$body = str_replace('%logo%', $vDomaine . '/images/logoms1inscription.png', $body);
$body = str_replace('%to_mail%', $arrCompte['com_courriel'], $body);
$body = str_replace('%subject%', $subject, $body);
$body = str_replace('%message%', $message, $body);
$body = str_replace('%attachment_url%', $attachment_url, $body);
$body = str_replace('%attachment_label%', $attachment_label, $body);
fxSendMail(
$subject,
$body,
$arrCompte['com_courriel'],
trim(($arrCompte['com_prenom'] ?? '') . ' ' . ($arrCompte['com_nom'] ?? '')),
$vEmail,
$vClient,
1,
0
);
return true;
}
/**
* MSIN-4401 — invitation / assignation par promoteur.
*
* @return array{state:string,message:string,created?:bool}
*/
function fxEveAccesPromoteurInvite($intEveId, $intGrantedBy, $arrPost, $strLangue = 'fr')
{
global $objDatabase;
$intEveId = intval($intEveId);
$intGrantedBy = intval($intGrantedBy);
$strLangue = ($strLangue === 'en') ? 'en' : 'fr';
if (!fxEveAccesCanTeamInvite($intGrantedBy, $intEveId)) {
return array('state' => 'error', 'message' => fxEveAccesPromoteurT('eve_acces_promoteur_err_generic', $strLangue));
}
$strCourriel = trim(strtolower((string)($arrPost['invite_courriel'] ?? '')));
$strPrenom = trim((string)($arrPost['invite_prenom'] ?? ''));
$strNom = trim((string)($arrPost['invite_nom'] ?? ''));
$intRoleId = intval($arrPost['invite_role_id'] ?? 0);
$intJours = intval($arrPost['invite_jours'] ?? 0);
if ($intJours < 0) {
$intJours = 0;
}
if ($intJours > 3650) {
$intJours = 3650;
}
if ($strCourriel === '' || !filter_var($strCourriel, FILTER_VALIDATE_EMAIL)) {
return array('state' => 'error', 'message' => fxEveAccesPromoteurT('eve_acces_promoteur_err_courriel', $strLangue));
}
if (!fxEveAccesRoleIsDelegable($intRoleId)) {
return array('state' => 'error', 'message' => fxEveAccesPromoteurT('eve_acces_promoteur_err_kit', $strLangue));
}
$arrEve = $objDatabase->fxGetRow(
'SELECT eve_id, eve_nom_fr, eve_nom_en FROM inscriptions_evenements WHERE eve_id = ' . $intEveId . ' LIMIT 1'
);
$arrRole = $objDatabase->fxGetRow(
'SELECT role_id, role_code, role_label_fr, role_label_en FROM inscriptions_eve_roles WHERE role_id = ' . $intRoleId . ' LIMIT 1'
);
if ($arrEve === null || $arrRole === null) {
return array('state' => 'error', 'message' => fxEveAccesPromoteurT('eve_acces_promoteur_err_generic', $strLangue));
}
$rowExisting = $objDatabase->fxGetRow(
"SELECT com_id, com_prenom, com_nom, com_courriel, com_login, com_actif
FROM inscriptions_comptes
WHERE com_courriel = '" . $objDatabase->fxEscape($strCourriel) . "'
ORDER BY com_actif DESC, com_id DESC
LIMIT 1"
);
$blnCreated = false;
$strResetToken = '';
$arrCompte = null;
if ($rowExisting !== null) {
if (intval($rowExisting['com_actif']) !== 1) {
return array('state' => 'error', 'message' => fxEveAccesPromoteurT('eve_acces_promoteur_err_inactive', $strLangue));
}
$arrCompte = $rowExisting;
if ($strPrenom !== '' && trim((string)$arrCompte['com_prenom']) === '') {
$objDatabase->fxQuery(
"UPDATE inscriptions_comptes SET com_prenom = '" . $objDatabase->fxEscape($strPrenom) . "'
WHERE com_id = " . intval($arrCompte['com_id'])
);
$arrCompte['com_prenom'] = $strPrenom;
}
if ($strNom !== '' && trim((string)$arrCompte['com_nom']) === '') {
$objDatabase->fxQuery(
"UPDATE inscriptions_comptes SET com_nom = '" . $objDatabase->fxEscape($strNom) . "'
WHERE com_id = " . intval($arrCompte['com_id'])
);
$arrCompte['com_nom'] = $strNom;
}
} else {
if ($strPrenom === '' || $strNom === '') {
return array('state' => 'error', 'message' => fxEveAccesPromoteurT('eve_acces_promoteur_err_nom', $strLangue));
}
$arrCreate = fxEveAccesPromoteurCreateCompteInvite(
$strCourriel,
$strPrenom,
$strNom,
$strLangue,
$intGrantedBy
);
if (($arrCreate['state'] ?? '') !== 'success') {
return array('state' => 'error', 'message' => fxEveAccesPromoteurT('eve_acces_promoteur_err_generic', $strLangue));
}
$blnCreated = true;
$strResetToken = (string)($arrCreate['reset_token'] ?? '');
$arrCompte = array(
'com_id' => intval($arrCreate['com_id']),
'com_prenom' => $strPrenom,
'com_nom' => $strNom,
'com_courriel' => $strCourriel,
'com_login' => $strCourriel,
);
}
$arrGrant = fxEveAccesGrant(
intval($arrCompte['com_id']),
$intEveId,
$intRoleId,
$intJours,
$intGrantedBy,
'MSIN-4401 invitation promoteur'
);
if (($arrGrant['state'] ?? '') !== 'success') {
return array(
'state' => 'error',
'message' => $arrGrant['message'] ?? fxEveAccesPromoteurT('eve_acces_promoteur_err_generic', $strLangue),
);
}
fxEveAccesPromoteurSendInviteMail($arrCompte, $arrEve, $arrRole, $strLangue, $blnCreated, $strResetToken);
return array(
'state' => 'success',
'created' => $blnCreated,
'message' => $blnCreated
? fxEveAccesPromoteurT('eve_acces_promoteur_ok_created', $strLangue)
: fxEveAccesPromoteurT('eve_acces_promoteur_ok_existing', $strLangue),
);
}
/**
* MSIN-4401 — révocation par le promoteur (team.manage ou auteur du grant / super admin).
*/
function fxEveAccesPromoteurRevoke($intEaId, $intActorComId, $intEveId)
{
global $objDatabase;
$intEaId = intval($intEaId);
$intActorComId = intval($intActorComId);
$intEveId = intval($intEveId);
$tab = $objDatabase->fxGetRow(
'SELECT * FROM inscriptions_eve_acces WHERE ea_id = ' . $intEaId . ' LIMIT 1'
);
if ($tab === null || intval($tab['eve_id']) !== $intEveId) {
return array('state' => 'error');
}
$blnOk = fxEveAccesCanTeamManage($intActorComId, $intEveId)
|| (intval($tab['ea_granted_by'] ?? 0) === $intActorComId && fxEveAccesCanTeamInvite($intActorComId, $intEveId));
if (!$blnOk) {
return array('state' => 'error');
}
return fxEveAccesRevoke($intEaId, $intActorComId);
}
/** MSIN-4401 — rendu page promoteur (invitation + liste). */
function fxEveAccesPromoteurRenderPage($intEveId, $strLangue = 'fr', $strFlash = '', $strError = '')
{
global $vDomaine;
@ -75,6 +465,11 @@ function fxEveAccesPromoteurRenderPage($intEveId, $strLangue = 'fr')
? fxGetPromoteurBackUrl($intEveId, $strPage)
: ($vDomaine . $strPage);
$intActor = intval($_SESSION['com_id'] ?? 0);
if (!empty($_SESSION['usa_id'])) {
$intActor = intval($_SESSION['usa_id']);
}
$arrEve = function_exists('fxGetEvenementsId') ? fxGetEvenementsId($intEveId) : null;
$strEveNom = '';
if (is_array($arrEve) && !empty($arrEve['eve_nom_' . $strLangue])) {
@ -82,7 +477,11 @@ function fxEveAccesPromoteurRenderPage($intEveId, $strLangue = 'fr')
}
$arrRows = fxEveAccesListByEveId($intEveId, true);
$arrRoles = fxEveAccesGetDelegableRoles();
$strRoleLabelKey = ($strLangue === 'en') ? 'role_label_en' : 'role_label_fr';
$blnCanManage = fxEveAccesCanTeamManage($intActor, $intEveId);
$strFormAction = $vDomaine . $strPage . '/inc_tableau_eve_acces?promoteur_eve_id='
. urlencode(base64_encode($intEveId)) . '&lng=' . $strLangue;
echo '<div class="eve-acces-promoteur-page" id="module-eve-acces-promoteur">';
@ -104,14 +503,71 @@ function fxEveAccesPromoteurRenderPage($intEveId, $strLangue = 'fr')
echo '<p class="text-muted small mb-0">' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_intro', $strLangue)) . '</p>';
echo '</div>';
// Emplacement invitation (étape suivante) — même format de carte v2
if ($strFlash !== '') {
echo '<div class="alert alert-success">' . fxEveAccesPromoteurEsc($strFlash) . '</div>';
}
if ($strError !== '') {
echo '<div class="alert alert-danger">' . fxEveAccesPromoteurEsc($strError) . '</div>';
}
echo '<div class="card mb-3 eve-acces-promoteur-card">';
echo '<div class="card-header d-flex align-items-center">';
echo '<i class="fa fa-user-plus mr-2" aria-hidden="true"></i>';
echo fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_invite_title', $strLangue));
echo '</div>';
echo '<div class="card-body">';
echo '<p class="text-muted small mb-0">' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_invite_soon', $strLangue)) . '</p>';
if (count($arrRoles) === 0) {
echo '<p class="text-muted small mb-0">' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_no_kits', $strLangue)) . '</p>';
} else {
echo '<form method="post" action="' . fxEveAccesPromoteurEsc($strFormAction) . '" class="eve-acces-promoteur-invite-form">';
echo '<input type="hidden" name="eve_acces_action" value="invite">';
echo '<div class="form-row">';
echo '<div class="form-group col-md-6">';
echo '<label for="invite_courriel">' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_field_courriel', $strLangue)) . '</label>';
echo '<input class="form-control rounded-0" type="email" id="invite_courriel" name="invite_courriel" required maxlength="100" autocomplete="email">';
echo '</div>';
echo '<div class="form-group col-md-3">';
echo '<label for="invite_prenom">' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_field_prenom', $strLangue)) . '</label>';
echo '<input class="form-control rounded-0" type="text" id="invite_prenom" name="invite_prenom" maxlength="50">';
echo '</div>';
echo '<div class="form-group col-md-3">';
echo '<label for="invite_nom">' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_field_nom', $strLangue)) . '</label>';
echo '<input class="form-control rounded-0" type="text" id="invite_nom" name="invite_nom" maxlength="50">';
echo '</div>';
echo '</div>';
echo '<div class="form-row">';
echo '<div class="form-group col-md-6">';
echo '<label for="invite_role_id">' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_field_kit', $strLangue)) . '</label>';
echo '<select class="form-control rounded-0" id="invite_role_id" name="invite_role_id" required>';
foreach ($arrRoles as $arrRole) {
$strLabel = trim((string)($arrRole[$strRoleLabelKey] ?? $arrRole['role_label_fr'] ?? ''));
echo '<option value="' . intval($arrRole['role_id']) . '">' . fxEveAccesPromoteurEsc($strLabel) . '</option>';
}
echo '</select>';
echo '</div>';
echo '<div class="form-group col-md-3">';
echo '<label for="invite_jours">' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_field_jours', $strLangue)) . '</label>';
echo '<input class="form-control rounded-0" type="number" id="invite_jours" name="invite_jours" min="0" max="3650" value="0">';
echo '<small class="form-text text-muted">' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_jours_help', $strLangue)) . '</small>';
echo '</div>';
echo '<div class="form-group col-md-3 d-flex align-items-end">';
echo '<button type="submit" class="btn btn-primary rounded-0 btn-block">';
echo '<i class="fa fa-paper-plane mr-1" aria-hidden="true"></i>';
echo fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_btn_invite', $strLangue));
echo '</button>';
echo '</div>';
echo '</div>';
echo '<p class="small text-muted mb-0">'
. fxEveAccesPromoteurEsc(
$strLangue === 'en'
? 'If the email already has an MS1 account, the kit is added to that account. Otherwise a new account is created.'
: 'Si le courriel a déjà un compte MS1, le kit est ajouté à ce compte. Sinon un nouveau compte est créé.'
)
. '</p>';
echo '</form>';
}
echo '</div></div>';
echo '<div class="card mb-3 eve-acces-promoteur-card">';
@ -130,10 +586,11 @@ function fxEveAccesPromoteurRenderPage($intEveId, $strLangue = 'fr')
echo '<th class="text-center">' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_col_statut', $strLangue)) . '</th>';
echo '<th>' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_col_depuis', $strLangue)) . '</th>';
echo '<th>' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_col_expire', $strLangue)) . '</th>';
echo '<th></th>';
echo '</tr></thead><tbody>';
if (count($arrRows) === 0) {
echo '<tr><td colspan="5" class="text-muted px-3 py-3">'
echo '<tr><td colspan="6" class="text-muted px-3 py-3">'
. fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_empty', $strLangue))
. '</td></tr>';
} else {
@ -151,6 +608,9 @@ function fxEveAccesPromoteurRenderPage($intEveId, $strLangue = 'fr')
$strBadge = 'info';
}
$blnCanRevoke = $blnCanManage
|| (intval($arrRow['ea_granted_by'] ?? 0) === $intActor);
echo '<tr>';
echo '<td>';
echo '<div>' . fxEveAccesPromoteurEsc($strCompte) . '</div>';
@ -163,6 +623,19 @@ function fxEveAccesPromoteurRenderPage($intEveId, $strLangue = 'fr')
. fxEveAccesPromoteurEsc(fxEveAccesPromoteurFormatDt($arrRow['ea_created_at'] ?? '')) . '</td>';
echo '<td class="text-nowrap small">'
. fxEveAccesPromoteurEsc(fxEveAccesPromoteurFormatDt($arrRow['ea_expires_at'] ?? '')) . '</td>';
echo '<td class="text-right text-nowrap">';
if ($blnCanRevoke && intval($arrRow['com_id']) !== $intActor) {
echo '<form method="post" action="' . fxEveAccesPromoteurEsc($strFormAction) . '" class="d-inline"';
echo ' onsubmit="return confirm(\'' . fxEveAccesPromoteurEsc(
$strLangue === 'en' ? 'Revoke this access?' : 'Retirer cet accès ?'
) . '\');">';
echo '<input type="hidden" name="eve_acces_action" value="revoke">';
echo '<input type="hidden" name="ea_id" value="' . intval($arrRow['ea_id']) . '">';
echo '<button type="submit" class="btn btn-sm btn-outline-danger rounded-0">';
echo fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_btn_revoke', $strLangue));
echo '</button></form>';
}
echo '</td>';
echo '</tr>';
}
}

View File

@ -0,0 +1,15 @@
-- MSIN-4401 — Flag comptes créés via invitation promoteur
-- Idempotent. Safe pilote.
SET @col_exists := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'inscriptions_comptes'
AND COLUMN_NAME = 'com_invite_team'
);
SET @ddl := IF(@col_exists = 0,
'ALTER TABLE `inscriptions_comptes` ADD COLUMN `com_invite_team` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT ''MSIN-4401 compte cree via invitation promoteur acces v2'' AFTER `com_actif`',
'DO 0');
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;