Enhance multi-page AJAX handling by enforcing context for translations in ajax_inscr_gestion and ajax_promoteur_hub. Introduce new language keys for improved user feedback and error messages. Increment version code to 4.72.806 to reflect these changes.

This commit is contained in:
2026-07-13 17:17:53 -04:00
parent af2ddba2fa
commit d3bb32eb97
15 changed files with 777 additions and 216 deletions

View File

@ -12,6 +12,26 @@ alwaysApply: true
- Référencer le ticket : `// MSIN-4405` ou bloc PHPDoc `* MSIN-4405 — …`.
- Langue : **français** pour les commentaires métier (comme le reste du projet), sauf si le fichier est entièrement en anglais.
## Libellés UI (table `info`)
- Tout texte affiché à lutilisateur (FR/EN) passe par **Info** (`afficheTexte` / clés `info_clef`), **jamais** codé en dur dans le PHP/JS.
- Nouvelle clé = script SQL `sql/MSIN-xxxx-….sql` (INSERT dans `info`, fr + en), exécutable en pilote/prod.
- Les fallbacks PHP FR/EN ne remplacent pas ce script : ce sont un filet de secours uniquement.
## AJAX multi-pages (Info)
Tout endpoint AJAX qui lit/écrit des libellés Info et peut être appelé depuis plusieurs pages (compte, hub, `/mobile`) doit **forcer le contexte `compte.php`** avant les includes utiles :
```php
define('MS1_…_AJAX', true); // optionnel, pattern existant
$_SERVER['PHP_SELF'] = '/compte.php';
$_SERVER['SCRIPT_NAME'] = '/compte.php';
$vPage = 'compte.php';
$vtexte_page = obtenirTextepage($vPage, $strLangue, 2);
```
Objectif : éviter les clés créées sous `info_prg=ajax_….php` et les libellés manquants selon la page appelante. Références : `ajax_promoteur_hub.php`, `ajax_bib_range.php`, `ajax_inscr_gestion.php`.
## Scripts SQL
En-tête obligatoire en tête de fichier :

View File

@ -5,6 +5,13 @@
*/
session_start();
// MSIN-4401 — Traductions toujours au nom de compte.php (jamais ajax_inscr_gestion.php).
// Evite les doublons info_prg=ajax_*.php et les libelles manquants selon la page appelante
// (hub, compte, /mobile). Meme pattern que ajax_promoteur_hub / ajax_bib_range.
define('MS1_INSCR_GESTION_AJAX', true);
$_SERVER['PHP_SELF'] = '/compte.php';
$_SERVER['SCRIPT_NAME'] = '/compte.php';
header('Content-Type: application/json; charset=utf-8');
// Filet de securite : transforme tout fatal (include manquant, fonction
@ -36,10 +43,14 @@ require_once 'php/inc_fx_eve_acces.php';
$intComId = intval($_SESSION['com_id'] ?? 0);
$strAction = trim((string)($_REQUEST['a'] ?? ''));
$strLangue = trim((string)($_REQUEST['lang'] ?? 'fr'));
if ($strLangue === '') {
if ($strLangue === '' || !in_array($strLangue, array('fr', 'en'), true)) {
$strLangue = 'fr';
}
$vPage = 'compte.php';
$vtexte_page = obtenirTextepage($vPage, $strLangue, 2);
fxInscrGestionInitTextes($strLangue);
if ($intComId <= 0) {
$GLOBALS['__inscr_gestion_done'] = true;
// MSIN-4401 — code session : le client recharge pour afficher le login.

View File

@ -2,18 +2,24 @@
session_start();
// MSIN-4401 — Traductions toujours au nom de compte.php (jamais ajax_inscr_gestion_qr.php).
define('MS1_INSCR_GESTION_AJAX', true);
$_SERVER['PHP_SELF'] = '/compte.php';
$_SERVER['SCRIPT_NAME'] = '/compte.php';
require_once 'php/inc_fonctions.php';
require_once 'php/inc_fx_inscriptions_gestion.php';
global $vDomaine;
$strLangue = 'fr';
if (!empty($_REQUEST['lang'])) {
if (!empty($_REQUEST['lang']) && in_array($_REQUEST['lang'], array('fr', 'en'), true)) {
$strLangue = $_REQUEST['lang'];
}
$vPage = 'compte.php';
$vtexte_page = obtenirTextepage($vPage, $strLangue, 2);
fxInscrGestionInitTextes($strLangue);
header('Content-Type: application/json; charset=utf-8');

View File

@ -26,7 +26,7 @@ if (empty($_SESSION['com_id'])) {
echo json_encode(array(
'ok' => false,
'code' => 'session',
'msg' => 'Session expirée',
'msg' => fxPromoteurHubTexte('promoteur_hub_ajax_session', 0),
));
exit;
}
@ -34,18 +34,18 @@ if (empty($_SESSION['com_id'])) {
$intComId = intval($_SESSION['com_id']);
if (!in_array($strAction, array('event_stats', 'event_links'), true) || $intEveId <= 0) {
echo json_encode(array('ok' => false, 'msg' => 'Requête invalide'));
echo json_encode(array('ok' => false, 'msg' => fxPromoteurHubTexte('promoteur_hub_ajax_invalid', 0)));
exit;
}
if (!fxPromoteurHubComHasEvent($intComId, $intEveId)) {
echo json_encode(array('ok' => false, 'msg' => 'Accès refusé'));
echo json_encode(array('ok' => false, 'msg' => fxPromoteurHubTexte('promoteur_hub_ajax_denied', 0)));
exit;
}
if ($strAction === 'event_stats') {
if (!fxPromoteurHubCanViewDashboard($intComId, $intEveId)) {
echo json_encode(array('ok' => false, 'msg' => 'Accès refusé'));
echo json_encode(array('ok' => false, 'msg' => fxPromoteurHubTexte('promoteur_hub_ajax_denied', 0)));
exit;
}
@ -61,7 +61,7 @@ if ($strAction === 'event_stats') {
$arrItem = fxPromoteurHubGetEventItemForLinks($intComId, $intEveId);
if ($arrItem === null) {
echo json_encode(array('ok' => false, 'msg' => 'Événement introuvable'));
echo json_encode(array('ok' => false, 'msg' => fxPromoteurHubTexte('promoteur_hub_ajax_not_found', 0)));
exit;
}

View File

@ -34,11 +34,9 @@ if ($intEveId <= 0
|| !fxEveAccesCanTeamInvite($intActor, $intEveId)) {
// Headers déjà envoyés (include après layout) : message + lien, pas de header Location.
echo '<div class="alert alert-warning">';
echo fxEveAccesPromoteurEsc(
$strLangue === 'en' ? 'Access denied.' : 'Accès refusé.'
);
echo fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_access_denied', $strLangue));
echo ' <a href="' . htmlspecialchars($strRedirect, ENT_QUOTES, 'UTF-8') . '">';
echo fxEveAccesPromoteurEsc($strLangue === 'en' ? 'Back' : 'Retour');
echo fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_btn_back', $strLangue));
echo '</a></div>';
return;
}

View File

@ -6,6 +6,11 @@
'use strict';
var cfg = window.MS1_V2_INSCR_GESTION || {};
var i18n = cfg.i18n || {};
function t(key) {
return (i18n[key] && String(i18n[key])) || '';
}
// MSIN-4401 — Session expiree en AJAX : recharger → ecran de login (pas un message mort).
function fxInscrRedirectIfSessionExpired(result) {
@ -189,10 +194,10 @@
$panel.attr('data-loaded', '1');
focusRefundPanel();
} else {
$panel.html('<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">\u26a0\ufe0f ' + ((result && result.message) || 'Erreur de chargement.') + '</div>');
$panel.html('<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">\u26a0\ufe0f ' + ((result && result.message) || t('errLoad')) + '</div>');
}
}, 'json').fail(function () {
$panel.html('<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">\u26a0\ufe0f Erreur reseau.</div>');
$panel.html('<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">\u26a0\ufe0f ' + t('errNetwork') + '</div>');
});
});
@ -230,10 +235,10 @@
$panel.html(result.html);
$panel.attr('data-loaded', '1');
} else {
$panel.html('<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">\u26a0\ufe0f ' + ((result && result.message) || 'Erreur de chargement.') + '</div>');
$panel.html('<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">\u26a0\ufe0f ' + ((result && result.message) || t('errLoad')) + '</div>');
}
}, 'json').fail(function () {
$panel.html('<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">\u26a0\ufe0f Erreur reseau.</div>');
$panel.html('<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">\u26a0\ufe0f ' + t('errNetwork') + '</div>');
});
});
@ -266,17 +271,17 @@
Ms1Loader.hide();
}
if (json && json.success) {
showMsg('\u2705 ' + (json.message || 'Remboursement effectue.'), true);
showMsg('\u2705 ' + (json.message || t('refundOk')), true);
onRefundSuccess(form, json);
} else {
showMsg('\u26a0\ufe0f ' + ((json && json.message) || 'Erreur remboursement.'), false);
showMsg('\u26a0\ufe0f ' + ((json && json.message) || t('errRefund')), false);
}
})
.catch(function () {
if (window.Ms1Loader) {
Ms1Loader.hide();
}
showMsg('\u26a0\ufe0f Erreur reseau pendant le remboursement.', false);
showMsg('\u26a0\ufe0f ' + t('errNetwork'), false);
});
});
})();
@ -733,23 +738,18 @@
}
function bibOcrErrorMessage(result) {
var blnEn = (document.documentElement.lang === 'en');
var code = result && result.message ? String(result.message) : '';
if (code === 'ocr_not_configured') {
return blnEn
? 'OCR not configured (Vision API key missing on server).'
: 'OCR non configuré (clé Vision absente sur le serveur).';
return t('bibOcrNotConfigured');
}
if (code === 'vision_api' || code === 'vision_http' || code === 'vision_curl') {
var detail = (result && result.detail) ? String(result.detail) : '';
return (blnEn ? 'Vision error: ' : 'Erreur Vision : ') + (detail || code);
return t('bibOcrVisionPrefix') + (detail || code);
}
if (code === 'image_invalid') {
return blnEn ? 'Invalid image capture.' : 'Image invalide.';
return t('bibOcrImageInvalid');
}
var base = blnEn
? 'Unreadable number. Frame a single number.'
: 'Numéro illisible. Cadrez un seul numéro.';
var base = t('bibOcrUnreadable');
if (result && result.seen) {
base += ' (' + result.seen + ')';
} else if (code && code !== 'no_number') {
@ -802,16 +802,16 @@
// Renvoie la valeur de l'attribut si elle est traduite, sinon le texte
// integre (bilingue) — evite d'afficher une clef brute a l'usager.
function scanMsg(panel, attr, frText, enText) {
function scanMsg(panel, attr) {
var value = panel ? panel.getAttribute(attr) : '';
if (value && !isRawI18nKey(value)) {
return value;
}
return (document.documentElement.lang === 'en') ? enText : frText;
return '';
}
BibScanSession.prototype.showReady = function () {
this.setStatus(scanMsg(this.panel, 'data-msg-ready', 'Cadrez le numéro, puis touchez l\'image', 'Frame the number, then tap the image'), false);
this.setStatus(scanMsg(this.panel, 'data-msg-ready'), false);
this.setCaptureReady(true);
if (this.captureLabel) {
this.captureLabel.hidden = false;
@ -838,11 +838,11 @@
return;
}
if (!self.video) {
self.setStatus(scanMsg(self.panel, 'data-msg-loading', 'Initialisation (1re fois)...', 'Initializing (first time)...'), true);
self.setStatus(scanMsg(self.panel, 'data-msg-loading'), true);
return;
}
if (!cfg.bibOcrCloud && !self.workerReady) {
self.setStatus(scanMsg(self.panel, 'data-msg-loading', 'Initialisation (1re fois)...', 'Initializing (first time)...'), true);
self.setStatus(scanMsg(self.panel, 'data-msg-loading'), true);
return;
}
self.runOcr();
@ -917,7 +917,7 @@
input.classList.add('inscr-gestion-bib--proposed');
var blnSearch = this.isSearchContext();
if (blnSearch) {
var tplSearch = scanMsg(this.panel, 'data-msg-found-search', 'Numéro %s détecté — recherche...', 'Number %s detected — searching...');
var tplSearch = scanMsg(this.panel, 'data-msg-found-search');
this.setStatus(tplSearch.replace('%s', strNum), true);
this.stop();
var form = this.block.closest('form.inscr-gestion-form');
@ -927,7 +927,7 @@
return;
}
input.focus();
var tpl = scanMsg(this.panel, 'data-msg-found', 'Numéro %s détecté — validez avec OK.', 'Number %s detected — press OK.');
var tpl = scanMsg(this.panel, 'data-msg-found');
this.setStatus(tpl.replace('%s', strNum), false);
this.stop();
};
@ -945,7 +945,7 @@
if (self.captureLabel) {
self.captureLabel.hidden = true;
}
self.setStatus(scanMsg(self.panel, 'data-msg-scanning', 'Lecture en cours...', 'Reading...'), true);
self.setStatus(scanMsg(self.panel, 'data-msg-scanning'), true);
// MSIN-4401 — Chemin produit : photo → Google Vision (serveur). Plus fiable que Tesseract mobile.
if (cfg.bibOcrCloud) {
@ -983,12 +983,7 @@
self.setStatus(bibOcrErrorMessage(result), false);
// Laisse l'erreur visible (pas de flash) — disparait au prochain tap / ready.
}).catch(function () {
self.setStatus(
(document.documentElement.lang === 'en')
? 'Network / capture error.'
: 'Erreur réseau ou capture.',
false
);
self.setStatus(t('bibOcrNetwork'), false);
}).then(function () {
self.ocrBusy = false;
self.setCaptureReady(true);
@ -1014,7 +1009,7 @@
return;
}
var seen = String(rawText || '').replace(/\D/g, '');
var base = scanMsg(self.panel, 'data-msg-invalid', 'Numéro illisible. Cadrez un seul numéro.', 'Unreadable number. Frame a single number.');
var base = scanMsg(self.panel, 'data-msg-invalid');
if (seen) {
base += ' (' + seen + ')';
}
@ -1025,7 +1020,7 @@
self.captureLabel.hidden = false;
}
}).catch(function () {
self.setStatus(scanMsg(self.panel, 'data-msg-invalid', 'Numéro illisible. Cadrez un seul numéro.', 'Unreadable number. Frame a single number.'), false);
self.setStatus(scanMsg(self.panel, 'data-msg-invalid'), false);
self.ocrBusy = false;
self.setCaptureReady(true);
if (self.captureLabel) {
@ -1049,7 +1044,7 @@
BibScanSession.prototype.start = function () {
var self = this;
if (!self.panel || !navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
self.setStatus(scanMsg(self.panel, 'data-msg-camera', 'Impossible d\'accéder à la caméra.', 'Unable to access the camera.'), false);
self.setStatus(scanMsg(self.panel, 'data-msg-camera'), false);
return;
}
if (activeScan && activeScan !== self) {
@ -1068,7 +1063,7 @@
self.scrollCaptureIntoView();
}
}, 120);
self.setStatus(scanMsg(self.panel, 'data-msg-loading', 'Initialisation (1re fois)...', 'Initializing (first time)...'), true);
self.setStatus(scanMsg(self.panel, 'data-msg-loading'), true);
var cameraPromise = navigator.mediaDevices.getUserMedia({
video: {
@ -1106,7 +1101,7 @@
// Apres affichage video : recentrer (layout final).
self.scrollCaptureIntoView();
}).catch(function () {
self.setStatus(scanMsg(self.panel, 'data-msg-camera', 'Impossible d\'accéder à la caméra.', 'Unable to access the camera.'), false);
self.setStatus(scanMsg(self.panel, 'data-msg-camera'), false);
self.stop();
});
};

View File

@ -13,13 +13,15 @@
}
var strAjaxUrl = cfg.ajaxUrl || '';
var strWaitMsg = cfg.preloaderWait || 'Chargement…';
var strWaitMsg = cfg.preloaderWait || '';
var i18n = cfg.i18n || {};
var strAjaxError = i18n.ajaxError || '';
var intHubLoaderPending = 0;
// MSIN-4401 — Session expiree : recharger la page → formulaire de login.
// Pas de message "Non connecte" qui laisse l'usager bloque.
// Detection par code serveur (pas egalite de message FR).
function fxHubRedirectIfSessionExpired(r) {
if (r && (r.code === 'session' || r.msg === 'Non connecté' || r.msg === 'Session expirée')) {
if (r && r.code === 'session') {
window.location.reload();
return true;
}
@ -68,7 +70,7 @@
return;
}
if (!r || !r.ok) {
$collapse.find('.promoteur-hub-stats-placeholder').text(r && r.msg ? r.msg : 'Erreur');
$collapse.find('.promoteur-hub-stats-placeholder').text(r && r.msg ? r.msg : strAjaxError);
return;
}
$collapse.find('.promoteur-hub-stats-placeholder').replaceWith(r.html || '');
@ -78,7 +80,7 @@
$collapse.data('hub-loaded', 1);
})
.fail(function () {
$collapse.find('.promoteur-hub-stats-placeholder').text('Erreur chargement');
$collapse.find('.promoteur-hub-stats-placeholder').text(strAjaxError);
})
.always(function () {
fxHubHideLoader();
@ -99,14 +101,14 @@
return;
}
if (!r || !r.ok) {
$collapse.find('.promoteur-hub-links-placeholder').text(r && r.msg ? r.msg : 'Erreur');
$collapse.find('.promoteur-hub-links-placeholder').text(r && r.msg ? r.msg : strAjaxError);
return;
}
$collapse.find('.promoteur-hub-links-placeholder').replaceWith(r.html || '');
$collapse.data('hub-links-loaded', 1);
})
.fail(function () {
$collapse.find('.promoteur-hub-links-placeholder').text('Erreur chargement');
$collapse.find('.promoteur-hub-links-placeholder').text(strAjaxError);
})
.always(function () {
fxHubHideLoader();

View File

@ -37,7 +37,7 @@ if (!isset($_SESSION['com_id'])) {
$_SESSION['redirect_after_login'] = $_SERVER['REQUEST_URI'];
$_SESSION['affiche_pas_connexion'] = 'true';
$strMetaTitle = ($strLangue === 'fr') ? 'Inscriptions mobile' : 'Mobile registrations';
$strMetaTitle = fxInscrGestionT('inscr_gestion_mobile_title');
$strMetaDescription = '';
$strMetaKeywords = '';
$blnBoutonRetour = false;
@ -108,40 +108,42 @@ if ($intInviteCom > 0 && $intEveForInvite > 0
. '&return=' . rawurlencode($strReturnPath);
$strStayUrl = $vDomaine . ($strLangue === 'en' ? '/account' : '/compte');
$strMetaTitle = ($strLangue === 'fr') ? 'Inscriptions mobile' : 'Mobile registrations';
$strMetaTitle = fxInscrGestionT('inscr_gestion_mobile_title');
$strMetaDescription = '';
$strMetaKeywords = '';
$blnBoutonRetour = false;
$footer_script = false;
$strMailPart = ($strTargetMail !== '')
? ' (' . htmlspecialchars($strTargetMail, ENT_QUOTES, 'UTF-8') . ')'
: '';
$strSignedAs = str_replace(
'%name%',
'<strong>' . htmlspecialchars($strSessionLabel, ENT_QUOTES, 'UTF-8') . '</strong>',
fxInscrGestionT('mobile_invite_conflict_signed_as')
);
$strInviteFor = str_replace(
array('%name%', '%mail_part%'),
array(
'<strong>' . htmlspecialchars($strTargetLabel, ENT_QUOTES, 'UTF-8') . '</strong>',
$strMailPart,
),
fxInscrGestionT('mobile_invite_conflict_for')
);
require_once('inc_header.php');
?>
<div id="page">
<div id="main">
<div class="container bg-white p-3 p-xl-4">
<div class="alert alert-warning" role="alert">
<?php if ($strLangue === 'en') { ?>
<p class="mb-2">You are currently signed in as
<strong><?php echo htmlspecialchars($strSessionLabel, ENT_QUOTES, 'UTF-8'); ?></strong>.</p>
<p class="mb-3">This invitation is for
<strong><?php echo htmlspecialchars($strTargetLabel, ENT_QUOTES, 'UTF-8'); ?></strong><?php
echo $strTargetMail !== '' ? ' (' . htmlspecialchars($strTargetMail, ENT_QUOTES, 'UTF-8') . ')' : '';
?>.
Sign out first, then sign in with that account.</p>
<?php } else { ?>
<p class="mb-2">Vous êtes actuellement connecté en tant que
<strong><?php echo htmlspecialchars($strSessionLabel, ENT_QUOTES, 'UTF-8'); ?></strong>.</p>
<p class="mb-3">Cette invitation concerne le compte
<strong><?php echo htmlspecialchars($strTargetLabel, ENT_QUOTES, 'UTF-8'); ?></strong><?php
echo $strTargetMail !== '' ? ' (' . htmlspecialchars($strTargetMail, ENT_QUOTES, 'UTF-8') . ')' : '';
?>.
Déconnectez-vous d&rsquo;abord, puis reconnectez-vous avec ce compte.</p>
<?php } ?>
<p class="mb-2"><?php echo $strSignedAs; ?></p>
<p class="mb-3"><?php echo $strInviteFor; ?></p>
<a class="btn btn-primary rounded-0 mr-2" href="<?php echo htmlspecialchars($strLogoutUrl, ENT_QUOTES, 'UTF-8'); ?>">
<?php echo ($strLangue === 'en') ? 'Sign out and continue' : 'Se déconnecter et continuer'; ?>
<?php echo htmlspecialchars(fxInscrGestionT('mobile_invite_conflict_btn_logout'), ENT_QUOTES, 'UTF-8'); ?>
</a>
<a class="btn btn-outline-secondary rounded-0" href="<?php echo htmlspecialchars($strStayUrl, ENT_QUOTES, 'UTF-8'); ?>">
<?php echo ($strLangue === 'en') ? 'Stay on my current account' : 'Rester sur mon compte actuel'; ?>
<?php echo htmlspecialchars(fxInscrGestionT('mobile_invite_conflict_btn_stay'), ENT_QUOTES, 'UTF-8'); ?>
</a>
</div>
</div>
@ -172,7 +174,7 @@ if ($intEveId === 0 && count($tabEveIds) === 1) {
exit;
}
$strMetaTitle = ($strLangue === 'fr') ? 'Inscriptions mobile' : 'Mobile registrations';
$strMetaTitle = fxInscrGestionT('inscr_gestion_mobile_title');
$strMetaDescription = '';
$strMetaKeywords = '';
$blnBoutonRetour = false;
@ -198,9 +200,7 @@ require_once('inc_header.php');
if ($intEveId === 0) {
if (count($tabEveIds) === 0) {
echo '<div class="alert alert-warning" role="alert">';
echo ($strLangue === 'fr')
? 'Aucun acces inscriptions mobile (v2) n\'est actif pour ce compte.'
: 'No active mobile registration access (v2) for this account.';
echo fxInscrGestionEsc(fxInscrGestionT('mobile_no_acces_v2'));
echo '</div>';
} else {
fxInscrGestionRenderEventPicker($tabEveIds, $strLangue, fxInscrGestionBaseUrl($strLangue, true));

View File

@ -48,9 +48,17 @@ function fxEveAccesPromoteurT($strClef, $strLangue = 'fr')
'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.',
@ -100,6 +108,29 @@ function fxEveAccesPromoteurT($strClef, $strLangue = 'fr')
'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])) {
@ -294,53 +325,26 @@ function fxEveAccesPromoteurSendInviteMail($arrCompte, $arrEve, $arrRole, $strLa
}
$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 !== '') {
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.<br><br>'
. 'Ensuite, connectez-vous ici pour travailler sur le terrain :<br>'
. '<a href="' . htmlspecialchars($strMobileUrl, ENT_QUOTES, 'UTF-8') . '">'
. htmlspecialchars($strMobileUrl, ENT_QUOTES, 'UTF-8') . '</a>';
$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.<br><br>'
. 'Then sign in here for field use:<br>'
. '<a href="' . htmlspecialchars($strMobileUrl, ENT_QUOTES, 'UTF-8') . '">'
. htmlspecialchars($strMobileUrl, ENT_QUOTES, 'UTF-8') . '</a>';
$attachment_label = 'Set my password';
}
$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 {
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>'
. 'Ouvrez le lien ci-dessous et connectez-vous avec le compte '
. '<strong>' . htmlspecialchars($strLogin, ENT_QUOTES, 'UTF-8') . '</strong> '
. '(si un autre compte est déjà ouvert, vous serez invité à vous déconnecter).';
$attachment_label = 'Ouvrir l\'accès (mobile)';
} 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>'
. 'Open the link below and sign in as '
. '<strong>' . htmlspecialchars($strLogin, ENT_QUOTES, 'UTF-8') . '</strong> '
. '(if another account is already open, you will be asked to sign out).';
$attachment_label = 'Open access (mobile)';
}
$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;
}
@ -739,11 +743,7 @@ function fxEveAccesPromoteurRenderPage($intEveId, $strLangue = 'fr', $strFlash =
echo '</div>';
echo '</div>';
echo '<p class="small text-muted mb-0">'
. fxEveAccesPromoteurEsc(
$strLangue === 'en'
? 'Enter the email first: we check if an MS1 account already exists before asking for a name.'
: 'Saisissez d\'abord le courriel : on vérifie s\'il existe déjà un compte MS1 avant de demander le nom.'
)
. fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_invite_courriel_help', $strLangue))
. '</p>';
echo '</form>';
fxEveAccesPromoteurRenderInviteScript();
@ -825,7 +825,7 @@ function fxEveAccesPromoteurRenderPage($intEveId, $strLangue = 'fr', $strFlash =
echo '</button></form>';
echo '<form method="post" action="' . fxEveAccesPromoteurEsc($strFormAction) . '" class="d-inline"';
echo ' onsubmit="return confirm(\'' . fxEveAccesPromoteurEsc(
$strLangue === 'en' ? 'Revoke this access?' : 'Retirer cet accès ?'
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']) . '">';

View File

@ -71,6 +71,11 @@ function fxInscrGestionCanDo($intComId, $intEveId, $strActionPerm) {
return false;
}
// MSIN-4401 — kit propriétaire / total (grants_all) = toutes les actions, sans cocher chaque droit.
if (fxEveAccesComEventHasGrantsAllKit(intval($intComId), intval($intEveId))) {
return true;
}
if (fxEveAccesHasPermission(intval($intComId), intval($intEveId), $strActionPerm)) {
return true;
}
@ -400,6 +405,53 @@ function fxInscrGestionFallbackTexte($strClef, $strLangue) {
'inscr_gestion_rech_statut' => $blnFr ? 'Statut de course' : 'Race status',
'inscr_gestion_rech_statut_all' => $blnFr ? 'Tous les statuts' : 'All statuses',
'inscr_gestion_rech_statut_n' => $blnFr ? '%d sélectionnés' : '%d selected',
'inscr_gestion_mobile_title' => $blnFr ? 'Inscriptions mobile' : 'Mobile registrations',
'inscr_gestion_login_user' => $blnFr ? 'Nom d\'utilisateur' : 'Username',
'inscr_gestion_login_pass' => $blnFr ? 'Mot de passe' : 'Password',
'inscr_gestion_login_btn' => $blnFr ? 'Connexion' : 'Sign in',
'inscr_gestion_login_intro' => $blnFr
? 'Connectez-vous pour accéder aux inscriptions mobile.'
: 'Sign in to access mobile registrations.',
'inscr_gestion_login_err_user' => $blnFr
? 'Veuillez entrer votre nom d\'usager'
: 'Please enter your username',
'inscr_gestion_login_err_pass' => $blnFr
? 'Veuillez entrer votre mot de passe'
: 'Please enter your password',
'inscr_gestion_choose_event' => $blnFr ? 'Choisir un événement' : 'Choose an event',
'inscr_gestion_back_events' => $blnFr ? 'Événements' : 'Events',
'tableau_promoteur_menueinscription_v2' => $blnFr ? 'Inscriptions V2' : 'Registrations V2',
'mobile_no_acces_v2' => $blnFr
? 'Aucun accès inscriptions mobile (v2) n\'est actif pour ce compte.'
: 'No active mobile registration access (v2) for this account.',
'mobile_invite_conflict_signed_as' => $blnFr
? 'Vous êtes actuellement connecté en tant que %name%.'
: 'You are currently signed in as %name%.',
'mobile_invite_conflict_for' => $blnFr
? 'Cette invitation concerne le compte %name%%mail_part%. Déconnectez-vous d\'abord, puis reconnectez-vous avec ce compte.'
: 'This invitation is for %name%%mail_part%. Sign out first, then sign in with that account.',
'mobile_invite_conflict_btn_logout' => $blnFr
? 'Se déconnecter et continuer'
: 'Sign out and continue',
'mobile_invite_conflict_btn_stay' => $blnFr
? 'Rester sur mon compte actuel'
: 'Stay on my current account',
'inscr_gestion_bib_scan_found_search' => $blnFr
? 'Numéro %s détecté — recherche...'
: 'Number %s detected — searching...',
'inscr_gestion_bib_ocr_not_configured' => $blnFr
? 'OCR non configuré (clé Vision absente sur le serveur).'
: 'OCR not configured (Vision API key missing on server).',
'inscr_gestion_bib_ocr_vision_prefix' => $blnFr ? 'Erreur Vision : ' : 'Vision error: ',
'inscr_gestion_bib_ocr_image_invalid' => $blnFr ? 'Image invalide.' : 'Invalid image capture.',
'inscr_gestion_bib_ocr_network' => $blnFr ? 'Erreur réseau ou capture.' : 'Network or capture error.',
'inscr_gestion_err_load' => $blnFr ? 'Erreur de chargement.' : 'Loading error.',
'inscr_gestion_err_network' => $blnFr ? 'Erreur réseau.' : 'Network error.',
'inscr_gestion_err_refund' => $blnFr ? 'Erreur remboursement.' : 'Refund error.',
'inscr_gestion_refund_ok' => $blnFr ? 'Remboursement effectué.' : 'Refund completed.',
'inscr_gestion_refund_cancel_hint' => $blnFr
? 'Votre transaction a été remboursée. Si vous voulez aussi l\'annuler dans le système d\'inscription :'
: 'Your transaction has been refunded. If you also want to cancel it in the registration system:',
);
}
@ -476,37 +528,33 @@ function fxInscrGestionLoginActionUrl($strLangue) {
function fxInscrGestionRenderLoginPage($strLangue) {
$strActionUrl = fxInscrGestionLoginActionUrl($strLangue);
$strLblUser = ($strLangue === 'fr') ? 'Nom d\'utilisateur' : 'Username';
$strLblPass = ($strLangue === 'fr') ? 'Mot de passe' : 'Password';
$strBtn = ($strLangue === 'fr') ? 'Connexion' : 'Sign in';
$strIntro = ($strLangue === 'fr')
? 'Connectez-vous pour accéder aux inscriptions mobile.'
: 'Sign in to access mobile registrations.';
echo '<div class="inscr-gestion-page inscr-gestion-login-wrap">';
echo '<div id="module-inscr-gestion" class="inscr-gestion-shell inscr-gestion-shell--login">';
fxInscrGestionRenderPageHead(
($strLangue === 'fr') ? 'Inscriptions mobile' : 'Mobile registrations'
);
echo '<p class="inscr-gestion-login-intro text-muted">' . fxInscrGestionEsc($strIntro) . '</p>';
fxInscrGestionRenderPageHead(fxInscrGestionT('inscr_gestion_mobile_title'));
echo '<p class="inscr-gestion-login-intro text-muted">'
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_login_intro')) . '</p>';
echo '<form action="' . fxInscrGestionEsc($strActionUrl) . '" id="frm_inscr_gestion_login" method="post" class="inscr-gestion-login-form">';
echo '<input type="hidden" name="form_action" value="' . fxInscrGestionEsc(urlencode(base64_encode('login'))) . '">';
echo '<label class="inscr-gestion-label" for="txt_login_mobile">' . fxInscrGestionEsc($strLblUser) . '</label>';
echo '<label class="inscr-gestion-label" for="txt_login_mobile">'
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_login_user')) . '</label>';
echo '<input class="form-control form-control-sm rounded-0" type="text" id="txt_login_mobile" name="txt_login" required autofocus>';
echo '<label class="inscr-gestion-label" for="txt_password_mobile">' . fxInscrGestionEsc($strLblPass) . '</label>';
echo '<label class="inscr-gestion-label" for="txt_password_mobile">'
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_login_pass')) . '</label>';
echo '<input class="form-control form-control-sm rounded-0" type="password" id="txt_password_mobile" name="txt_password" required>';
echo '<button class="btn btn-primary btn-sm rounded-0 btn-block mt-3" type="submit" id="btn_login_mobile" name="btn_login">' . fxInscrGestionEsc($strBtn) . '</button>';
echo '<button class="btn btn-primary btn-sm rounded-0 btn-block mt-3" type="submit" id="btn_login_mobile" name="btn_login">'
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_login_btn')) . '</button>';
echo '</form></div></div>';
}
/** Libellé menu promoteur V2 (même texte que l'ancien + suffixe V2). */
/** Libellé menu promoteur V2 (Info). */
function fxInscrGestionPromoteurMenuLabel() {
return afficheTexte('tableau_promoteur_menueinscription', 0) . ' V2';
return fxInscrGestionT('tableau_promoteur_menueinscription_v2');
}
function fxInscrGestionListBackLabel($strLangue) {
if (fxInscrGestionIsEntry()) {
return ($strLangue === 'fr') ? 'Événements' : 'Events';
return fxInscrGestionT('inscr_gestion_back_events');
}
return fxInscrGestionT('promoteur_back');
@ -1437,7 +1485,7 @@ function fxInscrGestionResolveQrScan($strRaw, $intEveId, $strLangue) {
function fxInscrGestionRenderEventPicker($tabEveIds, $strLangue, $strBaseUrl) {
global $objDatabase;
$strTitle = ($strLangue === 'fr') ? 'Choisir un événement' : 'Choose an event';
$strTitle = fxInscrGestionT('inscr_gestion_choose_event');
echo '<div id="module-inscr-gestion" class="inscr-gestion-shell">';
fxInscrGestionRenderPageHead($strTitle);
@ -1629,23 +1677,24 @@ function fxInscrGestionVisionReadBibNumber($strImageBase64) {
function fxInscrGestionRenderBibScanBlock($strBibInputId, $strLangue) {
echo '<div class="inscr-gestion-bib-scan-panel" hidden';
echo ' data-bib-input-id="' . fxInscrGestionEsc($strBibInputId) . '"';
echo ' data-msg-scanning="' . fxInscrGestionEsc(fxInscrGestionFallbackTexte('inscr_gestion_bib_scan_scanning', $strLangue)) . '"';
echo ' data-msg-loading="' . fxInscrGestionEsc(fxInscrGestionFallbackTexte('inscr_gestion_bib_scan_loading', $strLangue)) . '"';
echo ' data-msg-ready="' . fxInscrGestionEsc(fxInscrGestionFallbackTexte('inscr_gestion_bib_scan_ready', $strLangue)) . '"';
echo ' data-msg-found="' . fxInscrGestionEsc(fxInscrGestionFallbackTexte('inscr_gestion_bib_scan_found', $strLangue)) . '"';
echo ' data-msg-invalid="' . fxInscrGestionEsc(fxInscrGestionFallbackTexte('inscr_gestion_bib_scan_invalid', $strLangue)) . '"';
echo ' data-msg-camera="' . fxInscrGestionEsc(fxInscrGestionFallbackTexte('inscr_gestion_qr_camera_error', $strLangue)) . '">';
echo ' data-msg-scanning="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_bib_scan_scanning')) . '"';
echo ' data-msg-loading="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_bib_scan_loading')) . '"';
echo ' data-msg-ready="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_bib_scan_ready')) . '"';
echo ' data-msg-found="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_bib_scan_found')) . '"';
echo ' data-msg-found-search="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_bib_scan_found_search')) . '"';
echo ' data-msg-invalid="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_bib_scan_invalid')) . '"';
echo ' data-msg-camera="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_qr_camera_error')) . '">';
echo '<p class="inscr-gestion-bib-scan-hint text-muted small mb-1">'
. fxInscrGestionEsc(fxInscrGestionFallbackTexte('inscr_gestion_bib_scan_hint', $strLangue)) . '</p>';
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_bib_scan_hint')) . '</p>';
echo '<div class="inscr-gestion-bib-scan-status small mb-1" aria-live="polite"></div>';
echo '<div class="inscr-gestion-bib-scan-capture" role="button" tabindex="0">';
echo '<div class="inscr-gestion-bib-scan-video"></div>';
// Zone-cible centrale (eclaircie) : aligne le repere visuel sur la region
// reellement lue par l'OCR (captureCenterFrame : ~55% largeur x 30% hauteur).
echo '<div class="inscr-gestion-bib-scan-target" aria-hidden="true"></div>';
// Pastille courte (fallback integre) — evite un long texte info qui masque le viseur.
// Pastille courte texte Info (inscr_gestion_bib_scan_tap).
echo '<span class="inscr-gestion-bib-scan-capture-label">'
. fxInscrGestionEsc(fxInscrGestionFallbackTexte('inscr_gestion_bib_scan_tap', $strLangue)) . '</span>';
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_bib_scan_tap')) . '</span>';
echo '</div>';
echo '</div>';
}

View File

@ -463,9 +463,14 @@ function fxPromoteurHubStaticFallback($clef, $strLangParam = '')
$strLangParam = !empty($strLangue) ? $strLangue : 'fr';
}
$lng = ($strLangParam === 'en') ? 'en' : 'fr';
// MSIN-4401 — filet si Info absente (jamais la source de vérité).
$tab = array(
'promoteur_hub_section_gestion' => array('fr' => 'Gestion', 'en' => 'Management'),
'promoteur_hub_link_dashboard' => array('fr' => 'Tableau de bord V2', 'en' => 'V2 dashboard'),
'promoteur_hub_no_features' => array(
'fr' => 'Aucune fonctionnalité disponible avec vos droits actuels.',
'en' => 'No features available with your current permissions.',
),
'promoteur_hub_menu' => array('fr' => 'Tableau de bord V2', 'en' => 'V2 dashboard'),
'tableau_promoteur_menurapports' => array('fr' => 'Rapports', 'en' => 'Reports'),
'tableau_promoteur_menudossard' => array('fr' => 'Dossards', 'en' => 'Bibs'),
@ -474,6 +479,48 @@ function fxPromoteurHubStaticFallback($clef, $strLangParam = '')
'tableau_promoteur_menueinscription' => array('fr' => 'Inscriptions', 'en' => 'Registrations'),
'tableau_promoteur_rabais' => array('fr' => 'Rabais', 'en' => 'Discounts'),
'tableau_promoteur_envoie_email_questions' => array('fr' => 'Envoi courriel questions', 'en' => 'Send question emails'),
'promoteur_hub_load_menu' => array('fr' => 'Chargement du menu…', 'en' => 'Loading menu…'),
'promoteur_hub_load_dashboard' => array('fr' => 'Chargement du tableau de bord…', 'en' => 'Loading dashboard…'),
'promoteur_hub_epreuves_title' => array('fr' => 'Épreuves en vente', 'en' => 'Events for sale'),
'promoteur_hub_produits_title' => array('fr' => 'Produits en vente', 'en' => 'Products for sale'),
'promoteur_hub_bibs_sold' => array('fr' => 'dossards vendus', 'en' => 'bibs sold'),
'promoteur_hub_restant' => array('fr' => 'restant', 'en' => 'left'),
'promoteur_hub_unlimited' => array('fr' => 'illimité', 'en' => 'unlimited'),
'promoteur_hub_prix_courant' => array('fr' => 'Prix courant', 'en' => 'Current price'),
'promoteur_hub_prochain_prix' => array('fr' => 'Prochain prix', 'en' => 'Next price'),
'promoteur_hub_des' => array('fr' => 'dès', 'en' => 'on'),
'promoteur_hub_dossards_remis' => array('fr' => 'Dossards remis', 'en' => 'Bibs handed out'),
'promoteur_hub_liste_active' => array('fr' => 'liste active', 'en' => 'list active'),
'promoteur_hub_waiting_n' => array('fr' => 'en attente', 'en' => 'waiting'),
'promoteur_hub_waiting_list' => array('fr' => 'Liste d\'attente', 'en' => 'Waiting list'),
'promoteur_hub_disponible' => array('fr' => 'disponible(s)', 'en' => 'available'),
'promoteur_hub_vendus_title' => array('fr' => 'Vendus', 'en' => 'Sold'),
'promoteur_hub_vendus_lbl' => array('fr' => 'vendu(s)', 'en' => 'sold'),
'promoteur_hub_paniers_title' => array('fr' => 'Dans des paniers incomplets', 'en' => 'In incomplete carts'),
'promoteur_hub_paniers_lbl' => array('fr' => 'panier(s)', 'en' => 'cart(s)'),
'promoteur_hub_menu_features' => array('fr' => 'Menu des fonctionnalités', 'en' => 'Features menu'),
'promoteur_hub_flash_inscrits' => array('fr' => 'inscrits', 'en' => 'registered'),
'promoteur_hub_flash_rempli' => array('fr' => 'rempli', 'en' => 'filled'),
'promoteur_hub_flash_places' => array('fr' => 'places restantes', 'en' => 'spots left'),
'promoteur_hub_flash_revenus' => array('fr' => 'revenus est.', 'en' => 'est. revenue'),
'promoteur_hub_badge_completes' => array('fr' => 'complète(s)', 'en' => 'sold out'),
'promoteur_hub_badge_presque' => array('fr' => 'presque pleine(s)', 'en' => 'almost full'),
'promoteur_hub_badge_attente' => array('fr' => 'en attente', 'en' => 'waiting'),
'promoteur_hub_badge_limite' => array('fr' => 'limite', 'en' => 'deadline'),
'promoteur_hub_ajax_session' => array('fr' => 'Session expirée', 'en' => 'Session expired'),
'promoteur_hub_ajax_invalid' => array('fr' => 'Requête invalide', 'en' => 'Invalid request'),
'promoteur_hub_ajax_denied' => array('fr' => 'Accès refusé', 'en' => 'Access denied'),
'promoteur_hub_ajax_not_found' => array('fr' => 'Événement introuvable', 'en' => 'Event not found'),
'promoteur_hub_ajax_error' => array('fr' => 'Erreur', 'en' => 'Error'),
'promoteur_hub_rapport_chrono' => array('fr' => 'chrono + questions', 'en' => 'chrono + questions'),
'promoteur_hub_rapport_finances' => array('fr' => 'Transactions-Finances', 'en' => 'Transactions-Finances'),
'promoteur_hub_rapport_finances_all' => array('fr' => 'Transactions-Finances-Tout', 'en' => 'Transactions-Finances-All'),
'promoteur_hub_rapport_finances_adh' => array('fr' => 'Transactions-Finances-Adhésions Seul', 'en' => 'Transactions-Finances-Memberships only'),
'promoteur_hub_rapport_finances_eve' => array('fr' => 'Transactions-Finances-Événements Seul', 'en' => 'Transactions-Finances-Events only'),
'promoteur_hub_rapport_adhesions' => array('fr' => 'adhésions actives', 'en' => 'active memberships'),
'promoteur_hub_rapport_adhesions_annuelles' => array('fr' => 'liste d\'adhésions annuelles', 'en' => 'annual memberships list'),
'promoteur_hub_rapport_finances_capitaines' => array('fr' => 'Transactions-Finances (capitaines seulement)', 'en' => 'Transactions-Finances (captains only)'),
'promoteur_hub_rapport_paiements' => array('fr' => 'Paiements', 'en' => 'Payments'),
);
return $tab[$clef][$lng] ?? '';
@ -555,38 +602,74 @@ function fxPromoteurHubGetEventLinks($arrItem, $strLangue)
// ---- Rapports (niveau 1 V2 : chrono / finances / paiements) ----
if ($intTeId != 9 && $fnCan(array('reports.chrono'))) {
$arrRapports[] = array('label' => 'chrono + questions', 'url' => $strRapport . 'chrono&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank');
$arrRapports[] = array(
'label' => fxPromoteurHubTexte('promoteur_hub_rapport_chrono', 0),
'url' => $strRapport . 'chrono&e=' . $strE . '&lng=' . $strLangue,
'target' => '_blank',
);
}
if ($fnCan(array('reports.finances', 'reports.finances_membership', 'reports.finances_events'))) {
if ($intTeId == 13) {
$arrRapports[] = array('label' => 'Transactions-Finances-Tout', 'url' => $strRapport . 'finances-membership&e=' . $strE . '&lng=' . $strLangue . '&a-ach_maj_from=' . urlencode($strDateTq) . '&btn_search=', 'target' => '_blank');
$arrRapports[] = array(
'label' => fxPromoteurHubTexte('promoteur_hub_rapport_finances_all', 0),
'url' => $strRapport . 'finances-membership&e=' . $strE . '&lng=' . $strLangue . '&a-ach_maj_from=' . urlencode($strDateTq) . '&btn_search=',
'target' => '_blank',
);
} else {
$arrRapports[] = array('label' => 'Transactions-Finances', 'url' => $strRapport . 'finances&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank');
$arrRapports[] = array(
'label' => fxPromoteurHubTexte('promoteur_hub_rapport_finances', 0),
'url' => $strRapport . 'finances&e=' . $strE . '&lng=' . $strLangue,
'target' => '_blank',
);
}
}
if ($intTeId == 13 && $fnCan(array('reports.finances_membership', 'reports.finances_events', 'reports.adhesions', 'reports.adhesions_sales'))) {
if ($fnCan(array('reports.finances_membership'))) {
$arrRapports[] = array('label' => 'Transactions-Finances-Adhésions Seul', 'url' => $strRapport . 'finances-membership-tq&e=' . $strE . '&lng=' . $strLangue . '&a-ach_maj_from=' . urlencode($strDateTq) . '&btn_search=', 'target' => '_blank');
$arrRapports[] = array(
'label' => fxPromoteurHubTexte('promoteur_hub_rapport_finances_adh', 0),
'url' => $strRapport . 'finances-membership-tq&e=' . $strE . '&lng=' . $strLangue . '&a-ach_maj_from=' . urlencode($strDateTq) . '&btn_search=',
'target' => '_blank',
);
}
if ($fnCan(array('reports.finances_events'))) {
$arrRapports[] = array('label' => 'Transactions-Finances-Événements Seul', 'url' => $strRapport . 'finances-membership-autre&e=' . $strE . '&lng=' . $strLangue . '&a-ach_maj_from=' . urlencode($strDateTq) . '&btn_search=', 'target' => '_blank');
$arrRapports[] = array(
'label' => fxPromoteurHubTexte('promoteur_hub_rapport_finances_eve', 0),
'url' => $strRapport . 'finances-membership-autre&e=' . $strE . '&lng=' . $strLangue . '&a-ach_maj_from=' . urlencode($strDateTq) . '&btn_search=',
'target' => '_blank',
);
}
if ($fnCan(array('reports.adhesions'))) {
$arrRapports[] = array('label' => 'adhésions actives', 'url' => $strRapport . 'adhesions&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank');
$arrRapports[] = array(
'label' => fxPromoteurHubTexte('promoteur_hub_rapport_adhesions', 0),
'url' => $strRapport . 'adhesions&e=' . $strE . '&lng=' . $strLangue,
'target' => '_blank',
);
}
if ($fnCan(array('reports.adhesions_sales'))) {
$arrRapports[] = array('label' => "liste d'adhésions annuelles", 'url' => $strRapport . 'adhesions-ventes&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank');
$arrRapports[] = array(
'label' => fxPromoteurHubTexte('promoteur_hub_rapport_adhesions_annuelles', 0),
'url' => $strRapport . 'adhesions-ventes&e=' . $strE . '&lng=' . $strLangue,
'target' => '_blank',
);
}
}
if (intval($arrItem['rapport_capitaines'] ?? 0) == 1 && $fnCan(array('reports.finances_captains'))) {
$arrRapports[] = array('label' => 'Transactions-Finances (capitaines seulement)', 'url' => $strRapport . 'finances-pnq&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank');
$arrRapports[] = array(
'label' => fxPromoteurHubTexte('promoteur_hub_rapport_finances_capitaines', 0),
'url' => $strRapport . 'finances-pnq&e=' . $strE . '&lng=' . $strLangue,
'target' => '_blank',
);
}
if ($fnCan(array('reports.payments'))) {
$arrRapports[] = array('label' => ($strLangue === 'fr' ? 'Paiements' : 'Payments'), 'url' => $strRapport . 'remboursements&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank');
$arrRapports[] = array(
'label' => fxPromoteurHubTexte('promoteur_hub_rapport_paiements', 0),
'url' => $strRapport . 'remboursements&e=' . $strE . '&lng=' . $strLangue,
'target' => '_blank',
);
}
if (($blnLegacyMenus || $blnGrantsAll || $blnSuper || $fnCan(array('reports.custom'))) && function_exists('fxGetMenussuperadm')) {
@ -694,11 +777,8 @@ function fxPromoteurHubRenderEventPanelContent($arrItem, $strLangue)
// MSIN-4401 — pas d'en-têtes vides si aucun droit dans la section
if (!$blnGestion && !$blnRapports) {
$blnFr = ($strLangue !== 'en');
return '<p class="text-muted small mb-0">'
. fxPromoteurHubEsc($blnFr
? 'Aucune fonctionnalité disponible avec vos droits actuels.'
: 'No features available with your current permissions.')
. fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_no_features', 0))
. '</p>';
}
@ -732,8 +812,7 @@ function fxPromoteurHubRenderEventPanelShell($arrItem, $strLangue)
{
$strPanelId = 'hub-fn-' . intval($arrItem['eve_id']);
$intEveId = intval($arrItem['eve_id']);
$blnFr = ($strLangue !== 'en');
$strLoadMsg = $blnFr ? 'Chargement du menu…' : 'Loading menu…';
$strLoadMsg = fxPromoteurHubTexte('promoteur_hub_load_menu', 0);
global $vDomaine;
$strRunnerSrc = $vDomaine . '/images/ms1-runner-loader.gif?v=' . _VERSION_CODE;
@ -1074,13 +1153,8 @@ function fxPromoteurHubRenderFlash($arrStats, $strLangue = 'fr')
return '';
}
$blnFr = ($strLangue !== 'en');
$arrFlash = $arrStats['flash'];
$lbl = function ($fr, $en) use ($blnFr) {
return $blnFr ? $fr : $en;
};
$html = '';
// ---- Bande flash ----
@ -1088,25 +1162,25 @@ function fxPromoteurHubRenderFlash($arrStats, $strLangue = 'fr')
$html .= '<div class="phub-flash-item phub-flash-main">';
$html .= '<span class="phub-flash-num">' . intval($arrFlash['total_vendus']) . '</span>';
$html .= '<span class="phub-flash-lbl">' . fxPromoteurHubEsc($lbl('inscrits', 'registered')) . '</span>';
$html .= '<span class="phub-flash-lbl">' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_flash_inscrits', 0)) . '</span>';
$html .= '</div>';
if (intval($arrFlash['total_max']) > 0) {
$html .= '<div class="phub-flash-item">';
$html .= '<span class="phub-flash-num">' . intval($arrFlash['pct_global']) . '%</span>';
$html .= '<span class="phub-flash-lbl">' . fxPromoteurHubEsc($lbl('rempli', 'filled')) . '</span>';
$html .= '<span class="phub-flash-lbl">' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_flash_rempli', 0)) . '</span>';
$html .= '</div>';
$html .= '<div class="phub-flash-item">';
$html .= '<span class="phub-flash-num">' . intval($arrFlash['total_restant']) . '</span>';
$html .= '<span class="phub-flash-lbl">' . fxPromoteurHubEsc($lbl('places restantes', 'spots left')) . '</span>';
$html .= '<span class="phub-flash-lbl">' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_flash_places', 0)) . '</span>';
$html .= '</div>';
}
if (floatval($arrFlash['revenus']) > 0) {
$html .= '<div class="phub-flash-item">';
$html .= '<span class="phub-flash-num">' . fxPromoteurHubEsc(fxShowPrix($arrFlash['revenus'], $strLangue)) . '</span>';
$html .= '<span class="phub-flash-lbl">' . fxPromoteurHubEsc($lbl('revenus est.', 'est. revenue')) . '</span>';
$html .= '<span class="phub-flash-lbl">' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_flash_revenus', 0)) . '</span>';
$html .= '</div>';
}
@ -1116,19 +1190,19 @@ function fxPromoteurHubRenderFlash($arrStats, $strLangue = 'fr')
$arrBadges = array();
if (intval($arrFlash['nb_completes']) > 0) {
$arrBadges[] = array('cls' => 'phub-badge-danger', 'icon' => 'fa-ban',
'txt' => intval($arrFlash['nb_completes']) . ' ' . $lbl('complète(s)', 'sold out'));
'txt' => intval($arrFlash['nb_completes']) . ' ' . fxPromoteurHubTexte('promoteur_hub_badge_completes', 0));
}
if (intval($arrFlash['nb_presque']) > 0) {
$arrBadges[] = array('cls' => 'phub-badge-warn', 'icon' => 'fa-exclamation-triangle',
'txt' => intval($arrFlash['nb_presque']) . ' ' . $lbl('presque pleine(s)', 'almost full'));
'txt' => intval($arrFlash['nb_presque']) . ' ' . fxPromoteurHubTexte('promoteur_hub_badge_presque', 0));
}
if (intval($arrFlash['total_attente']) > 0) {
$arrBadges[] = array('cls' => 'phub-badge-info', 'icon' => 'fa-hourglass-half',
'txt' => intval($arrFlash['total_attente']) . ' ' . $lbl('en attente', 'waiting'));
'txt' => intval($arrFlash['total_attente']) . ' ' . fxPromoteurHubTexte('promoteur_hub_badge_attente', 0));
}
if ($arrFlash['prochaine_date_limite'] !== '') {
$arrBadges[] = array('cls' => 'phub-badge-soft', 'icon' => 'fa-clock-o',
'txt' => $lbl('limite', 'deadline') . ' ' . fxShowDate($arrFlash['prochaine_date_limite'], $strLangue, 1));
'txt' => fxPromoteurHubTexte('promoteur_hub_badge_limite', 0) . ' ' . fxShowDate($arrFlash['prochaine_date_limite'], $strLangue, 1));
}
if (!empty($arrBadges)) {
$html .= '<div class="promoteur-hub-badges">';
@ -1151,12 +1225,10 @@ function fxPromoteurHubRenderEpreuvesGrid($arrStats, $strLangue = 'fr')
return '';
}
$blnFr = ($strLangue !== 'en');
// ---- Titre de section ----
$html = '<h6 class="promoteur-hub-produits-title text-uppercase text-muted">'
. '<i class="fa fa-flag-checkered mr-2" aria-hidden="true"></i>'
. fxPromoteurHubEsc($blnFr ? 'Épreuves en vente' : 'Events for sale') . '</h6>';
. fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_epreuves_title', 0)) . '</h6>';
// ---- Tuiles par epreuve ----
$html .= '<div class="promoteur-hub-epreuves-grid">';
@ -1182,45 +1254,45 @@ function fxPromoteurHubRenderEpreuvesGrid($arrStats, $strLangue = 'fr')
if (!$arrEpr['illimitee']) {
$html .= '<span class="phub-epr-max">/ ' . intval($arrEpr['max']) . '</span>';
}
$html .= '<span class="phub-epr-biglbl">' . fxPromoteurHubEsc($blnFr ? 'dossards vendus' : 'bibs sold') . '</span>';
$html .= '<span class="phub-epr-biglbl">' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_bibs_sold', 0)) . '</span>';
$html .= '</div>';
// Barre de progression
if (!$arrEpr['illimitee'] && intval($arrEpr['max']) > 0) {
$html .= '<div class="phub-epr-bar"><span style="width:' . intval($arrEpr['pct']) . '%"></span></div>';
$html .= '<div class="phub-epr-barlbl">' . intval($arrEpr['pct']) . '% &middot; '
. intval($arrEpr['restant']) . ' ' . fxPromoteurHubEsc($blnFr ? 'restant' : 'left') . '</div>';
. intval($arrEpr['restant']) . ' ' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_restant', 0)) . '</div>';
} else {
$html .= '<div class="phub-epr-barlbl phub-epr-illimite"><span class="phub-infini mr-1" aria-hidden="true">&infin;</span>'
. fxPromoteurHubEsc($blnFr ? 'illimité' : 'unlimited') . '</div>';
. fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_unlimited', 0)) . '</div>';
}
// Pastilles secondaires
$html .= '<div class="phub-epr-chips">';
if ($arrEpr['prix_courant'] !== null) {
$html .= '<span class="phub-chip phub-chip-prix" title="' . fxPromoteurHubEsc($blnFr ? 'Prix courant' : 'Current price') . '">'
$html .= '<span class="phub-chip phub-chip-prix" title="' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_prix_courant', 0)) . '">'
. '<i class="fa fa-tag mr-1" aria-hidden="true"></i>' . fxPromoteurHubEsc(fxShowPrix($arrEpr['prix_courant'], $strLangue)) . '</span>';
if ($arrEpr['prix_prochain'] !== null && $arrEpr['prix_date_fin'] !== '' && $arrEpr['prix_date_fin'] >= date('Y-m-d')) {
$html .= '<span class="phub-chip phub-chip-next" title="' . fxPromoteurHubEsc($blnFr ? 'Prochain prix' : 'Next price') . '">'
$html .= '<span class="phub-chip phub-chip-next" title="' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_prochain_prix', 0)) . '">'
. '<i class="fa fa-arrow-up mr-1" aria-hidden="true"></i>'
. fxPromoteurHubEsc(fxShowPrix($arrEpr['prix_prochain'], $strLangue))
. ' ' . fxPromoteurHubEsc($blnFr ? 'dès' : 'on') . ' '
. ' ' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_des', 0)) . ' '
. fxShowDate($arrEpr['prix_date_fin'], $strLangue, 1) . '</span>';
}
}
if (intval($arrEpr['checkin']) > 0) {
$html .= '<span class="phub-chip phub-chip-checkin" title="' . fxPromoteurHubEsc($blnFr ? 'Dossards remis' : 'Bibs handed out') . '">'
$html .= '<span class="phub-chip phub-chip-checkin" title="' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_dossards_remis', 0)) . '">'
. '<i class="fa fa-check mr-1" aria-hidden="true"></i>' . intval($arrEpr['checkin']) . '</span>';
}
if (intval($arrEpr['attente_nb']) > 0 || $arrEpr['attente_active']) {
$strAtt = $arrEpr['attente_active'] && intval($arrEpr['attente_nb']) === 0
? ($blnFr ? 'liste active' : 'list active')
: intval($arrEpr['attente_nb']) . ' ' . ($blnFr ? 'en attente' : 'waiting');
$html .= '<span class="phub-chip phub-chip-attente" title="' . fxPromoteurHubEsc($blnFr ? 'Liste d\'attente' : 'Waiting list') . '">'
? fxPromoteurHubTexte('promoteur_hub_liste_active', 0)
: intval($arrEpr['attente_nb']) . ' ' . fxPromoteurHubTexte('promoteur_hub_waiting_n', 0);
$html .= '<span class="phub-chip phub-chip-attente" title="' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_waiting_list', 0)) . '">'
. '<i class="fa fa-hourglass-half mr-1" aria-hidden="true"></i>' . fxPromoteurHubEsc($strAtt) . '</span>';
}
@ -1335,12 +1407,10 @@ function fxPromoteurHubRenderProduits($arrProduits, $strLangue = 'fr')
return '';
}
$blnFr = ($strLangue !== 'en');
$html = '<div class="promoteur-hub-produits">';
$html .= '<h6 class="promoteur-hub-produits-title text-uppercase text-muted">'
. '<i class="fa fa-shopping-bag mr-2" aria-hidden="true"></i>'
. fxPromoteurHubEsc($blnFr ? 'Produits en vente' : 'Products for sale') . '</h6>';
. fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_produits_title', 0)) . '</h6>';
$html .= '<div class="promoteur-hub-produits-grid">';
foreach ($arrProduits as $arrProd) {
@ -1359,17 +1429,17 @@ function fxPromoteurHubRenderProduits($arrProduits, $strLangue = 'fr')
$html .= '<span class="phub-prod-num">' . intval($arrProd['restant']) . '</span>';
$html .= '<span class="phub-prod-tot">/ ' . intval($arrProd['total']) . '</span>';
}
$html .= '<span class="phub-prod-lbl">' . fxPromoteurHubEsc($blnFr ? 'disponible(s)' : 'available') . '</span>';
$html .= '<span class="phub-prod-lbl">' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_disponible', 0)) . '</span>';
$html .= '</div>';
$html .= '<div class="phub-epr-chips">';
$html .= '<span class="phub-chip phub-chip-checkin" title="' . fxPromoteurHubEsc($blnFr ? 'Vendus' : 'Sold') . '">'
$html .= '<span class="phub-chip phub-chip-checkin" title="' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_vendus_title', 0)) . '">'
. '<i class="fa fa-check mr-1" aria-hidden="true"></i>' . intval($arrProd['vendus']) . ' '
. fxPromoteurHubEsc($blnFr ? 'vendu(s)' : 'sold') . '</span>';
. fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_vendus_lbl', 0)) . '</span>';
if (intval($arrProd['panier']) > 0) {
$html .= '<span class="phub-chip phub-chip-attente" title="' . fxPromoteurHubEsc($blnFr ? 'Dans des paniers incomplets' : 'In incomplete carts') . '">'
$html .= '<span class="phub-chip phub-chip-attente" title="' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_paniers_title', 0)) . '">'
. '<i class="fa fa-shopping-cart mr-1" aria-hidden="true"></i>' . intval($arrProd['panier']) . ' '
. fxPromoteurHubEsc($blnFr ? 'panier(s)' : 'cart(s)') . '</span>';
. fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_paniers_lbl', 0)) . '</span>';
}
$html .= '</div>'; // .phub-epr-chips
@ -1445,7 +1515,6 @@ function fxPromoteurHubRenderEventCard($arrItem, $strLangue, $blnArchived = fals
{
$strCardClass = 'card promoteur-hub-card mb-3' . ($blnArchived ? ' promoteur-hub-card-archived' : '');
$blnFr = ($strLangue !== 'en');
$intEveId = intval($arrItem['eve_id']);
$intComId = intval($arrItem['com_id'] ?? ($_SESSION['com_id'] ?? 0));
@ -1506,7 +1575,7 @@ function fxPromoteurHubRenderEventCard($arrItem, $strLangue, $blnArchived = fals
if ($blnFonctions) {
$html .= '<button type="button" class="btn btn-outline-primary rounded-pill mb-2 promoteur-hub-toggle collapsed" data-toggle="collapse" data-target="#' . fxPromoteurHubEsc($strFnId) . '" aria-expanded="false" aria-controls="' . fxPromoteurHubEsc($strFnId) . '">';
$html .= '<i class="fa fa-bars mr-2" aria-hidden="true"></i>' . fxPromoteurHubEsc($blnFr ? 'Menu des fonctionnalités' : 'Features menu');
$html .= '<i class="fa fa-bars mr-2" aria-hidden="true"></i>' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_menu_features', 0));
$html .= '<i class="fa fa-chevron-down ml-2 promoteur-hub-toggle-caret" aria-hidden="true"></i>';
$html .= '</button>';
}
@ -1517,7 +1586,7 @@ function fxPromoteurHubRenderEventCard($arrItem, $strLangue, $blnArchived = fals
// ---- Collapse : stats chargées au clic (ajax_promoteur_hub.php) ----
if ($blnHasStats) {
global $vDomaine;
$strLoadMsg = $blnFr ? 'Chargement du tableau de bord…' : 'Loading dashboard';
$strLoadMsg = fxPromoteurHubTexte('promoteur_hub_load_dashboard', 0);
$strRunnerSrc = $vDomaine . '/images/ms1-runner-loader.gif?v=' . _VERSION_CODE;
$html .= '<div class="collapse promoteur-hub-epreuves-collapse mt-2" id="' . fxPromoteurHubEsc($strEprId) . '"'
. ' data-hub-eve-id="' . $intEveId . '" data-hub-lang="' . fxPromoteurHubEsc($strLangue) . '">';

View File

@ -7,7 +7,7 @@
* Constantes *
*
**************/
define('_VERSION_CODE', '4.72.804');
define('_VERSION_CODE', '4.72.806');
define('_DATE_CODE', '2026-07-13');
//MSIN-4290
define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe');

View File

@ -23,13 +23,23 @@ if (!function_exists('fxV2RegisterScript')) {
$strLangue = ($strLangue === 'en') ? 'en' : 'fr';
$strWait = afficheTexte('preloader_wait', 0, 0, 1);
if (trim((string)$strWait) === '' || $strWait === 'preloader_wait') {
$strWait = ($strLangue === 'en') ? 'Loading…' : 'Chargement…';
if (trim((string)$strWait) === '' || $strWait === 'preloader_wait' || $strWait === '*preloader_wait*') {
$strWait = function_exists('fxPromoteurHubTexte')
? fxPromoteurHubTexte('promoteur_hub_load_dashboard', 0)
: '';
}
// MSIN-4401 — libellés Info injectés (pas de FR/EN en dur dans promoteur-hub.js).
$strAjaxError = function_exists('fxPromoteurHubTexte')
? fxPromoteurHubTexte('promoteur_hub_ajax_error', 0)
: afficheTexte('promoteur_hub_ajax_error', 0, 0, 1);
return [
'ajaxUrl' => $vDomaine . '/ajax_promoteur_hub.php',
'preloaderWait' => $strWait,
'i18n' => [
'ajaxError' => $strAjaxError,
],
];
}
@ -47,17 +57,29 @@ if (!function_exists('fxV2RegisterScript')) {
? (bool)fxInscrGestionBibOcrCloudEnabled()
: false,
'preloaderWait' => afficheTexte('preloader_wait', 0, 0, 1),
'refundCancelHint' => ($strLangue === 'en')
? 'Your transaction has been refunded. If you also want to cancel it in the registration system:'
: "Votre transaction a été remboursée. Si vous voulez aussi l'annuler dans le système d'inscription :",
'refundCancelHint' => function_exists('fxInscrGestionT')
? fxInscrGestionT('inscr_gestion_refund_cancel_hint')
: '',
'loginMessages' => [
'txt_login' => ($strLangue === 'fr')
? "Veuillez entrer votre nom d'usager"
: 'Please enter your username',
'txt_password' => ($strLangue === 'fr')
? 'Veuillez entrer votre mot de passe'
: 'Please enter your password',
'txt_login' => function_exists('fxInscrGestionT')
? fxInscrGestionT('inscr_gestion_login_err_user')
: '',
'txt_password' => function_exists('fxInscrGestionT')
? fxInscrGestionT('inscr_gestion_login_err_pass')
: '',
],
// MSIN-4401 — libellés Info injectés (pas de FR/EN en dur dans le JS).
'i18n' => function_exists('fxInscrGestionT') ? [
'errLoad' => fxInscrGestionT('inscr_gestion_err_load'),
'errNetwork' => fxInscrGestionT('inscr_gestion_err_network'),
'errRefund' => fxInscrGestionT('inscr_gestion_err_refund'),
'refundOk' => fxInscrGestionT('inscr_gestion_refund_ok'),
'bibOcrNotConfigured' => fxInscrGestionT('inscr_gestion_bib_ocr_not_configured'),
'bibOcrVisionPrefix' => fxInscrGestionT('inscr_gestion_bib_ocr_vision_prefix'),
'bibOcrImageInvalid' => fxInscrGestionT('inscr_gestion_bib_ocr_image_invalid'),
'bibOcrNetwork' => fxInscrGestionT('inscr_gestion_bib_ocr_network'),
'bibOcrUnreadable' => fxInscrGestionT('inscr_gestion_bib_scan_invalid'),
] : [],
];
}

View File

@ -0,0 +1,99 @@
-- MSIN-4401 — Libellés Info manquants (gestion accès V2 promoteur)
-- Prérequis : sql/MSIN-4401-eve-acces-promoteur-page.sql (clés de base déjà en Info)
-- Notes : idempotent (DELETE + INSERT). Source de vérité = table info, pas le PHP.
DELETE FROM info
WHERE info_clef IN (
'eve_acces_promoteur_statut_actif',
'eve_acces_promoteur_statut_pending',
'eve_acces_promoteur_col_action',
'eve_acces_promoteur_field_courriel',
'eve_acces_promoteur_field_prenom',
'eve_acces_promoteur_field_nom',
'eve_acces_promoteur_field_kit',
'eve_acces_promoteur_field_jours',
'eve_acces_promoteur_jours_help',
'eve_acces_promoteur_invite_courriel_help',
'eve_acces_promoteur_btn_invite',
'eve_acces_promoteur_btn_resend',
'eve_acces_promoteur_btn_revoke',
'eve_acces_promoteur_confirm_revoke',
'eve_acces_promoteur_no_kits',
'eve_acces_promoteur_ok_existing',
'eve_acces_promoteur_ok_created',
'eve_acces_promoteur_ok_revoked',
'eve_acces_promoteur_ok_resent',
'eve_acces_promoteur_err_courriel',
'eve_acces_promoteur_err_kit',
'eve_acces_promoteur_err_nom',
'eve_acces_promoteur_err_inactive',
'eve_acces_promoteur_err_revoke',
'eve_acces_promoteur_err_resend',
'eve_acces_promoteur_err_generic',
'eve_acces_promoteur_check_exists',
'eve_acces_promoteur_check_new',
'eve_acces_promoteur_check_inactive',
'eve_acces_promoteur_check_invalid'
) AND info_prg = 'compte.php';
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) VALUES
('eve_acces_promoteur_statut_actif', 'fr', 'Actif', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_statut_actif', 'en', 'Active', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_statut_pending', 'fr', 'Compte non activé', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_statut_pending', 'en', 'Account not activated', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_col_action', 'fr', 'Action', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_col_action', 'en', 'Action', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_field_courriel', 'fr', 'Courriel', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_field_courriel', 'en', 'Email', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_field_prenom', 'fr', 'Prénom', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_field_prenom', 'en', 'First name', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_field_nom', 'fr', 'Nom', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_field_nom', 'en', 'Last name', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_field_kit', 'fr', 'Kit d''accès', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_field_kit', 'en', 'Access kit', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_field_jours', 'fr', 'Durée (jours)', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_field_jours', 'en', 'Duration (days)', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_jours_help', 'fr', '0 = sans expiration. Utilisé surtout pour les nouveaux accès.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_jours_help', 'en', '0 = no expiration. Mainly for new access grants.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_invite_courriel_help', 'fr', 'Saisissez d''abord le courriel : on vérifie s''il existe déjà un compte MS1 avant de demander le nom.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_invite_courriel_help', 'en', 'Enter the email first: we check if an MS1 account already exists before asking for a name.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_btn_invite', 'fr', 'Assigner l''accès', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_btn_invite', 'en', 'Grant access', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_btn_resend', 'fr', 'Renvoyer le mail', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_btn_resend', 'en', 'Resend email', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_btn_revoke', 'fr', 'Retirer', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_btn_revoke', 'en', 'Revoke', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_confirm_revoke', 'fr', 'Retirer cet accès ?', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_confirm_revoke', 'en', 'Revoke this access?', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_no_kits', 'fr', 'Aucun kit délégable disponible. Demandez à un super admin d''activer « délégable » sur un kit.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_no_kits', 'en', 'No delegable kit available. Ask a super admin to mark a kit as delegable.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_ok_existing', 'fr', 'Accès assigné au compte existant. Un courriel a été envoyé.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_ok_existing', 'en', 'Access granted to the existing account. An email was sent.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_ok_created', 'fr', 'Compte créé et accès assigné. Un courriel avec le lien de mot de passe a été envoyé.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_ok_created', 'en', 'Account created and access granted. An email with the password link was sent.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_ok_revoked', 'fr', 'Accès retiré.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_ok_revoked', 'en', 'Access revoked.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_ok_resent', 'fr', 'Courriel d''invitation renvoyé.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_ok_resent', 'en', 'Invitation email resent.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_err_courriel', 'fr', 'Courriel invalide.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_err_courriel', 'en', 'Invalid email.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_err_kit', 'fr', 'Kit non autorisé.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_err_kit', 'en', 'Kit not allowed.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_err_nom', 'fr', 'Prénom et nom requis pour créer un nouveau compte.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_err_nom', 'en', 'First and last name required to create a new account.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_err_inactive', 'fr', 'Un compte existe avec ce courriel mais il est inactif. Contactez le support.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_err_inactive', 'en', 'An account exists with this email but it is inactive. Contact support.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_err_revoke', 'fr', 'Retrait impossible.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_err_revoke', 'en', 'Unable to revoke.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_err_resend', 'fr', 'Renvoi du courriel impossible.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_err_resend', 'en', 'Unable to resend the email.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_err_generic', 'fr', 'Une erreur est survenue.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_err_generic', 'en', 'An error occurred.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_check_exists', 'fr', 'Compte déjà existant. Un courriel de confirmation sera envoyé.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_check_exists', 'en', 'Existing account. A confirmation email will be sent.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_check_new', 'fr', 'Aucun compte avec ce courriel. Indiquez le prénom et le nom pour créer le compte.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_check_new', 'en', 'No account with this email. Enter first and last name to create the account.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_check_inactive', 'fr', 'Un compte existe avec ce courriel mais il est inactif. Contactez le support.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_check_inactive', 'en', 'An account exists with this email but it is inactive. Contact support.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_check_invalid', 'fr', 'Courriel invalide.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_check_invalid', 'en', 'Invalid email.', '', 'compte.php', '', 0, 1, '', '', '', NOW());

View File

@ -0,0 +1,290 @@
-- MSIN-4401 — Libellés Info V2 manquants (source de vérité)
-- Couvre : mobile invite, courriels accès, gestion inscriptions, OCR/JS, hub dashboard
-- Prérequis : scripts MSIN-4401-eve-acces-promoteur-*.sql déjà exécutés si applicable
-- Idempotent (DELETE + INSERT). Exécuter en pilote puis prod.
DELETE FROM info
WHERE info_prg = 'compte.php'
AND info_clef IN (
'mobile_invite_conflict_signed_as',
'mobile_invite_conflict_for',
'mobile_invite_conflict_btn_logout',
'mobile_invite_conflict_btn_stay',
'mobile_no_acces_v2',
'inscr_gestion_mobile_title',
'inscr_gestion_login_user',
'inscr_gestion_login_pass',
'inscr_gestion_login_btn',
'inscr_gestion_login_intro',
'inscr_gestion_login_err_user',
'inscr_gestion_login_err_pass',
'inscr_gestion_choose_event',
'inscr_gestion_back_events',
'tableau_promoteur_menueinscription_v2',
'eve_acces_promoteur_access_denied',
'eve_acces_promoteur_btn_back',
'eve_acces_promoteur_mail_new_subject',
'eve_acces_promoteur_mail_new_body',
'eve_acces_promoteur_mail_new_cta',
'eve_acces_promoteur_mail_existing_subject',
'eve_acces_promoteur_mail_existing_body',
'eve_acces_promoteur_mail_existing_cta',
'inscr_gestion_show_invoice',
'inscr_gestion_show_transaction',
'inscr_gestion_hide_transaction',
'inscr_gestion_renvoi',
'inscr_gestion_modifier_participant',
'inscr_gestion_search_prompt',
'inscr_gestion_annulation_titre',
'inscr_gestion_statut_annule',
'inscr_gestion_statut_transfert',
'inscr_gestion_back_bib',
'inscr_gestion_back_prev',
'inscr_gestion_back_full_account',
'inscr_gestion_back_mon_compte',
'inscr_gestion_chk_cancelled',
'inscr_gestion_rech_statut',
'inscr_gestion_rech_statut_all',
'inscr_gestion_rech_statut_n',
'inscr_gestion_bib_scan_found_search',
'inscr_gestion_bib_ocr_not_configured',
'inscr_gestion_bib_ocr_vision_prefix',
'inscr_gestion_bib_ocr_image_invalid',
'inscr_gestion_bib_ocr_network',
'inscr_gestion_err_load',
'inscr_gestion_err_network',
'inscr_gestion_err_refund',
'inscr_gestion_refund_ok',
'inscr_gestion_refund_cancel_hint',
'promoteur_hub_load_menu',
'promoteur_hub_load_dashboard',
'promoteur_hub_epreuves_title',
'promoteur_hub_produits_title',
'promoteur_hub_bibs_sold',
'promoteur_hub_restant',
'promoteur_hub_unlimited',
'promoteur_hub_prix_courant',
'promoteur_hub_prochain_prix',
'promoteur_hub_des',
'promoteur_hub_dossards_remis',
'promoteur_hub_liste_active',
'promoteur_hub_waiting_n',
'promoteur_hub_waiting_list',
'promoteur_hub_disponible',
'promoteur_hub_vendus_title',
'promoteur_hub_vendus_lbl',
'promoteur_hub_paniers_title',
'promoteur_hub_paniers_lbl',
'promoteur_hub_menu_features',
'promoteur_hub_flash_inscrits',
'promoteur_hub_flash_rempli',
'promoteur_hub_flash_places',
'promoteur_hub_flash_revenus',
'promoteur_hub_badge_completes',
'promoteur_hub_badge_presque',
'promoteur_hub_badge_attente',
'promoteur_hub_badge_limite',
'promoteur_hub_ajax_session',
'promoteur_hub_ajax_invalid',
'promoteur_hub_ajax_denied',
'promoteur_hub_ajax_not_found',
'promoteur_hub_ajax_error',
'promoteur_hub_rapport_chrono',
'promoteur_hub_rapport_finances',
'promoteur_hub_rapport_finances_all',
'promoteur_hub_rapport_finances_adh',
'promoteur_hub_rapport_finances_eve',
'promoteur_hub_rapport_adhesions',
'promoteur_hub_rapport_adhesions_annuelles',
'promoteur_hub_rapport_finances_capitaines',
'promoteur_hub_rapport_paiements',
'promoteur_hub_no_features'
);
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) VALUES
('mobile_invite_conflict_signed_as', 'fr', 'Vous êtes actuellement connecté en tant que %name%.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('mobile_invite_conflict_signed_as', 'en', 'You are currently signed in as %name%.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('mobile_invite_conflict_for', 'fr', 'Cette invitation concerne le compte %name%%mail_part%. Déconnectez-vous d''abord, puis reconnectez-vous avec ce compte.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('mobile_invite_conflict_for', 'en', 'This invitation is for %name%%mail_part%. Sign out first, then sign in with that account.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('mobile_invite_conflict_btn_logout', 'fr', 'Se déconnecter et continuer', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('mobile_invite_conflict_btn_logout', 'en', 'Sign out and continue', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('mobile_invite_conflict_btn_stay', 'fr', 'Rester sur mon compte actuel', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('mobile_invite_conflict_btn_stay', 'en', 'Stay on my current account', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('mobile_no_acces_v2', 'fr', 'Aucun accès inscriptions mobile (v2) n''est actif pour ce compte.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('mobile_no_acces_v2', 'en', 'No active mobile registration access (v2) for this account.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_mobile_title', 'fr', 'Inscriptions mobile', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_mobile_title', 'en', 'Mobile registrations', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_login_user', 'fr', 'Nom d''utilisateur', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_login_user', 'en', 'Username', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_login_pass', 'fr', 'Mot de passe', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_login_pass', 'en', 'Password', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_login_btn', 'fr', 'Connexion', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_login_btn', 'en', 'Sign in', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_login_intro', 'fr', 'Connectez-vous pour accéder aux inscriptions mobile.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_login_intro', 'en', 'Sign in to access mobile registrations.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_login_err_user', 'fr', 'Veuillez entrer votre nom d''usager', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_login_err_user', 'en', 'Please enter your username', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_login_err_pass', 'fr', 'Veuillez entrer votre mot de passe', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_login_err_pass', 'en', 'Please enter your password', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_choose_event', 'fr', 'Choisir un événement', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_choose_event', 'en', 'Choose an event', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_back_events', 'fr', 'Événements', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_back_events', 'en', 'Events', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('tableau_promoteur_menueinscription_v2', 'fr', 'Inscriptions V2', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('tableau_promoteur_menueinscription_v2', 'en', 'Registrations V2', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_access_denied', 'fr', 'Accès refusé.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_access_denied', 'en', 'Access denied.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_btn_back', 'fr', 'Retour', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_btn_back', 'en', 'Back', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_mail_new_subject', 'fr', 'Accès promoteur — créez votre mot de passe', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_mail_new_subject', 'en', 'Promoter access — set your password', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_mail_new_body', '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>', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_mail_new_body', '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>', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_mail_new_cta', 'fr', 'Choisir mon mot de passe', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_mail_new_cta', 'en', 'Set my password', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_mail_existing_subject', 'fr', 'Nouvel accès — %eve_nom%', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_mail_existing_subject', 'en', 'New access — %eve_nom%', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_mail_existing_body', '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).', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_mail_existing_body', '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).', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_mail_existing_cta', 'fr', 'Ouvrir l''accès (mobile)', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('eve_acces_promoteur_mail_existing_cta', 'en', 'Open access (mobile)', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_show_invoice', 'fr', 'Afficher la facture', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_show_invoice', 'en', 'Show invoice', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_show_transaction', 'fr', 'Afficher la transaction', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_show_transaction', 'en', 'Show transaction', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_hide_transaction', 'fr', 'Masquer la transaction', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_hide_transaction', 'en', 'Hide transaction', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_renvoi', 'fr', 'Retourner confirmation', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_renvoi', 'en', 'Resend confirmation', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_modifier_participant', 'fr', 'Modifier l''inscription', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_modifier_participant', 'en', 'Edit registration', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_search_prompt', 'fr', 'Utilisez la recherche ci-dessus pour afficher les inscriptions. Laissez les champs vides et lancez la recherche pour toutes les afficher.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_search_prompt', 'en', 'Use the search above to display registrations. Leave the fields empty and search to show them all.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_annulation_titre', 'fr', 'Statut de l''inscription', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_annulation_titre', 'en', 'Registration status', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_statut_annule', 'fr', 'Annulée', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_statut_annule', 'en', 'Cancelled', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_statut_transfert', 'fr', 'Transférée', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_statut_transfert', 'en', 'Transferred', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_back_bib', 'fr', 'Retour aux dossards', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_back_bib', 'en', 'Back to bibs', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_back_prev', 'fr', 'Retour', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_back_prev', 'en', 'Back', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_back_full_account', 'fr', 'Retour au tableau de bord', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_back_full_account', 'en', 'Back to dashboard', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_back_mon_compte', 'fr', 'Retour à Mon compte', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_back_mon_compte', 'en', 'Back to My account', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_chk_cancelled', 'fr', 'Montrer les inscriptions annulées ou transférées', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_chk_cancelled', 'en', 'Show cancelled or transferred registrations', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_rech_statut', 'fr', 'Statut de course', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_rech_statut', 'en', 'Race status', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_rech_statut_all', 'fr', 'Tous les statuts', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_rech_statut_all', 'en', 'All statuses', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_rech_statut_n', 'fr', '%d sélectionnés', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_rech_statut_n', 'en', '%d selected', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_bib_scan_found_search', 'fr', 'Numéro %s détecté — recherche...', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_bib_scan_found_search', 'en', 'Number %s detected — searching...', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_bib_ocr_not_configured', 'fr', 'OCR non configuré (clé Vision absente sur le serveur).', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_bib_ocr_not_configured', 'en', 'OCR not configured (Vision API key missing on server).', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_bib_ocr_vision_prefix', 'fr', 'Erreur Vision : ', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_bib_ocr_vision_prefix', 'en', 'Vision error: ', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_bib_ocr_image_invalid', 'fr', 'Image invalide.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_bib_ocr_image_invalid', 'en', 'Invalid image capture.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_bib_ocr_network', 'fr', 'Erreur réseau ou capture.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_bib_ocr_network', 'en', 'Network or capture error.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_err_load', 'fr', 'Erreur de chargement.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_err_load', 'en', 'Loading error.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_err_network', 'fr', 'Erreur réseau.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_err_network', 'en', 'Network error.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_err_refund', 'fr', 'Erreur remboursement.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_err_refund', 'en', 'Refund error.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_refund_ok', 'fr', 'Remboursement effectué.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_refund_ok', 'en', 'Refund completed.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_refund_cancel_hint', 'fr', 'Votre transaction a été remboursée. Si vous voulez aussi l''annuler dans le système d''inscription :', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('inscr_gestion_refund_cancel_hint', 'en', 'Your transaction has been refunded. If you also want to cancel it in the registration system:', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_load_menu', 'fr', 'Chargement du menu…', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_load_menu', 'en', 'Loading menu…', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_load_dashboard', 'fr', 'Chargement du tableau de bord…', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_load_dashboard', 'en', 'Loading dashboard…', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_epreuves_title', 'fr', 'Épreuves en vente', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_epreuves_title', 'en', 'Events for sale', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_produits_title', 'fr', 'Produits en vente', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_produits_title', 'en', 'Products for sale', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_bibs_sold', 'fr', 'dossards vendus', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_bibs_sold', 'en', 'bibs sold', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_restant', 'fr', 'restant', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_restant', 'en', 'left', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_unlimited', 'fr', 'illimité', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_unlimited', 'en', 'unlimited', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_prix_courant', 'fr', 'Prix courant', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_prix_courant', 'en', 'Current price', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_prochain_prix', 'fr', 'Prochain prix', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_prochain_prix', 'en', 'Next price', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_des', 'fr', 'dès', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_des', 'en', 'on', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_dossards_remis', 'fr', 'Dossards remis', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_dossards_remis', 'en', 'Bibs handed out', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_liste_active', 'fr', 'liste active', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_liste_active', 'en', 'list active', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_waiting_n', 'fr', 'en attente', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_waiting_n', 'en', 'waiting', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_waiting_list', 'fr', 'Liste d''attente', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_waiting_list', 'en', 'Waiting list', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_disponible', 'fr', 'disponible(s)', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_disponible', 'en', 'available', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_vendus_title', 'fr', 'Vendus', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_vendus_title', 'en', 'Sold', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_vendus_lbl', 'fr', 'vendu(s)', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_vendus_lbl', 'en', 'sold', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_paniers_title', 'fr', 'Dans des paniers incomplets', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_paniers_title', 'en', 'In incomplete carts', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_paniers_lbl', 'fr', 'panier(s)', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_paniers_lbl', 'en', 'cart(s)', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_menu_features', 'fr', 'Menu des fonctionnalités', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_menu_features', 'en', 'Features menu', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_flash_inscrits', 'fr', 'inscrits', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_flash_inscrits', 'en', 'registered', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_flash_rempli', 'fr', 'rempli', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_flash_rempli', 'en', 'filled', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_flash_places', 'fr', 'places restantes', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_flash_places', 'en', 'spots left', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_flash_revenus', 'fr', 'revenus est.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_flash_revenus', 'en', 'est. revenue', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_badge_completes', 'fr', 'complète(s)', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_badge_completes', 'en', 'sold out', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_badge_presque', 'fr', 'presque pleine(s)', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_badge_presque', 'en', 'almost full', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_badge_attente', 'fr', 'en attente', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_badge_attente', 'en', 'waiting', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_badge_limite', 'fr', 'limite', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_badge_limite', 'en', 'deadline', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_ajax_session', 'fr', 'Session expirée', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_ajax_session', 'en', 'Session expired', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_ajax_invalid', 'fr', 'Requête invalide', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_ajax_invalid', 'en', 'Invalid request', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_ajax_denied', 'fr', 'Accès refusé', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_ajax_denied', 'en', 'Access denied', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_ajax_not_found', 'fr', 'Événement introuvable', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_ajax_not_found', 'en', 'Event not found', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_ajax_error', 'fr', 'Erreur', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_ajax_error', 'en', 'Error', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_rapport_chrono', 'fr', 'chrono + questions', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_rapport_chrono', 'en', 'chrono + questions', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_rapport_finances', 'fr', 'Transactions-Finances', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_rapport_finances', 'en', 'Transactions-Finances', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_rapport_finances_all', 'fr', 'Transactions-Finances-Tout', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_rapport_finances_all', 'en', 'Transactions-Finances-All', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_rapport_finances_adh', 'fr', 'Transactions-Finances-Adhésions Seul', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_rapport_finances_adh', 'en', 'Transactions-Finances-Memberships only', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_rapport_finances_eve', 'fr', 'Transactions-Finances-Événements Seul', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_rapport_finances_eve', 'en', 'Transactions-Finances-Events only', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_rapport_adhesions', 'fr', 'adhésions actives', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_rapport_adhesions', 'en', 'active memberships', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_rapport_adhesions_annuelles', 'fr', 'liste d''adhésions annuelles', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_rapport_adhesions_annuelles', 'en', 'annual memberships list', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_rapport_finances_capitaines', 'fr', 'Transactions-Finances (capitaines seulement)', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_rapport_finances_capitaines', 'en', 'Transactions-Finances (captains only)', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_rapport_paiements', 'fr', 'Paiements', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_rapport_paiements', 'en', 'Payments', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_no_features', 'fr', 'Aucune fonctionnalité disponible avec vos droits actuels.', '', 'compte.php', '', 0, 1, '', '', '', NOW()),
('promoteur_hub_no_features', 'en', 'No features available with your current permissions.', '', 'compte.php', '', 0, 1, '', '', '', NOW());