diff --git a/inc_tableau_eve_acces.php b/inc_tableau_eve_acces.php index 5c759e6..a8b8bf6 100644 --- a/inc_tableau_eve_acces.php +++ b/inc_tableau_eve_acces.php @@ -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); diff --git a/php/inc_fx_eve_acces.php b/php/inc_fx_eve_acces.php index 9dd299f..2277509 100644 --- a/php/inc_fx_eve_acces.php +++ b/php/inc_fx_eve_acces.php @@ -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'); } diff --git a/php/inc_fx_eve_acces_promoteur.php b/php/inc_fx_eve_acces_promoteur.php index d68bcd5..720ffb4 100644 --- a/php/inc_fx_eve_acces_promoteur.php +++ b/php/inc_fx_eve_acces_promoteur.php @@ -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 . ',

' + . 'Un accès vous a été accordé pour ' . htmlspecialchars($strEveNom, ENT_QUOTES, 'UTF-8') . '' + . ' (kit : ' . htmlspecialchars($strKit, ENT_QUOTES, 'UTF-8') . ').

' + . 'Un compte a été créé avec le courriel ' . htmlspecialchars($strLogin, ENT_QUOTES, 'UTF-8') . '.
' + . '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 . ',

' + . 'You were granted access to ' . htmlspecialchars($strEveNom, ENT_QUOTES, 'UTF-8') . '' + . ' (kit: ' . htmlspecialchars($strKit, ENT_QUOTES, 'UTF-8') . ').

' + . 'An account was created with ' . htmlspecialchars($strLogin, ENT_QUOTES, 'UTF-8') . '.
' + . '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 . ',

' + . 'Un accès vous a été accordé pour ' . htmlspecialchars($strEveNom, ENT_QUOTES, 'UTF-8') . '' + . ' (kit : ' . htmlspecialchars($strKit, ENT_QUOTES, 'UTF-8') . ').

' + . 'Connectez-vous avec votre compte existant (' . htmlspecialchars($strLogin, ENT_QUOTES, 'UTF-8') . ').'; + $attachment_label = 'Ouvrir mon compte'; + } else { + $subject = 'New access — ' . $strEveNom; + $message = 'Hello ' . $strPrenom . ',

' + . 'You were granted access to ' . htmlspecialchars($strEveNom, ENT_QUOTES, 'UTF-8') . '' + . ' (kit: ' . htmlspecialchars($strKit, ENT_QUOTES, 'UTF-8') . ').

' + . 'Sign in with your existing account (' . htmlspecialchars($strLogin, ENT_QUOTES, 'UTF-8') . ').'; + $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 '
'; @@ -104,14 +503,71 @@ function fxEveAccesPromoteurRenderPage($intEveId, $strLangue = 'fr') echo '

' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_intro', $strLangue)) . '

'; echo '
'; - // Emplacement invitation (étape suivante) — même format de carte v2 + if ($strFlash !== '') { + echo '
' . fxEveAccesPromoteurEsc($strFlash) . '
'; + } + if ($strError !== '') { + echo '
' . fxEveAccesPromoteurEsc($strError) . '
'; + } + echo '
'; echo '
'; echo ''; echo fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_invite_title', $strLangue)); echo '
'; echo '
'; - echo '

' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_invite_soon', $strLangue)) . '

'; + + if (count($arrRoles) === 0) { + echo '

' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_no_kits', $strLangue)) . '

'; + } else { + echo '
'; + echo ''; + echo '
'; + echo '
'; + echo ''; + echo ''; + echo '
'; + echo '
'; + echo ''; + echo ''; + echo '
'; + echo '
'; + echo ''; + echo ''; + echo '
'; + echo '
'; + echo '
'; + echo '
'; + echo ''; + echo ''; + echo '
'; + echo '
'; + echo ''; + echo ''; + echo '' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_jours_help', $strLangue)) . ''; + echo '
'; + echo '
'; + echo ''; + echo '
'; + echo '
'; + echo '

' + . 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éé.' + ) + . '

'; + echo '
'; + } + echo '
'; echo '
'; @@ -130,10 +586,11 @@ function fxEveAccesPromoteurRenderPage($intEveId, $strLangue = 'fr') echo '' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_col_statut', $strLangue)) . ''; echo '' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_col_depuis', $strLangue)) . ''; echo '' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_col_expire', $strLangue)) . ''; + echo ''; echo ''; if (count($arrRows) === 0) { - echo '' + echo '' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_empty', $strLangue)) . ''; } else { @@ -151,6 +608,9 @@ function fxEveAccesPromoteurRenderPage($intEveId, $strLangue = 'fr') $strBadge = 'info'; } + $blnCanRevoke = $blnCanManage + || (intval($arrRow['ea_granted_by'] ?? 0) === $intActor); + echo ''; echo ''; echo '
' . fxEveAccesPromoteurEsc($strCompte) . '
'; @@ -163,6 +623,19 @@ function fxEveAccesPromoteurRenderPage($intEveId, $strLangue = 'fr') . fxEveAccesPromoteurEsc(fxEveAccesPromoteurFormatDt($arrRow['ea_created_at'] ?? '')) . ''; echo '' . fxEveAccesPromoteurEsc(fxEveAccesPromoteurFormatDt($arrRow['ea_expires_at'] ?? '')) . ''; + echo ''; + if ($blnCanRevoke && intval($arrRow['com_id']) !== $intActor) { + echo '
'; + echo ''; + echo ''; + echo '
'; + } + echo ''; echo ''; } } diff --git a/sql/MSIN-4401-com-invite-team-flag.sql b/sql/MSIN-4401-com-invite-team-flag.sql new file mode 100644 index 0000000..b9a2d73 --- /dev/null +++ b/sql/MSIN-4401-com-invite-team-flag.sql @@ -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;