1134 lines
50 KiB
PHP
1134 lines
50 KiB
PHP
<?php
|
|
|
|
/**
|
|
* MSIN-4401 — Gestion des accès V2 côté promoteur (UI événement).
|
|
* Style aligné sur les modules v2 (hub / gestion inscriptions / dossards).
|
|
*/
|
|
|
|
require_once __DIR__ . '/inc_fx_eve_acces.php';
|
|
|
|
function fxEveAccesPromoteurT($strClef, $strLangue = 'fr')
|
|
{
|
|
$strTexte = afficheTexte($strClef, 0, 0, 1);
|
|
if ($strTexte === $strClef || trim((string)$strTexte) === '' || strpos((string)$strTexte, '*' . $strClef . '*') !== false) {
|
|
$arrFallback = array(
|
|
'tableau_promoteur_menu_eve_acces' => array('fr' => 'Gestion des accès V2', 'en' => 'Access management V2'),
|
|
'eve_acces_promoteur_title' => array('fr' => 'Gestion des accès V2', 'en' => 'Access management V2'),
|
|
'eve_acces_promoteur_intro' => array(
|
|
'fr' => 'Assignez des kits d\'accès à d\'autres personnes pour cet événement.',
|
|
'en' => 'Assign access kits to other people for this event.',
|
|
),
|
|
'eve_acces_promoteur_list_title' => array('fr' => 'Personnes assignées', 'en' => 'Assigned people'),
|
|
'eve_acces_promoteur_invite_title' => array('fr' => 'Inviter quelqu\'un', 'en' => 'Invite someone'),
|
|
'eve_acces_promoteur_invite_soon' => array(
|
|
'fr' => 'L\'invitation par courriel arrive à l\'étape suivante. Pour l\'instant, consultez les accès déjà en place.',
|
|
'en' => 'Email invitation comes in the next step. For now, review existing access.',
|
|
),
|
|
'eve_acces_promoteur_empty' => array(
|
|
'fr' => 'Aucun accès assigné pour cet événement.',
|
|
'en' => 'No access assigned for this event.',
|
|
),
|
|
'eve_acces_promoteur_col_compte' => array('fr' => 'Compte', 'en' => 'Account'),
|
|
'eve_acces_promoteur_col_kit' => array('fr' => 'Kit', 'en' => 'Kit'),
|
|
'eve_acces_promoteur_col_statut' => array('fr' => 'Statut', 'en' => 'Status'),
|
|
'eve_acces_promoteur_statut_actif' => array('fr' => 'Actif', 'en' => 'Active'),
|
|
'eve_acces_promoteur_statut_pending' => array(
|
|
'fr' => 'Compte non activé',
|
|
'en' => 'Account not activated',
|
|
),
|
|
'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_invite_courriel_help' => array(
|
|
'fr' => 'Saisissez d\'abord le courriel : on vérifie s\'il existe déjà un compte MS1 avant de demander le nom.',
|
|
'en' => 'Enter the email first: we check if an MS1 account already exists before asking for a name.',
|
|
),
|
|
'eve_acces_promoteur_btn_invite' => array('fr' => 'Assigner l\'accès', 'en' => 'Grant access'),
|
|
'eve_acces_promoteur_btn_resend' => array('fr' => 'Renvoyer le mail', 'en' => 'Resend email'),
|
|
'eve_acces_promoteur_btn_revoke' => array('fr' => 'Retirer', 'en' => 'Revoke'),
|
|
'eve_acces_promoteur_confirm_revoke' => array(
|
|
'fr' => 'Retirer cet accès ?',
|
|
'en' => 'Revoke this access?',
|
|
),
|
|
'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.',
|
|
),
|
|
// MSIN-4478 — réinvite d'un stub jamais activé
|
|
'eve_acces_promoteur_ok_pending' => array(
|
|
'fr' => 'Invitation mise à jour. Un courriel avec le lien de mot de passe a été envoyé.',
|
|
'en' => 'Invitation updated. An email with the password link was sent.',
|
|
),
|
|
'eve_acces_promoteur_ok_revoked' => array('fr' => 'Accès retiré.', 'en' => 'Access revoked.'),
|
|
'eve_acces_promoteur_ok_resent' => array(
|
|
'fr' => 'Courriel d\'invitation renvoyé.',
|
|
'en' => 'Invitation email resent.',
|
|
),
|
|
'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_resend' => array(
|
|
'fr' => 'Renvoi du courriel impossible.',
|
|
'en' => 'Unable to resend the email.',
|
|
),
|
|
'eve_acces_promoteur_err_generic' => array('fr' => 'Une erreur est survenue.', 'en' => 'An error occurred.'),
|
|
'eve_acces_promoteur_check_exists' => array(
|
|
'fr' => 'Compte déjà existant. Un courriel de confirmation sera envoyé.',
|
|
'en' => 'Existing account. A confirmation email will be sent.',
|
|
),
|
|
'eve_acces_promoteur_check_new' => array(
|
|
'fr' => 'Aucun compte avec ce courriel. Indiquez le prénom et le nom pour créer le compte.',
|
|
'en' => 'No account with this email. Enter first and last name to create the account.',
|
|
),
|
|
'eve_acces_promoteur_check_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_check_invalid' => array(
|
|
'fr' => 'Courriel invalide.',
|
|
'en' => 'Invalid email.',
|
|
),
|
|
'eve_acces_promoteur_access_denied' => array('fr' => 'Accès refusé.', 'en' => 'Access denied.'),
|
|
'eve_acces_promoteur_btn_back' => array('fr' => 'Retour', 'en' => 'Back'),
|
|
'eve_acces_promoteur_mail_new_subject' => array(
|
|
'fr' => 'Accès promoteur — créez votre mot de passe',
|
|
'en' => 'Promoter access — set your password',
|
|
),
|
|
'eve_acces_promoteur_mail_new_body' => array(
|
|
'fr' => 'Bonjour %prenom%,<br><br>Un accès vous a été accordé pour <strong>%eve_nom%</strong> (kit : %kit%).<br><br>Un compte a été créé avec le courriel <strong>%login%</strong>.<br>Cliquez ci-dessous pour choisir votre mot de passe.<br><br>Ensuite, connectez-vous ici pour travailler sur le terrain :<br><a href="%mobile_url%">%mobile_url%</a>',
|
|
'en' => 'Hello %prenom%,<br><br>You were granted access to <strong>%eve_nom%</strong> (kit: %kit%).<br><br>An account was created with <strong>%login%</strong>.<br>Click below to set your password.<br><br>Then sign in here for field use:<br><a href="%mobile_url%">%mobile_url%</a>',
|
|
),
|
|
'eve_acces_promoteur_mail_new_cta' => array('fr' => 'Choisir mon mot de passe', 'en' => 'Set my password'),
|
|
'eve_acces_promoteur_mail_existing_subject' => array(
|
|
'fr' => 'Nouvel accès — %eve_nom%',
|
|
'en' => 'New access — %eve_nom%',
|
|
),
|
|
'eve_acces_promoteur_mail_existing_body' => array(
|
|
'fr' => 'Bonjour %prenom%,<br><br>Un accès vous a été accordé pour <strong>%eve_nom%</strong> (kit : %kit%).<br><br>Ouvrez le lien ci-dessous et connectez-vous avec le compte <strong>%login%</strong> (si un autre compte est déjà ouvert, vous serez invité à vous déconnecter).',
|
|
'en' => 'Hello %prenom%,<br><br>You were granted access to <strong>%eve_nom%</strong> (kit: %kit%).<br><br>Open the link below and sign in as <strong>%login%</strong> (if another account is already open, you will be asked to sign out).',
|
|
),
|
|
'eve_acces_promoteur_mail_existing_cta' => array(
|
|
'fr' => 'Ouvrir l\'accès (mobile)',
|
|
'en' => 'Open access (mobile)',
|
|
),
|
|
);
|
|
$strLang = ($strLangue === 'en') ? 'en' : 'fr';
|
|
if (isset($arrFallback[$strClef][$strLang])) {
|
|
return $arrFallback[$strClef][$strLang];
|
|
}
|
|
}
|
|
|
|
return $strTexte;
|
|
}
|
|
|
|
function fxEveAccesPromoteurEsc($str)
|
|
{
|
|
return htmlspecialchars((string)$str, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
|
|
function fxEveAccesPromoteurFormatDt($strDt)
|
|
{
|
|
$strDt = trim((string)$strDt);
|
|
if ($strDt === '' || strpos($strDt, '0000-00-00') === 0) {
|
|
return '—';
|
|
}
|
|
|
|
return substr($strDt, 0, 16);
|
|
}
|
|
|
|
function fxEveAccesPromoteurMenuLabel($strLangue = 'fr')
|
|
{
|
|
return fxEveAccesPromoteurT('tableau_promoteur_menu_eve_acces', $strLangue);
|
|
}
|
|
|
|
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-4478 — compte stub d'invitation jamais vraiment utilisé (pas de 1re visite).
|
|
*/
|
|
function fxEveAccesPromoteurCompteIsPendingInvite($arrCompte)
|
|
{
|
|
if (!is_array($arrCompte) || empty($arrCompte['com_id'])) {
|
|
return false;
|
|
}
|
|
|
|
$strLastVisit = trim((string)($arrCompte['com_lastvisit'] ?? ''));
|
|
$blnNeverVisited = ($strLastVisit === '' || strpos($strLastVisit, '0000-00-00') === 0);
|
|
if (!$blnNeverVisited) {
|
|
return false;
|
|
}
|
|
|
|
if (!fxEveAccesPromoteurHasInviteTeamColumn()) {
|
|
return false;
|
|
}
|
|
|
|
return intval($arrCompte['com_invite_team'] ?? 0) === 1;
|
|
}
|
|
|
|
/**
|
|
* MSIN-4478 — nouveau jeton reset pour renvoyer le mail « créez votre mot de passe ».
|
|
*
|
|
* @return string token ('' si colonnes absentes)
|
|
*/
|
|
function fxEveAccesPromoteurRefreshResetToken($intComId)
|
|
{
|
|
global $objDatabase;
|
|
|
|
$intComId = intval($intComId);
|
|
if ($intComId <= 0) {
|
|
return '';
|
|
}
|
|
|
|
$strToken = uniqid('inv', true);
|
|
$strNow = function_exists('fxGetDateTime') ? fxGetDateTime() : date('Y-m-d H:i:s');
|
|
$arrSet = array();
|
|
|
|
$tabResetTok = $objDatabase->fxGetResults("SHOW COLUMNS FROM inscriptions_comptes LIKE 'com_reset_token'");
|
|
if ($tabResetTok != null && count($tabResetTok) > 0) {
|
|
$arrSet[] = "com_reset_token = '" . $objDatabase->fxEscape($strToken) . "'";
|
|
}
|
|
$tabResetDate = $objDatabase->fxGetResults("SHOW COLUMNS FROM inscriptions_comptes LIKE 'com_reset_date'");
|
|
if ($tabResetDate != null && count($tabResetDate) > 0) {
|
|
$arrSet[] = "com_reset_date = '" . $objDatabase->fxEscape($strNow) . "'";
|
|
}
|
|
|
|
if (count($arrSet) === 0) {
|
|
return '';
|
|
}
|
|
|
|
$objDatabase->fxQuery(
|
|
'UPDATE inscriptions_comptes SET ' . implode(', ', $arrSet)
|
|
. ' WHERE com_id = ' . $intComId
|
|
);
|
|
|
|
return $strToken;
|
|
}
|
|
|
|
/**
|
|
* MSIN-4478 — un seul kit actif/invite par personne+événement : révoque les autres rôles.
|
|
*/
|
|
function fxEveAccesPromoteurRevokeOtherKits($intComId, $intEveId, $intKeepRoleId, $intActorComId)
|
|
{
|
|
global $objDatabase;
|
|
|
|
$intComId = intval($intComId);
|
|
$intEveId = intval($intEveId);
|
|
$intKeepRoleId = intval($intKeepRoleId);
|
|
$intActorComId = intval($intActorComId);
|
|
|
|
if ($intComId <= 0 || $intEveId <= 0 || $intKeepRoleId <= 0 || !fxEveAccesIsEnabled()) {
|
|
return;
|
|
}
|
|
|
|
$tab = $objDatabase->fxGetResults(
|
|
"SELECT ea_id FROM inscriptions_eve_acces
|
|
WHERE com_id = $intComId
|
|
AND eve_id = $intEveId
|
|
AND role_id <> $intKeepRoleId
|
|
AND ea_statut IN ('actif', 'invite')"
|
|
);
|
|
if ($tab == null) {
|
|
return;
|
|
}
|
|
|
|
foreach ($tab as $row) {
|
|
fxEveAccesRevoke(intval($row['ea_id']), $intActorComId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* MSIN-4478 — après revoke : si stub jamais activé et plus aucun accès, désactive le compte (libère le courriel).
|
|
*/
|
|
function fxEveAccesPromoteurDeactivateOrphanInviteStub($intComId)
|
|
{
|
|
global $objDatabase;
|
|
|
|
$intComId = intval($intComId);
|
|
if ($intComId <= 0 || !fxEveAccesIsEnabled()) {
|
|
return false;
|
|
}
|
|
|
|
$arrCompte = $objDatabase->fxGetRow(
|
|
'SELECT com_id, com_actif, com_invite_team, com_lastvisit
|
|
FROM inscriptions_comptes WHERE com_id = ' . $intComId . ' LIMIT 1'
|
|
);
|
|
if (empty($arrCompte) || !fxEveAccesPromoteurCompteIsPendingInvite($arrCompte)) {
|
|
return false;
|
|
}
|
|
if (intval($arrCompte['com_actif'] ?? 0) !== 1) {
|
|
return false;
|
|
}
|
|
|
|
$intLeft = intval($objDatabase->fxGetVar(
|
|
"SELECT COUNT(*) FROM inscriptions_eve_acces
|
|
WHERE com_id = $intComId
|
|
AND ea_statut IN ('actif', 'invite')"
|
|
));
|
|
if ($intLeft > 0) {
|
|
return false;
|
|
}
|
|
|
|
$arrSet = array('com_actif = 0');
|
|
$tabResetTok = $objDatabase->fxGetResults("SHOW COLUMNS FROM inscriptions_comptes LIKE 'com_reset_token'");
|
|
if ($tabResetTok != null && count($tabResetTok) > 0) {
|
|
$arrSet[] = "com_reset_token = ''";
|
|
}
|
|
|
|
$objDatabase->fxQuery(
|
|
'UPDATE inscriptions_comptes SET ' . implode(', ', $arrSet)
|
|
. ' WHERE com_id = ' . $intComId
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* MSIN-4401 — Signature du destinataire invite (évite spoof invite_com).
|
|
*/
|
|
function fxEveAccesPromoteurInviteSig($intComId, $intEveId)
|
|
{
|
|
$strSecret = defined('QR_SECRET_KEY') ? (string)QR_SECRET_KEY : 'ms1-invite';
|
|
return substr(hash_hmac('sha256', intval($intComId) . '|' . intval($intEveId), $strSecret), 0, 20);
|
|
}
|
|
|
|
function fxEveAccesPromoteurInviteSigValid($intComId, $intEveId, $strSig)
|
|
{
|
|
$strExpect = fxEveAccesPromoteurInviteSig($intComId, $intEveId);
|
|
return is_string($strSig) && $strSig !== '' && hash_equals($strExpect, $strSig);
|
|
}
|
|
|
|
/** Ajoute invite_com + invite_sig à une URL /mobile (ou absolue). */
|
|
function fxEveAccesPromoteurAppendInviteParams($strUrl, $intComId, $intEveId)
|
|
{
|
|
$strSep = (strpos($strUrl, '?') !== false) ? '&' : '?';
|
|
return $strUrl . $strSep
|
|
. 'invite_com=' . intval($intComId)
|
|
. '&invite_sig=' . rawurlencode(fxEveAccesPromoteurInviteSig($intComId, $intEveId));
|
|
}
|
|
|
|
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);
|
|
$intComId = intval($arrCompte['com_id'] ?? 0);
|
|
$strEveParam = urlencode(base64_encode((string)$intEveId));
|
|
$strComptePath = ($strLangue === 'en') ? '/account' : '/compte';
|
|
// MSIN-4401 — Lien terrain : /mobile + identité destinataire (invite_com/sig).
|
|
$strMobileUrl = $vDomaine . '/mobile?promoteur_eve_id=' . $strEveParam;
|
|
if ($strLangue === 'en') {
|
|
$strMobileUrl = $vDomaine . '/mobile/en?promoteur_eve_id=' . $strEveParam;
|
|
}
|
|
$strMobileUrl = fxEveAccesPromoteurAppendInviteParams($strMobileUrl, $intComId, $intEveId);
|
|
|
|
// Placeholders Info (contenu HTML géré dans table info).
|
|
$arrMailVars = array(
|
|
'%prenom%' => htmlspecialchars($strPrenom, ENT_QUOTES, 'UTF-8'),
|
|
'%eve_nom%' => htmlspecialchars($strEveNom, ENT_QUOTES, 'UTF-8'),
|
|
'%kit%' => htmlspecialchars($strKit, ENT_QUOTES, 'UTF-8'),
|
|
'%login%' => htmlspecialchars((string)$strLogin, ENT_QUOTES, 'UTF-8'),
|
|
'%mobile_url%' => htmlspecialchars($strMobileUrl, ENT_QUOTES, 'UTF-8'),
|
|
);
|
|
|
|
if ($blnNewAccount && $strResetToken !== '') {
|
|
$subject = strtr(fxEveAccesPromoteurT('eve_acces_promoteur_mail_new_subject', $strLangue), $arrMailVars);
|
|
$message = strtr(fxEveAccesPromoteurT('eve_acces_promoteur_mail_new_body', $strLangue), $arrMailVars);
|
|
$attachment_label = fxEveAccesPromoteurT('eve_acces_promoteur_mail_new_cta', $strLangue);
|
|
$attachment_url = $vDomaine . $strComptePath . '/reset?t=' . urlencode($strResetToken)
|
|
. '&m=' . urlencode($arrCompte['com_courriel'])
|
|
. '&id=' . intval($arrCompte['com_id']);
|
|
} else {
|
|
$subject = strtr(fxEveAccesPromoteurT('eve_acces_promoteur_mail_existing_subject', $strLangue), $arrMailVars);
|
|
$message = strtr(fxEveAccesPromoteurT('eve_acces_promoteur_mail_existing_body', $strLangue), $arrMailVars);
|
|
$attachment_label = fxEveAccesPromoteurT('eve_acces_promoteur_mail_existing_cta', $strLangue);
|
|
$attachment_url = $strMobileUrl;
|
|
}
|
|
|
|
$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 / MSIN-4478 — 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'
|
|
);
|
|
// fxGetRow peut retourner false (pas null) selon l'environnement.
|
|
if (empty($arrEve) || empty($arrRole)) {
|
|
return array('state' => 'error', 'message' => fxEveAccesPromoteurT('eve_acces_promoteur_err_generic', $strLangue));
|
|
}
|
|
|
|
$strCompteCols = 'com_id, com_prenom, com_nom, com_courriel, com_login, com_actif, com_lastvisit';
|
|
if (fxEveAccesPromoteurHasInviteTeamColumn()) {
|
|
$strCompteCols .= ', com_invite_team';
|
|
}
|
|
|
|
$rowExisting = $objDatabase->fxGetRow(
|
|
"SELECT $strCompteCols
|
|
FROM inscriptions_comptes
|
|
WHERE com_courriel = '" . $objDatabase->fxEscape($strCourriel) . "'
|
|
ORDER BY com_actif DESC, com_id DESC
|
|
LIMIT 1"
|
|
);
|
|
|
|
$blnCreated = false;
|
|
$blnPending = false;
|
|
$strResetToken = '';
|
|
$arrCompte = null;
|
|
|
|
if (!empty($rowExisting) && is_array($rowExisting)) {
|
|
$blnPending = fxEveAccesPromoteurCompteIsPendingInvite($rowExisting);
|
|
|
|
// MSIN-4478 — stub désactivé après revoke : on le réactive pour réinviter.
|
|
if (intval($rowExisting['com_actif']) !== 1) {
|
|
if (!$blnPending) {
|
|
return array('state' => 'error', 'message' => fxEveAccesPromoteurT('eve_acces_promoteur_err_inactive', $strLangue));
|
|
}
|
|
$objDatabase->fxQuery(
|
|
'UPDATE inscriptions_comptes SET com_actif = 1 WHERE com_id = ' . intval($rowExisting['com_id'])
|
|
);
|
|
$rowExisting['com_actif'] = 1;
|
|
}
|
|
|
|
$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;
|
|
}
|
|
|
|
if ($blnPending) {
|
|
$strResetToken = fxEveAccesPromoteurRefreshResetToken(intval($arrCompte['com_id']));
|
|
}
|
|
} 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;
|
|
$blnPending = 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,
|
|
'com_invite_team' => 1,
|
|
'com_lastvisit' => '',
|
|
);
|
|
}
|
|
|
|
// MSIN-4478 — changer de kit = remplacer, pas empiler.
|
|
fxEveAccesPromoteurRevokeOtherKits(
|
|
intval($arrCompte['com_id']),
|
|
$intEveId,
|
|
$intRoleId,
|
|
$intGrantedBy
|
|
);
|
|
|
|
// Stub jamais activé → statut invite + mail mot de passe (même au 2e envoi).
|
|
$strGrantStatut = $blnPending ? 'invite' : 'actif';
|
|
$arrGrant = fxEveAccesGrant(
|
|
intval($arrCompte['com_id']),
|
|
$intEveId,
|
|
$intRoleId,
|
|
$intJours,
|
|
$intGrantedBy,
|
|
'MSIN-4478 invitation promoteur',
|
|
$strGrantStatut
|
|
);
|
|
|
|
if (($arrGrant['state'] ?? '') !== 'success') {
|
|
return array(
|
|
'state' => 'error',
|
|
'message' => $arrGrant['message'] ?? fxEveAccesPromoteurT('eve_acces_promoteur_err_generic', $strLangue),
|
|
);
|
|
}
|
|
|
|
// L'accès est déjà enregistré : un échec mail ne doit pas faire planter la page.
|
|
try {
|
|
fxEveAccesPromoteurSendInviteMail(
|
|
$arrCompte,
|
|
$arrEve,
|
|
$arrRole,
|
|
$strLangue,
|
|
$blnPending,
|
|
$strResetToken
|
|
);
|
|
} catch (Throwable $e) {
|
|
// ignore
|
|
}
|
|
|
|
if ($blnCreated) {
|
|
$strMsg = fxEveAccesPromoteurT('eve_acces_promoteur_ok_created', $strLangue);
|
|
} elseif ($blnPending) {
|
|
$strMsg = fxEveAccesPromoteurT('eve_acces_promoteur_ok_pending', $strLangue);
|
|
} else {
|
|
$strMsg = fxEveAccesPromoteurT('eve_acces_promoteur_ok_existing', $strLangue);
|
|
}
|
|
|
|
return array(
|
|
'state' => 'success',
|
|
'created' => $blnCreated,
|
|
'message' => $strMsg,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* MSIN-4401 / MSIN-4478 — 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');
|
|
}
|
|
|
|
$arrResult = fxEveAccesRevoke($intEaId, $intActorComId);
|
|
if (($arrResult['state'] ?? '') === 'success') {
|
|
fxEveAccesPromoteurDeactivateOrphanInviteStub(intval($tab['com_id'] ?? 0));
|
|
}
|
|
|
|
return $arrResult;
|
|
}
|
|
|
|
/**
|
|
* MSIN-4401 — renvoi du courriel d'invitation (lien /mobile + invite_com/sig).
|
|
* Compte non activé : nouveau jeton reset + mail « créer mot de passe ».
|
|
*/
|
|
function fxEveAccesPromoteurResendMail($intEaId, $intActorComId, $intEveId, $strLangue = 'fr')
|
|
{
|
|
global $objDatabase;
|
|
|
|
$intEaId = intval($intEaId);
|
|
$intActorComId = intval($intActorComId);
|
|
$intEveId = intval($intEveId);
|
|
$strLangue = ($strLangue === 'en') ? 'en' : 'fr';
|
|
|
|
if ($intEaId <= 0 || $intEveId <= 0 || !fxEveAccesCanTeamInvite($intActorComId, $intEveId)) {
|
|
return array('state' => 'error', 'message' => fxEveAccesPromoteurT('eve_acces_promoteur_err_resend', $strLangue));
|
|
}
|
|
|
|
$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', 'message' => fxEveAccesPromoteurT('eve_acces_promoteur_err_resend', $strLangue));
|
|
}
|
|
|
|
$strStatut = (string)($tab['ea_statut'] ?? '');
|
|
if ($strStatut !== 'actif' && $strStatut !== 'invite') {
|
|
return array('state' => 'error', 'message' => fxEveAccesPromoteurT('eve_acces_promoteur_err_resend', $strLangue));
|
|
}
|
|
|
|
$blnOk = fxEveAccesCanTeamManage($intActorComId, $intEveId)
|
|
|| (intval($tab['ea_granted_by'] ?? 0) === $intActorComId);
|
|
if (!$blnOk) {
|
|
return array('state' => 'error', 'message' => fxEveAccesPromoteurT('eve_acces_promoteur_err_resend', $strLangue));
|
|
}
|
|
|
|
$intComId = intval($tab['com_id'] ?? 0);
|
|
$arrCompte = $objDatabase->fxGetRow(
|
|
'SELECT * FROM inscriptions_comptes WHERE com_id = ' . $intComId . ' LIMIT 1'
|
|
);
|
|
if ($arrCompte === null || intval($arrCompte['com_actif'] ?? 0) !== 1) {
|
|
return array('state' => 'error', 'message' => fxEveAccesPromoteurT('eve_acces_promoteur_err_resend', $strLangue));
|
|
}
|
|
|
|
$arrEve = function_exists('fxGetEvenementsId') ? fxGetEvenementsId($intEveId) : null;
|
|
if (!is_array($arrEve)) {
|
|
return array('state' => 'error', 'message' => fxEveAccesPromoteurT('eve_acces_promoteur_err_resend', $strLangue));
|
|
}
|
|
|
|
$arrRole = $objDatabase->fxGetRow(
|
|
'SELECT * FROM inscriptions_eve_roles WHERE role_id = ' . intval($tab['role_id']) . ' LIMIT 1'
|
|
);
|
|
if ($arrRole === null) {
|
|
return array('state' => 'error', 'message' => fxEveAccesPromoteurT('eve_acces_promoteur_err_resend', $strLangue));
|
|
}
|
|
|
|
$strLastVisit = trim((string)($arrCompte['com_lastvisit'] ?? ''));
|
|
$blnInviteTeam = intval($arrCompte['com_invite_team'] ?? 0) === 1;
|
|
$blnPending = ($strStatut === 'invite')
|
|
|| fxEveAccesPromoteurCompteIsPendingInvite($arrCompte)
|
|
|| ($blnInviteTeam && ($strLastVisit === '' || strpos($strLastVisit, '0000-00-00') === 0));
|
|
|
|
$strResetToken = '';
|
|
if ($blnPending) {
|
|
$strResetToken = fxEveAccesPromoteurRefreshResetToken($intComId);
|
|
}
|
|
|
|
try {
|
|
fxEveAccesPromoteurSendInviteMail($arrCompte, $arrEve, $arrRole, $strLangue, $blnPending, $strResetToken);
|
|
} catch (Throwable $e) {
|
|
return array('state' => 'error', 'message' => fxEveAccesPromoteurT('eve_acces_promoteur_err_resend', $strLangue));
|
|
}
|
|
|
|
return array(
|
|
'state' => 'success',
|
|
'message' => fxEveAccesPromoteurT('eve_acces_promoteur_ok_resent', $strLangue),
|
|
);
|
|
}
|
|
|
|
/** MSIN-4401 — rendu page promoteur (invitation + liste). */
|
|
function fxEveAccesPromoteurRenderPage($intEveId, $strLangue = 'fr', $strFlash = '', $strError = '')
|
|
{
|
|
global $vDomaine;
|
|
|
|
$intEveId = intval($intEveId);
|
|
$strLangue = ($strLangue === 'en') ? 'en' : 'fr';
|
|
$strPage = ($strLangue === 'fr') ? '/compte' : '/account';
|
|
$strBackUrl = function_exists('fxGetPromoteurBackUrl')
|
|
? 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])) {
|
|
$strEveNom = fxUnescape($arrEve['eve_nom_' . $strLangue]);
|
|
}
|
|
|
|
$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">';
|
|
|
|
echo '<a class="btn btn-primary rounded-pill" id="link_back" href="' . fxEveAccesPromoteurEsc($strBackUrl) . '">';
|
|
echo '<i class="fa fa-chevron-left mr-2" aria-hidden="true"></i>';
|
|
echo fxEveAccesPromoteurEsc(afficheTexte('promoteur_back', 0));
|
|
echo '</a>';
|
|
|
|
echo '<div class="eve-acces-promoteur-head mt-4 mb-3">';
|
|
echo '<h1 class="h4 mb-1">' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_title', $strLangue));
|
|
if (function_exists('fxEveAccesPermInspectBadge')) {
|
|
echo ' ' . fxEveAccesPermInspectBadge('team.invite', 'menu');
|
|
echo ' ' . fxEveAccesPermInspectBadge('team.manage', 'menu');
|
|
}
|
|
echo '</h1>';
|
|
if ($strEveNom !== '') {
|
|
echo '<p class="text-muted mb-1">' . fxEveAccesPromoteurEsc($strEveNom) . '</p>';
|
|
}
|
|
echo '<p class="text-muted small mb-0">' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_intro', $strLangue)) . '</p>';
|
|
echo '</div>';
|
|
|
|
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">';
|
|
|
|
if (count($arrRoles) === 0) {
|
|
echo '<p class="text-muted small mb-0">' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_no_kits', $strLangue)) . '</p>';
|
|
} else {
|
|
$strAjaxUrl = $vDomaine . '/ajax_verif_courriel.php';
|
|
echo '<form method="post" action="' . fxEveAccesPromoteurEsc($strFormAction)
|
|
. '" class="eve-acces-promoteur-invite-form" id="eve_acces_invite_form"'
|
|
. ' data-ajax-url="' . fxEveAccesPromoteurEsc($strAjaxUrl) . '"'
|
|
. ' data-msg-exists="' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_check_exists', $strLangue)) . '"'
|
|
. ' data-msg-new="' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_check_new', $strLangue)) . '">';
|
|
echo '<input type="hidden" name="eve_acces_action" value="invite">';
|
|
echo '<div class="form-row">';
|
|
echo '<div class="form-group col-md-12">';
|
|
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 id="invite_courriel_feedback" class="small mt-2 mb-0" style="display:none;" role="status" aria-live="polite"></div>';
|
|
echo '</div>';
|
|
echo '</div>';
|
|
echo '<div class="form-row" id="invite_name_row" style="display:none;">';
|
|
echo '<div class="form-group col-md-6">';
|
|
echo '<label for="invite_prenom">' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_field_prenom', $strLangue))
|
|
. ' <span class="text-danger" aria-hidden="true">*</span></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-6">';
|
|
echo '<label for="invite_nom">' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_field_nom', $strLangue))
|
|
. ' <span class="text-danger" aria-hidden="true">*</span></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" id="invite_submit_btn">';
|
|
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(fxEveAccesPromoteurT('eve_acces_promoteur_invite_courriel_help', $strLangue))
|
|
. '</p>';
|
|
echo '</form>';
|
|
fxEveAccesPromoteurRenderInviteScript();
|
|
}
|
|
|
|
echo '</div></div>';
|
|
|
|
echo '<div class="card mb-3 eve-acces-promoteur-card">';
|
|
echo '<div class="card-header d-flex justify-content-between align-items-center flex-wrap">';
|
|
echo '<span><i class="fa fa-users mr-2" aria-hidden="true"></i>';
|
|
echo fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_list_title', $strLangue));
|
|
echo '</span>';
|
|
echo '<span class="small text-muted">' . count($arrRows) . '</span>';
|
|
echo '</div>';
|
|
echo '<div class="card-body p-0">';
|
|
echo '<div class="table-responsive mb-0">';
|
|
echo '<table class="table table-sm table-striped table-hover mb-0">';
|
|
echo '<thead class="thead-light"><tr>';
|
|
echo '<th>' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_col_compte', $strLangue)) . '</th>';
|
|
echo '<th>' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_col_kit', $strLangue)) . '</th>';
|
|
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="6" class="text-muted px-3 py-3">'
|
|
. fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_empty', $strLangue))
|
|
. '</td></tr>';
|
|
} else {
|
|
foreach ($arrRows as $arrRow) {
|
|
$strCompte = trim(($arrRow['com_prenom'] ?? '') . ' ' . ($arrRow['com_nom'] ?? ''));
|
|
$strKit = trim((string)($arrRow[$strRoleLabelKey] ?? $arrRow['role_label_fr'] ?? ''));
|
|
if ($strKit === '') {
|
|
$strKit = (string)($arrRow['role_code'] ?? '');
|
|
}
|
|
$strStatut = (string)($arrRow['ea_statut'] ?? '');
|
|
$strLastVisit = trim((string)($arrRow['com_lastvisit'] ?? ''));
|
|
$blnInviteTeam = intval($arrRow['com_invite_team'] ?? 0) === 1;
|
|
// Nouveau compte invitation : en attente tant que pas activé (mdp) / jamais connecté.
|
|
$blnPending = ($strStatut === 'invite')
|
|
|| ($blnInviteTeam && ($strLastVisit === '' || strpos($strLastVisit, '0000-00-00') === 0));
|
|
|
|
$strBadge = 'secondary';
|
|
$strStatutLabel = $strStatut;
|
|
if ($blnPending) {
|
|
$strBadge = 'warning';
|
|
$strStatutLabel = fxEveAccesPromoteurT('eve_acces_promoteur_statut_pending', $strLangue);
|
|
} elseif ($strStatut === 'actif') {
|
|
$strBadge = 'success';
|
|
$strStatutLabel = fxEveAccesPromoteurT('eve_acces_promoteur_statut_actif', $strLangue);
|
|
}
|
|
|
|
$blnCanRevoke = $blnCanManage
|
|
|| (intval($arrRow['ea_granted_by'] ?? 0) === $intActor);
|
|
|
|
echo '<tr>';
|
|
echo '<td>';
|
|
echo '<div>' . fxEveAccesPromoteurEsc($strCompte) . '</div>';
|
|
echo '<div class="small text-muted">' . fxEveAccesPromoteurEsc($arrRow['com_courriel'] ?? '') . '</div>';
|
|
echo '</td>';
|
|
echo '<td>' . fxEveAccesPromoteurEsc($strKit) . '</td>';
|
|
echo '<td class="text-center"><span class="badge badge-' . $strBadge . '">'
|
|
. fxEveAccesPromoteurEsc($strStatutLabel) . '</span></td>';
|
|
echo '<td class="text-nowrap small">'
|
|
. 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 mr-1">';
|
|
echo '<input type="hidden" name="eve_acces_action" value="resend">';
|
|
echo '<input type="hidden" name="ea_id" value="' . intval($arrRow['ea_id']) . '">';
|
|
echo '<button type="submit" class="btn btn-sm btn-outline-primary rounded-0" title="'
|
|
. fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_btn_resend', $strLangue)) . '">';
|
|
echo '<i class="fa fa-envelope mr-1" aria-hidden="true"></i>';
|
|
echo fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_btn_resend', $strLangue));
|
|
echo '</button></form>';
|
|
echo '<form method="post" action="' . fxEveAccesPromoteurEsc($strFormAction) . '" class="d-inline"';
|
|
echo ' onsubmit="return confirm(\'' . fxEveAccesPromoteurEsc(
|
|
fxEveAccesPromoteurT('eve_acces_promoteur_confirm_revoke', $strLangue)
|
|
) . '\');">';
|
|
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>';
|
|
}
|
|
}
|
|
|
|
echo '</tbody></table></div></div></div>';
|
|
|
|
echo '<a class="btn btn-primary rounded-pill mt-2" href="' . fxEveAccesPromoteurEsc($strBackUrl) . '">';
|
|
echo '<i class="fa fa-chevron-left mr-2" aria-hidden="true"></i>';
|
|
echo fxEveAccesPromoteurEsc(afficheTexte('promoteur_back', 0));
|
|
echo '</a>';
|
|
|
|
echo '</div>';
|
|
}
|
|
|
|
/** MSIN-4401 — JS vérif courriel via ajax_verif_courriel.php (existant). */
|
|
function fxEveAccesPromoteurRenderInviteScript()
|
|
{
|
|
static $blnDone = false;
|
|
if ($blnDone) {
|
|
return;
|
|
}
|
|
$blnDone = true;
|
|
?>
|
|
<script>
|
|
(function () {
|
|
var form = document.getElementById('eve_acces_invite_form');
|
|
if (!form) return;
|
|
|
|
var input = document.getElementById('invite_courriel');
|
|
var feedback = document.getElementById('invite_courriel_feedback');
|
|
var nameRow = document.getElementById('invite_name_row');
|
|
var prenom = document.getElementById('invite_prenom');
|
|
var nom = document.getElementById('invite_nom');
|
|
var submitBtn = document.getElementById('invite_submit_btn');
|
|
var ajaxUrl = form.getAttribute('data-ajax-url') || '';
|
|
var msgExists = form.getAttribute('data-msg-exists') || '';
|
|
var msgNew = form.getAttribute('data-msg-new') || '';
|
|
var timer = null;
|
|
var lastChecked = '';
|
|
|
|
function setFeedback(msg, kind) {
|
|
if (!feedback) return;
|
|
if (!msg) {
|
|
feedback.style.display = 'none';
|
|
feedback.textContent = '';
|
|
feedback.className = 'small mt-2 mb-0';
|
|
return;
|
|
}
|
|
feedback.style.display = 'block';
|
|
feedback.textContent = msg;
|
|
feedback.className = 'small mt-2 mb-0 text-' + (kind || 'muted');
|
|
}
|
|
|
|
function setNameMode(mode) {
|
|
if (!nameRow || !prenom || !nom) return;
|
|
if (mode === 'required') {
|
|
nameRow.style.display = '';
|
|
prenom.required = true;
|
|
nom.required = true;
|
|
if (submitBtn) submitBtn.disabled = false;
|
|
} else {
|
|
nameRow.style.display = 'none';
|
|
prenom.required = false;
|
|
nom.required = false;
|
|
if (submitBtn) submitBtn.disabled = false;
|
|
}
|
|
}
|
|
|
|
function isValidEmail(v) {
|
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
|
|
}
|
|
|
|
function checkCourriel() {
|
|
var email = (input.value || '').trim().toLowerCase();
|
|
if (email === '' || !isValidEmail(email)) {
|
|
lastChecked = '';
|
|
setFeedback('', '');
|
|
setNameMode('hidden');
|
|
return;
|
|
}
|
|
if (email === lastChecked) return;
|
|
|
|
var requestEmail = email;
|
|
// Même API que la fiche compte : true = libre, false = déjà pris (actif).
|
|
var qs = 'com_courriel=' + encodeURIComponent(email);
|
|
|
|
fetch(ajaxUrl + '?' + qs, { credentials: 'same-origin' })
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (libre) {
|
|
if ((input.value || '').trim().toLowerCase() !== requestEmail) return;
|
|
lastChecked = requestEmail;
|
|
if (libre === false || libre === 'false' || libre === 0) {
|
|
setFeedback(msgExists, 'success');
|
|
setNameMode('hidden');
|
|
} else {
|
|
setFeedback(msgNew, 'info');
|
|
setNameMode('required');
|
|
}
|
|
})
|
|
.catch(function () {
|
|
if ((input.value || '').trim().toLowerCase() !== requestEmail) return;
|
|
lastChecked = '';
|
|
setFeedback('', '');
|
|
setNameMode('hidden');
|
|
});
|
|
}
|
|
|
|
input.addEventListener('blur', checkCourriel);
|
|
input.addEventListener('input', function () {
|
|
if (timer) clearTimeout(timer);
|
|
timer = setTimeout(checkCourriel, 450);
|
|
});
|
|
})();
|
|
</script>
|
|
<?php
|
|
}
|
|
|