Files
ms1inscription-v5/php/inc_fx_promoteur_hub.php
stephan ca452f9ade Update promoteur hub statistics display and refine CSS for better user experience
This commit enhances the `fxPromoteurHubGetEpreuveStats` function to improve the aggregation and presentation of event statistics, including sold tickets and check-ins. Additionally, it refines the CSS styles for the promoteur hub, ensuring a more cohesive and visually appealing layout for statistics and event tiles. These updates aim to provide users with clearer insights and a more organized interface for event management.
2026-06-27 17:35:37 -04:00

1108 lines
43 KiB
PHP

<?php
/**
* Hub promoteur — comptes migres acces evenement v2 uniquement.
* Legacy (com_eve_promoteur) : comportement inchange, pas de hub.
*/
require_once __DIR__ . '/inc_fx_eve_acces.php';
function fxPromoteurHubComUsesV2($intComId)
{
return fxEveAccesComHasV2(intval($intComId));
}
/** La page compte_promoteur_hub est-elle installee (SQL applique) ? */
function fxPromoteurHubPageExists()
{
global $objDatabase;
static $blnExists = null;
if ($blnExists !== null) {
return $blnExists;
}
if (!isset($objDatabase)) {
$blnExists = false;
return $blnExists;
}
$intNb = $objDatabase->fxGetVar(
"SELECT COUNT(*) FROM inscriptions_pages WHERE pag_code = 'compte_promoteur_hub' AND pag_actif = 1"
);
$blnExists = ($intNb !== null && intval($intNb) > 0);
return $blnExists;
}
/** Le compte doit-il atterrir sur le hub v2 ? (acces v2 + page installee) */
function fxPromoteurHubShouldUse($intComId)
{
return fxPromoteurHubComUsesV2(intval($intComId)) && fxPromoteurHubPageExists();
}
function fxPromoteurHubUrl($strLangue = 'fr')
{
global $vDomaine;
return $vDomaine . ($strLangue === 'fr' ? '/compte/promoteur_hub' : '/account/promoteur_hub');
}
function fxPromoteurHubParticipantUrl($strLangue = 'fr')
{
global $vDomaine;
return $vDomaine . ($strLangue === 'fr' ? '/compte/accueil' : '/account/accueil');
}
function fxPromoteurHubPromoteurTableUrl($intEveId, $strLangue = 'fr')
{
global $vDomaine;
$strBase = ($strLangue === 'fr') ? '/compte/inc_tableau_promoteur' : '/account/inc_tableau_promoteur';
return $vDomaine . $strBase . '?promoteur_eve_id=' . urlencode(base64_encode(intval($intEveId)));
}
function fxPromoteurHubPermKeysWeb()
{
return array(
'inscriptions.view',
'inscriptions.edit',
'dossards.view',
'dossards.manage',
'epreuves.view',
'epreuves.edit_qte',
'rabais.view',
'rabais.manage',
'emails.send',
'reports.chrono',
'reports.finances',
'reports.finances_membership',
'reports.finances_events',
'reports.payments',
'reports.adhesions',
'reports.adhesions_sales',
'reports.finances_captains',
'reports.custom',
'team.view',
'team.invite',
'team.manage',
);
}
function fxPromoteurHubCanAccessPromoteurWeb($intComId, $intEveId)
{
if (!function_exists('fxIsPromoteur')) {
require_once __DIR__ . '/inc_fonctions.php';
}
if (fxIsPromoteur(intval($intComId), intval($intEveId))) {
return true;
}
if (!fxEveAccesComHasV2ForEvent(intval($intComId), intval($intEveId))) {
return false;
}
return fxEveAccesHasAnyPermission(intval($intComId), intval($intEveId), fxPromoteurHubPermKeysWeb());
}
function fxPromoteurHubParseDateTs($strDate)
{
$strDate = trim((string)$strDate);
if ($strDate === '' || strpos($strDate, '0000-00-00') === 0) {
return 0;
}
return strtotime(substr($strDate, 0, 10));
}
function fxPromoteurHubHighlightUntilTs($strDateDebut, $strDateFin)
{
$intDebut = fxPromoteurHubParseDateTs($strDateDebut);
$intFin = fxPromoteurHubParseDateTs($strDateFin);
$intRef = ($intFin > 0) ? max($intDebut, $intFin) : $intDebut;
if ($intRef <= 0) {
return 0;
}
return strtotime('+14 days', $intRef);
}
function fxPromoteurHubFormatEventDate($arrEvent, $strLangue)
{
$strDebut = trim($arrEvent['eve_date_debut'] ?? '');
$strFin = trim($arrEvent['eve_date_fin'] ?? '');
if ($strDebut === '' || strpos($strDebut, '0000-00-00') === 0) {
return '';
}
$strFmtDebut = date('Y-m-d', fxPromoteurHubParseDateTs($strDebut));
$intFin = fxPromoteurHubParseDateTs($strFin);
if ($intFin <= 0 || $strFmtDebut === date('Y-m-d', $intFin)) {
return $strFmtDebut;
}
return $strFmtDebut . ' — ' . date('Y-m-d', $intFin);
}
function fxPromoteurHubGetRoleLabelsByEvent($intComId)
{
global $objDatabase;
$arrByEve = array();
$tabRows = fxEveAccesListByComId(intval($intComId));
if (!is_array($tabRows)) {
return $arrByEve;
}
for ($i = 1; $i <= count($tabRows); $i++) {
$row = $tabRows[$i];
if (($row['ea_statut'] ?? '') !== 'actif') {
continue;
}
$intEveId = intval($row['eve_id'] ?? 0);
if ($intEveId <= 0) {
continue;
}
$strLabel = trim($row['role_label_fr'] ?? '');
if ($strLabel === '') {
$strLabel = trim($row['role_code'] ?? '');
}
if ($strLabel === '') {
continue;
}
if (!isset($arrByEve[$intEveId])) {
$arrByEve[$intEveId] = array();
}
if (!in_array($strLabel, $arrByEve[$intEveId], true)) {
$arrByEve[$intEveId][] = $strLabel;
}
}
return $arrByEve;
}
/** Evenements promoteur legacy (com_eve_promoteur) pour ce compte. */
function fxPromoteurHubGetLegacyEventIds($intComId)
{
global $objDatabase;
$row = $objDatabase->fxGetRow(
'SELECT com_eve_promoteur FROM inscriptions_comptes WHERE com_id = ' . intval($intComId)
);
if ($row === null || trim((string)$row['com_eve_promoteur']) === '') {
return array();
}
return array_map('trim', explode(',', $row['com_eve_promoteur']));
}
function fxPromoteurHubGetEvents($intComId, $strLangue = 'fr')
{
global $objDatabase, $vDomaine, $vRepertoireFichiers;
$arrHighlight = array();
$arrArchived = array();
// Union : evenements legacy (com_eve_promoteur) + acces v2
$arrEventIds = array_merge(
fxPromoteurHubGetLegacyEventIds(intval($intComId)),
fxEveAccesGetEventIds(intval($intComId))
);
if (empty($arrEventIds)) {
return array('highlight' => $arrHighlight, 'archived' => $arrArchived);
}
$arrIds = array_values(array_unique(array_filter(array_map('intval', $arrEventIds), function ($v) {
return $v > 0;
})));
if (empty($arrIds)) {
return array('highlight' => $arrHighlight, 'archived' => $arrArchived);
}
$strLang = in_array($strLangue, array('fr', 'en'), true) ? $strLangue : 'fr';
$strAlt = ($strLang === 'fr') ? 'en' : 'fr';
$arrRoles = fxPromoteurHubGetRoleLabelsByEvent(intval($intComId));
$intToday = strtotime(date('Y-m-d'));
$sql = "
SELECT e.eve_id, e.eve_nom_fr, e.eve_nom_en, e.eve_date_debut, e.eve_date_fin,
e.eve_lieu_fr, e.eve_lieu_en, e.eve_photo_fr, e.eve_photo_en,
e.te_id, e.eve_qte_modifiable, e.eve_rapport_finances_capitaines,
t.te_icone
FROM inscriptions_evenements e
JOIN inscriptions_types_evenements t ON t.te_id = e.te_id
WHERE e.eve_actif = 1
AND e.eve_id IN (" . implode(',', $arrIds) . ")
ORDER BY e.eve_date_debut ASC, e.eve_nom_fr ASC
";
$tabEvents = $objDatabase->fxGetResults($sql);
if (!is_array($tabEvents)) {
return array('highlight' => $arrHighlight, 'archived' => $arrArchived);
}
for ($i = 1; $i <= count($tabEvents); $i++) {
$row = $tabEvents[$i];
$intEveId = intval($row['eve_id']);
$strNom = trim($row['eve_nom_' . $strLang] ?? '');
if ($strNom === '') {
$strNom = trim($row['eve_nom_' . $strAlt] ?? '');
}
$strLieu = trim($row['eve_lieu_' . $strLang] ?? '');
if ($strLieu === '') {
$strLieu = trim($row['eve_lieu_' . $strAlt] ?? '');
}
if (intval($row['eve_id']) === 175 && trim($row['eve_photo_' . $strLang] ?? '') !== '') {
$strIcon = $vDomaine . $vRepertoireFichiers . '/images/evenements/' . $row['eve_photo_' . $strLang];
} else {
$strIcon = $vDomaine . '/images/' . ($row['te_icone'] ?? '');
}
$intHighlightUntil = fxPromoteurHubHighlightUntilTs($row['eve_date_debut'] ?? '', $row['eve_date_fin'] ?? '');
$blnHighlight = ($intHighlightUntil <= 0) || ($intToday <= $intHighlightUntil);
$arrItem = array(
'eve_id' => $intEveId,
'nom' => $strNom,
'lieu' => $strLieu,
'date_label' => fxPromoteurHubFormatEventDate($row, $strLang),
'ts_debut' => fxPromoteurHubParseDateTs($row['eve_date_debut'] ?? ''),
'icon' => $strIcon,
'roles' => $arrRoles[$intEveId] ?? array(),
'te_id' => intval($row['te_id'] ?? 0),
'qte_modifiable' => intval($row['eve_qte_modifiable'] ?? 0),
'rapport_capitaines' => intval($row['eve_rapport_finances_capitaines'] ?? 0),
'com_id' => intval($intComId),
'url_promoteur' => fxPromoteurHubCanAccessPromoteurWeb($intComId, $intEveId)
? fxPromoteurHubPromoteurTableUrl($intEveId, $strLang)
: '',
'url_mobile' => '',
);
if (!function_exists('fxInscrGestionCanAccess')) {
require_once __DIR__ . '/inc_fx_inscriptions_gestion.php';
}
if (fxInscrGestionCanAccess(intval($intComId), $intEveId)) {
$arrItem['url_mobile'] = fxInscrGestionBaseUrl($strLang, false)
. '?promoteur_eve_id=' . urlencode(base64_encode($intEveId));
}
if ($blnHighlight) {
$arrHighlight[] = $arrItem;
} else {
$arrArchived[] = $arrItem;
}
}
usort($arrHighlight, function ($a, $b) {
$ta = intval($a['ts_debut'] ?? 0);
$tb = intval($b['ts_debut'] ?? 0);
if ($ta === $tb) {
return strcasecmp($a['nom'], $b['nom']);
}
if ($ta <= 0) {
return 1;
}
if ($tb <= 0) {
return -1;
}
return $ta <=> $tb;
});
usort($arrArchived, function ($a, $b) {
$ta = intval($a['ts_debut'] ?? 0);
$tb = intval($b['ts_debut'] ?? 0);
if ($ta === $tb) {
return strcasecmp($b['nom'], $a['nom']);
}
return $tb <=> $ta;
});
return array('highlight' => $arrHighlight, 'archived' => $arrArchived);
}
function fxPromoteurHubEsc($str)
{
return htmlspecialchars((string)$str, ENT_QUOTES, 'UTF-8');
}
/**
* Liste des fonctions d'un evenement (groupes : rapports + gestion),
* fidele a inc_tableau_promoteur.php.
*/
function fxPromoteurHubGetEventLinks($arrItem, $strLangue)
{
global $vDomaine, $objDatabase;
$intEveId = intval($arrItem['eve_id']);
$intComId = intval($arrItem['com_id'] ?? ($_SESSION['com_id'] ?? 0));
$intTeId = intval($arrItem['te_id'] ?? 0);
$blnDon = ($intTeId == 9);
$strE = urlencode(base64_encode($intEveId));
$strRapport = $vDomaine . '/superadm/rapport_new.php?r=';
$strCompte = $vDomaine . ($strLangue === 'fr' ? '/compte/' : '/account/');
$year = date('Y');
$strDateTq = $year . '-04-01 00:00:00';
$arrRapports = array();
$arrGestion = array();
// ---- Rapports ----
if ($intTeId != 9) {
$arrRapports[] = array('label' => 'chrono + questions', 'url' => $strRapport . 'chrono&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank');
}
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');
} else {
$arrRapports[] = array('label' => 'Transactions-Finances', 'url' => $strRapport . 'finances&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank');
}
if ($intTeId == 13) {
$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' => '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' => 'adhésions actives', 'url' => $strRapport . 'adhesions&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank');
$arrRapports[] = array('label' => "liste d'adhésions annuelles", 'url' => $strRapport . 'adhesions-ventes&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank');
}
if (intval($arrItem['rapport_capitaines'] ?? 0) == 1) {
$arrRapports[] = array('label' => 'Transactions-Finances (capitaines seulement)', 'url' => $strRapport . 'finances-pnq&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank');
}
$arrRapports[] = array('label' => ($strLangue === 'fr' ? 'Paiements' : 'Payments'), 'url' => $strRapport . 'remboursements&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank');
if (function_exists('fxGetMenussuperadm')) {
$tabRapports = fxGetMenussuperadm(0, 0, 1, $intEveId);
if (is_array($tabRapports)) {
for ($intCtr = 1; $intCtr <= count($tabRapports); $intCtr++) {
$arrRapports[] = array(
'label' => fxUnescape($tabRapports[$intCtr]['men_nom']),
'url' => $vDomaine . '/superadm/index.php?t=' . urlencode(base64_encode(fxUnescape($tabRapports[$intCtr]['men_id']))) . '&a=report&lng=' . $strLangue,
'target' => '_blank',
);
}
}
}
// ---- Gestion ----
if (!$blnDon) {
$arrGestion[] = array('label' => afficheTexte('tableau_promoteur_menudossard', 0), 'url' => $strCompte . 'inc_tableau_dossards?promoteur_eve_id=' . $strE . '&lng=' . $strLangue, 'icon' => 'fa-id-card-o');
if (intval($arrItem['qte_modifiable'] ?? 0) == 1) {
$arrGestion[] = array('label' => afficheTexte('tableau_promoteur_menuepreuves', 0), 'url' => $strCompte . 'inc_tableau_epreuves?promoteur_eve_id=' . $strE . '&lng=' . $strLangue, 'icon' => 'fa-pencil-square-o');
}
if (!empty($_SESSION['usa_id'])
|| fxEveAccesHasAnyPermission($intComId, $intEveId, array('dossards.manage', 'epreuves.edit_qte'))) {
$strMenuQte = afficheTexte('tableau_promoteur_menu_gestion_qte_dossards', 0, 0);
if (strpos($strMenuQte, '*tableau_promoteur_menu_gestion_qte_dossards*') !== false) {
$strMenuQte = 'Gestion des quantités et dossards';
}
$arrGestion[] = array('label' => $strMenuQte, 'url' => $strCompte . 'inc_tableau_gestion_epreuves?promoteur_eve_id=' . $strE . '&lng=' . $strLangue, 'icon' => 'fa-pencil-square-o');
}
}
$arrGestion[] = array('label' => afficheTexte('tableau_promoteur_menueinscription', 0), 'url' => $strCompte . 'inc_tableau_inscriptions?promoteur_eve_id=' . $strE . '&lng=' . $strLangue, 'icon' => 'fa-users');
if (function_exists('fxInscrGestionCanAccess') && fxInscrGestionCanAccess($intComId, $intEveId)) {
$arrGestion[] = array('label' => fxInscrGestionPromoteurMenuLabel(), 'url' => $strCompte . 'inc_tableau_inscriptions_gestion?promoteur_eve_id=' . $strE . '&lng=' . $strLangue, 'icon' => 'fa-users');
}
$arrGestion[] = array('label' => afficheTexte('tableau_promoteur_rabais', 0), 'url' => $strCompte . 'inc_tableau_rabais?promoteur_eve_id=' . $strE . '&lng=' . $strLangue, 'icon' => 'fa-money');
if ($intTeId == 13) {
$arrGestion[] = array('label' => afficheTexte('tableau_promoteur_envoie_email_questions', 0), 'url' => $strCompte . 'inc_tableau_mail_questions?promoteur_eve_id=' . $strE . '&lng=' . $strLangue, 'icon' => 'fa-paper-plane');
}
return array('rapports' => $arrRapports, 'gestion' => $arrGestion);
}
function fxPromoteurHubRenderLinkList($arrLinks, $strDefaultIcon)
{
if (empty($arrLinks)) {
return '';
}
$html = '<ul class="list-unstyled mb-0">';
foreach ($arrLinks as $arrLink) {
$strIcon = !empty($arrLink['icon']) ? $arrLink['icon'] : $strDefaultIcon;
$strTarget = !empty($arrLink['target']) ? ' target="' . fxPromoteurHubEsc($arrLink['target']) . '"' : '';
$html .= '<li class="mb-2">';
$html .= '<a class="btn btn-outline-primary btn-block text-left rounded-0" href="' . fxPromoteurHubEsc($arrLink['url']) . '"' . $strTarget . '>';
$html .= '<i class="fa ' . fxPromoteurHubEsc($strIcon) . ' mr-2" aria-hidden="true"></i>' . fxPromoteurHubEsc($arrLink['label']);
$html .= '</a></li>';
}
$html .= '</ul>';
return $html;
}
function fxPromoteurHubRenderEventPanel($arrItem, $strLangue)
{
$strPanelId = 'hub-fn-' . intval($arrItem['eve_id']);
$arrLinks = fxPromoteurHubGetEventLinks($arrItem, $strLangue);
$html = '<div class="collapse promoteur-hub-functions mt-3" id="' . fxPromoteurHubEsc($strPanelId) . '">';
$html .= '<div class="row">';
// Colonne gauche : Gestion (promoteur). Colonne droite : Rapports.
$html .= '<div class="col-12 col-lg-6 mb-3">';
$html .= '<h6 class="text-uppercase text-muted mb-2"><i class="fa fa-cogs mr-2" aria-hidden="true"></i>' . fxPromoteurHubEsc(afficheTexte('promoteur_hub_section_gestion', 0)) . '</h6>';
$html .= fxPromoteurHubRenderLinkList($arrLinks['gestion'], 'fa-list');
$html .= '</div>';
$html .= '<div class="col-12 col-lg-6 mb-3">';
$html .= '<h6 class="text-uppercase text-muted mb-2"><i class="fa fa-file mr-2" aria-hidden="true"></i>' . fxPromoteurHubEsc(afficheTexte('tableau_promoteur_menurapports', 0)) . '</h6>';
$html .= fxPromoteurHubRenderLinkList($arrLinks['rapports'], 'fa-bar-chart');
$html .= '</div>';
$html .= '</div>';
$html .= '</div>';
return $html;
}
/**
* Statistiques par epreuve pour un evenement (vendus / restant / max,
* check-in, liste d'attente, prix courant + date du prochain palier).
* Requetes agregees (1 par metrique) plutot qu'une boucle fxGetQuantites,
* pour rester rapide dans le hub.
*
* Retourne array('epreuves' => [...], 'flash' => [...]) ou null si aucune epreuve.
*/
function fxPromoteurHubGetEpreuveStats($intEveId, $strLangue = 'fr')
{
global $objDatabase;
$intEveId = intval($intEveId);
if ($intEveId <= 0 || !isset($objDatabase)) {
return null;
}
$strLang = in_array($strLangue, array('fr', 'en'), true) ? $strLangue : 'fr';
$strAlt = ($strLang === 'fr') ? 'en' : 'fr';
// ---- Liste des epreuves ----
$sqlEpreuves = "
SELECT epr_id, epr_categorie_fr, epr_categorie_en, epr_type_fr, epr_type_en,
epr_nom_fr, epr_nom_en, epr_date, epr_heure, epr_date_limite,
epr_qte, epr_qte_origine, epr_qte_limitee, epr_equipe, epr_nb_participants
FROM inscriptions_epreuves
WHERE eve_id = " . $intEveId . " AND epr_actif = 1
ORDER BY epr_tri, epr_type_" . $strLang . ", epr_nom_" . $strLang . "
";
$tabEpreuves = $objDatabase->fxGetResults($sqlEpreuves);
if (!is_array($tabEpreuves) || count($tabEpreuves) === 0) {
return null;
}
$arrIds = array();
for ($i = 1; $i <= count($tabEpreuves); $i++) {
$arrIds[] = intval($tabEpreuves[$i]['epr_id']);
}
$strIds = implode(',', $arrIds);
// ---- Vendus / inscrits (confirmes, non annules) ----
$arrVendus = array();
$sqlVendus = "
SELECT p.epr_id AS epr_id, COUNT(DISTINCT e.pec_id_original) AS nb
FROM resultats_epreuves_commandees e
JOIN resultats_participants p
ON p.epr_id = e.epr_id AND p.pec_id = e.pec_id_original
WHERE p.is_cancelled = 0 AND p.epr_id IN (" . $strIds . ")
GROUP BY p.epr_id
";
$tabVendus = $objDatabase->fxGetResults($sqlVendus);
if (is_array($tabVendus)) {
for ($i = 1; $i <= count($tabVendus); $i++) {
$arrVendus[intval($tabVendus[$i]['epr_id'])] = intval($tabVendus[$i]['nb']);
}
}
// ---- Check-in (dossards remis) ----
$arrCheckin = array();
$sqlCheckin = "
SELECT epr_id, COUNT(*) AS nb
FROM resultats_participants
WHERE no_bib_remis = 1 AND epr_id IN (" . $strIds . ")
GROUP BY epr_id
";
$tabCheckin = $objDatabase->fxGetResults($sqlCheckin);
if (is_array($tabCheckin)) {
for ($i = 1; $i <= count($tabCheckin); $i++) {
$arrCheckin[intval($tabCheckin[$i]['epr_id'])] = intval($tabCheckin[$i]['nb']);
}
}
// ---- Liste d'attente : nombre + statut actif ----
$arrAttenteNb = array();
$sqlAttente = "
SELECT epr_id, COUNT(1) AS nb
FROM inscriptions_liste_attente_participants
WHERE (lispar_statut = 1 OR lispar_statut = 2) AND epr_id IN (" . $strIds . ")
GROUP BY epr_id
";
$tabAttente = $objDatabase->fxGetResults($sqlAttente);
if (is_array($tabAttente)) {
for ($i = 1; $i <= count($tabAttente); $i++) {
$arrAttenteNb[intval($tabAttente[$i]['epr_id'])] = intval($tabAttente[$i]['nb']);
}
}
$arrAttenteActive = array();
$sqlAttenteActive = "
SELECT epr_id, lis_active
FROM inscriptions_liste_attente
WHERE epr_id IN (" . $strIds . ")
";
$tabAttenteActive = $objDatabase->fxGetResults($sqlAttenteActive);
if (is_array($tabAttenteActive)) {
for ($i = 1; $i <= count($tabAttenteActive); $i++) {
$arrAttenteActive[intval($tabAttenteActive[$i]['epr_id'])] = intval($tabAttenteActive[$i]['lis_active']);
}
}
// ---- Prix (paliers publics) : prix courant + date du prochain palier ----
$arrTiers = array();
$sqlPrix = "
SELECT epr_id, ep_montant, ep_montant_affiche, ep_date_limite
FROM inscriptions_epreuves_prix
WHERE ep_prive = 0 AND epr_id IN (" . $strIds . ")
ORDER BY epr_id, ep_date_limite ASC
";
$tabPrix = $objDatabase->fxGetResults($sqlPrix);
if (is_array($tabPrix)) {
for ($i = 1; $i <= count($tabPrix); $i++) {
$intEpr = intval($tabPrix[$i]['epr_id']);
$fltMontant = (floatval($tabPrix[$i]['ep_montant_affiche']) > 0)
? floatval($tabPrix[$i]['ep_montant_affiche'])
: floatval($tabPrix[$i]['ep_montant']);
if (!isset($arrTiers[$intEpr])) {
$arrTiers[$intEpr] = array();
}
$arrTiers[$intEpr][] = array(
'montant' => $fltMontant,
'date_limite' => trim((string)($tabPrix[$i]['ep_date_limite'] ?? '')),
);
}
}
$strToday = date('Y-m-d');
// ---- Assemblage par epreuve + agregat flash ----
$arrEpreuves = array();
$intFlashVendus = 0;
$intFlashMax = 0;
$intFlashAttente = 0;
$intFlashCompletes = 0;
$intFlashPresque = 0;
$fltFlashRevenus = 0.0;
$strFlashProchaineLimite = '';
for ($i = 1; $i <= count($tabEpreuves); $i++) {
$row = $tabEpreuves[$i];
$intEpr = intval($row['epr_id']);
$strTitre = trim((string)($row['epr_type_' . $strLang] ?? ''));
if ($strTitre === '') {
$strTitre = trim((string)($row['epr_type_' . $strAlt] ?? ''));
}
$strNom = trim((string)($row['epr_nom_' . $strLang] ?? ''));
if ($strNom === '') {
$strNom = trim((string)($row['epr_nom_' . $strAlt] ?? ''));
}
$strCategorie = trim((string)($row['epr_categorie_' . $strLang] ?? ''));
if ($strTitre === '') {
$strTitre = ($strNom !== '') ? $strNom : ('#' . $intEpr);
}
$intVendus = $arrVendus[$intEpr] ?? 0;
$intRestant = intval($row['epr_qte']);
$blnIllimitee = (intval($row['epr_qte_limitee']) !== 1);
// Max courant = places vendues + restantes ; origine = max initial fixe.
$intMax = $intVendus + $intRestant;
$intOrigine = intval($row['epr_qte_origine']);
$fltPct = ($blnIllimitee || $intMax <= 0) ? 0 : round(($intVendus / $intMax) * 100);
if ($fltPct > 100) {
$fltPct = 100;
}
// Etat de remplissage : complete / presque / ok / illimitee
if ($blnIllimitee) {
$strEtat = 'illimitee';
} elseif ($intRestant <= 0) {
$strEtat = 'complete';
$intFlashCompletes++;
} elseif ($fltPct >= 90) {
$strEtat = 'presque';
$intFlashPresque++;
} elseif ($fltPct >= 75) {
$strEtat = 'attention';
} else {
$strEtat = 'ok';
}
// Prix courant + date du prochain palier
$fltPrixCourant = null;
$strPrixDateFin = '';
$fltPrixProchain = null;
if (!empty($arrTiers[$intEpr])) {
$arrEprTiers = $arrTiers[$intEpr];
$intIdxCourant = -1;
foreach ($arrEprTiers as $intIdx => $arrTier) {
if ($arrTier['date_limite'] !== '' && $arrTier['date_limite'] >= $strToday) {
$intIdxCourant = $intIdx;
break;
}
}
if ($intIdxCourant === -1) {
// Tous les paliers sont passes : on prend le dernier (prix final), pas de prochain.
$intIdxCourant = count($arrEprTiers) - 1;
}
$fltPrixCourant = $arrEprTiers[$intIdxCourant]['montant'];
$strPrixDateFin = $arrEprTiers[$intIdxCourant]['date_limite'];
if (isset($arrEprTiers[$intIdxCourant + 1])) {
$fltPrixProchain = $arrEprTiers[$intIdxCourant + 1]['montant'];
}
}
$intAttenteNb = $arrAttenteNb[$intEpr] ?? 0;
$blnAttenteActive = !empty($arrAttenteActive[$intEpr]);
// Agregat flash
$intFlashVendus += $intVendus;
if (!$blnIllimitee) {
$intFlashMax += $intMax;
}
$intFlashAttente += $intAttenteNb;
if ($fltPrixCourant !== null) {
$fltFlashRevenus += $fltPrixCourant * $intVendus;
}
// Prochaine date limite d'inscription (la plus proche, dans le futur)
$strLimiteInscr = trim((string)($row['epr_date_limite'] ?? ''));
if ($strLimiteInscr !== '' && strpos($strLimiteInscr, '0000') !== 0 && $strLimiteInscr >= $strToday) {
if ($strFlashProchaineLimite === '' || $strLimiteInscr < $strFlashProchaineLimite) {
$strFlashProchaineLimite = $strLimiteInscr;
}
}
$arrEpreuves[] = array(
'epr_id' => $intEpr,
'titre' => $strTitre,
'nom' => $strNom,
'categorie' => $strCategorie,
'date' => trim((string)($row['epr_date'] ?? '')),
'heure' => trim((string)($row['epr_heure'] ?? '')),
'date_limite_inscr' => $strLimiteInscr,
'equipe' => (intval($row['epr_equipe']) === 1),
'nb_participants' => intval($row['epr_nb_participants']),
'vendus' => $intVendus,
'restant' => $intRestant,
'max' => $intMax,
'origine' => $intOrigine,
'illimitee' => $blnIllimitee,
'pct' => $fltPct,
'etat' => $strEtat,
'checkin' => $arrCheckin[$intEpr] ?? 0,
'attente_nb' => $intAttenteNb,
'attente_active' => $blnAttenteActive,
'prix_courant' => $fltPrixCourant,
'prix_date_fin' => $strPrixDateFin,
'prix_prochain' => $fltPrixProchain,
);
}
$intPctGlobal = ($intFlashMax > 0) ? round(($intFlashVendus / $intFlashMax) * 100) : 0;
if ($intPctGlobal > 100) {
$intPctGlobal = 100;
}
$arrFlash = array(
'total_vendus' => $intFlashVendus,
'total_max' => $intFlashMax,
'total_restant' => max(0, $intFlashMax - $intFlashVendus),
'pct_global' => $intPctGlobal,
'revenus' => $fltFlashRevenus,
'nb_epreuves' => count($arrEpreuves),
'nb_completes' => $intFlashCompletes,
'nb_presque' => $intFlashPresque,
'total_attente' => $intFlashAttente,
'prochaine_date_limite' => $strFlashProchaineLimite,
);
return array('epreuves' => $arrEpreuves, 'flash' => $arrFlash);
}
/**
* Rendu de la ligne resume (bande flash + badges d'alerte). Toujours visible.
*/
function fxPromoteurHubRenderFlash($arrStats, $strLangue = 'fr')
{
if (empty($arrStats) || empty($arrStats['epreuves'])) {
return '';
}
$blnFr = ($strLangue !== 'en');
$arrFlash = $arrStats['flash'];
$lbl = function ($fr, $en) use ($blnFr) {
return $blnFr ? $fr : $en;
};
$html = '';
// ---- Bande flash ----
$html .= '<div class="promoteur-hub-flash">';
$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 .= '</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 .= '</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 .= '</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 .= '</div>';
}
$html .= '</div>'; // .promoteur-hub-flash
// ---- Badges d'alerte ----
$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'));
}
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'));
}
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'));
}
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));
}
if (!empty($arrBadges)) {
$html .= '<div class="promoteur-hub-badges">';
foreach ($arrBadges as $arrBadge) {
$html .= '<span class="phub-badge ' . $arrBadge['cls'] . '"><i class="fa ' . $arrBadge['icon'] . ' mr-1" aria-hidden="true"></i>'
. fxPromoteurHubEsc($arrBadge['txt']) . '</span>';
}
$html .= '</div>';
}
return $html;
}
/**
* Rendu de la grille de tuiles par epreuve (contenu du collapse « Tableau de bord »).
*/
function fxPromoteurHubRenderEpreuvesGrid($arrStats, $strLangue = 'fr')
{
if (empty($arrStats) || empty($arrStats['epreuves'])) {
return '';
}
$blnFr = ($strLangue !== 'en');
// ---- Tuiles par epreuve ----
$html = '<div class="promoteur-hub-epreuves-grid">';
foreach ($arrStats['epreuves'] as $arrEpr) {
$strEtat = $arrEpr['etat'];
$html .= '<div class="phub-epr-tile phub-etat-' . fxPromoteurHubEsc($strEtat) . '">';
// En-tete : titre + categorie
$html .= '<div class="phub-epr-head">';
$html .= '<span class="phub-epr-titre">' . fxPromoteurHubEsc($arrEpr['titre']) . '</span>';
if ($arrEpr['categorie'] !== '') {
$html .= '<span class="phub-epr-cat">' . fxPromoteurHubEsc($arrEpr['categorie']) . '</span>';
}
if ($arrEpr['nom'] !== '' && $arrEpr['nom'] !== $arrEpr['titre']) {
$html .= '<span class="phub-epr-sub">' . fxPromoteurHubEsc($arrEpr['nom']) . '</span>';
}
$html .= '</div>';
// Gros chiffre : nombre de dossards vendus
$html .= '<div class="phub-epr-big">';
$html .= '<span class="phub-epr-bignum">' . intval($arrEpr['vendus']) . '</span>';
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 .= '</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>';
} 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>';
}
// 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') . '">'
. '<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') . '">'
. '<i class="fa fa-arrow-up mr-1" aria-hidden="true"></i>'
. fxPromoteurHubEsc(fxShowPrix($arrEpr['prix_prochain'], $strLangue))
. ' ' . fxPromoteurHubEsc($blnFr ? 'dès' : 'on') . ' '
. 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') . '">'
. '<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') . '">'
. '<i class="fa fa-hourglass-half mr-1" aria-hidden="true"></i>' . fxPromoteurHubEsc($strAtt) . '</span>';
}
if ($arrEpr['date'] !== '' && strpos($arrEpr['date'], '0000') !== 0) {
$html .= '<span class="phub-chip phub-chip-date"><i class="fa fa-calendar-o mr-1" aria-hidden="true"></i>'
. fxShowDate($arrEpr['date'], $strLangue, 1) . '</span>';
}
$html .= '</div>'; // .phub-epr-chips
$html .= '</div>'; // .phub-epr-tile
}
$html .= '</div>'; // .promoteur-hub-epreuves-grid
return $html;
}
function fxPromoteurHubRenderEventCard($arrItem, $strLangue, $blnArchived = false)
{
$strCardClass = 'card promoteur-hub-card mb-3' . ($blnArchived ? ' promoteur-hub-card-archived' : '');
$blnFr = ($strLangue !== 'en');
$intEveId = intval($arrItem['eve_id']);
// Stats par epreuve : flash (resume, toujours visible) + grille (dans le collapse).
$arrStats = fxPromoteurHubGetEpreuveStats($intEveId, $strLangue);
$strFlash = fxPromoteurHubRenderFlash($arrStats, $strLangue);
$strGrid = fxPromoteurHubRenderEpreuvesGrid($arrStats, $strLangue);
$blnHasStats = ($strGrid !== '');
$blnFonctions = ($arrItem['url_promoteur'] !== '');
$strEprId = 'hub-epr-' . $intEveId;
$strFnId = 'hub-fn-' . $intEveId;
$html = '<div class="' . fxPromoteurHubEsc($strCardClass) . '">';
$html .= '<div class="card-body">';
// ---- Haut : identite (gauche) + resume flash (droite) ----
$html .= '<div class="row promoteur-hub-card-row">';
$html .= '<div class="' . ($strFlash !== '' ? 'col-12 col-lg-5' : 'col-12') . ' promoteur-hub-card-main">';
$html .= '<div class="d-flex align-items-center flex-wrap">';
$html .= '<div class="promoteur-hub-card-icon mr-3"><img src="' . fxPromoteurHubEsc($arrItem['icon']) . '" width="64" alt=""></div>';
$html .= '<div class="flex-grow-1">';
$html .= '<h2 class="h5 mb-1 promoteur-hub-event-title">' . fxPromoteurHubEsc($arrItem['nom']) . '</h2>';
if ($arrItem['date_label'] !== '') {
$html .= '<div class="text-muted small"><i class="fa fa-calendar-o mr-1" aria-hidden="true"></i>' . fxPromoteurHubEsc($arrItem['date_label']) . '</div>';
}
if ($arrItem['lieu'] !== '') {
$html .= '<div class="text-muted small"><i class="fa fa-map-marker mr-1" aria-hidden="true"></i>' . fxPromoteurHubEsc($arrItem['lieu']) . '</div>';
}
if (!empty($arrItem['roles'])) {
$html .= '<div class="small mt-1 text-muted">' . fxPromoteurHubEsc(implode(' · ', $arrItem['roles'])) . '</div>';
}
$html .= '</div></div>';
$html .= '</div>'; // .promoteur-hub-card-main
if ($strFlash !== '') {
$html .= '<div class="col-12 col-lg-7 promoteur-hub-card-flash">' . $strFlash . '</div>';
}
$html .= '</div>'; // .promoteur-hub-card-row
// ---- Boutons d'action (pleine largeur) ----
if ($blnHasStats || $blnFonctions) {
$html .= '<div class="promoteur-hub-actions mt-3">';
if ($blnHasStats) {
$html .= '<button type="button" class="btn btn-primary rounded-pill mr-2 mb-2 promoteur-hub-toggle collapsed" data-toggle="collapse" data-target="#' . fxPromoteurHubEsc($strEprId) . '" aria-expanded="false" aria-controls="' . fxPromoteurHubEsc($strEprId) . '">';
$html .= '<i class="fa fa-tachometer mr-2" aria-hidden="true"></i>' . fxPromoteurHubEsc(afficheTexte('promoteur_hub_btn_tableau', 0));
$html .= '<i class="fa fa-chevron-down ml-2 promoteur-hub-toggle-caret" aria-hidden="true"></i>';
$html .= '</button>';
}
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-chevron-down ml-2 promoteur-hub-toggle-caret" aria-hidden="true"></i>';
$html .= '</button>';
}
$html .= '</div>'; // .promoteur-hub-actions
}
// ---- Collapse : tuiles par epreuve (pleine largeur) ----
if ($blnHasStats) {
$html .= '<div class="collapse promoteur-hub-epreuves-collapse mt-2" id="' . fxPromoteurHubEsc($strEprId) . '">' . $strGrid . '</div>';
}
// ---- Collapse : menu des fonctionnalites (gestion + rapports) ----
if ($blnFonctions) {
$html .= fxPromoteurHubRenderEventPanel($arrItem, $strLangue);
}
$html .= '</div></div>';
return $html;
}
function fxPromoteurHubRenderEventSection($arrItems, $strTitleClef, $strLangue, $blnArchived = false)
{
if (empty($arrItems)) {
return '';
}
$html = '<h2 class="h4 mt-4 mb-3 promoteur-hub-section-title">' . fxPromoteurHubEsc(afficheTexte($strTitleClef, 0)) . '</h2>';
$html .= '<div class="promoteur promoteur-hub-events">';
foreach ($arrItems as $arrItem) {
$html .= fxPromoteurHubRenderEventCard($arrItem, $strLangue, $blnArchived);
}
$html .= '</div>';
return $html;
}
function fxShowPromoteurHub($intComId, $strLangue = 'fr')
{
$arrBuckets = fxPromoteurHubGetEvents(intval($intComId), $strLangue);
$html = '';
$html .= '<div class="promoteur-hub-toolbar mb-4 d-flex flex-wrap align-items-center justify-content-between">';
$html .= '<p class="mb-2 mb-md-0 text-muted">' . fxPromoteurHubEsc(afficheTexte('promoteur_hub_intro', 0)) . '</p>';
$html .= '<a class="btn btn-outline-secondary rounded-pill" href="' . fxPromoteurHubEsc(fxPromoteurHubParticipantUrl($strLangue)) . '">';
$html .= '<i class="fa fa-user mr-2" aria-hidden="true"></i>' . fxPromoteurHubEsc(afficheTexte('promoteur_hub_link_participant', 0));
$html .= '</a>';
$html .= '</div>';
// Annonces : uniquement si le module doc a reellement des pages (sinon pas de "?" mort)
if (function_exists('fxDocRenderModule')) {
$strDocModule = fxDocRenderModule('promoteur_hub_annonces');
if ($strDocModule !== '') {
$html .= '<div class="promoteur-hub-annonces alert alert-info d-flex align-items-center mb-4">';
$html .= '<div class="flex-grow-1 mr-2">' . fxPromoteurHubEsc(afficheTexte('promoteur_hub_annonces_titre', 0)) . '</div>';
$html .= fxDocRenderTrigger('promoteur_hub_annonces');
$html .= $strDocModule;
$html .= '</div>';
}
}
$html .= fxPromoteurHubRenderEventSection($arrBuckets['highlight'], 'promoteur_hub_section_highlight', $strLangue, false);
$html .= fxPromoteurHubRenderEventSection($arrBuckets['archived'], 'promoteur_hub_section_archived', $strLangue, true);
if (empty($arrBuckets['highlight']) && empty($arrBuckets['archived'])) {
$html .= '<div class="alert alert-warning">' . fxPromoteurHubEsc(afficheTexte('promoteur_hub_empty', 0)) . '</div>';
}
// Accordeon par carte : ouvrir « Tableau de bord » ferme « Menu des fonctionnalites » et vice versa.
$html .= '<script>(function ($) {'
. 'if (!$) { return; }'
. '$(document).on("show.bs.collapse", ".promoteur-hub-epreuves-collapse, .promoteur-hub-functions", function () {'
. 'var $card = $(this).closest(".promoteur-hub-card");'
. '$card.find(".promoteur-hub-epreuves-collapse.show, .promoteur-hub-functions.show").not(this).collapse("hide");'
. '});'
. '})(window.jQuery);</script>';
return $html;
}
function fxShowPromoteurHubMenuLink($arrCompteClient, $strLangue)
{
global $vDomaine;
if (!fxPromoteurHubShouldUse(intval($arrCompteClient['com_id'] ?? 0))) {
return '';
}
$arrIds = array_unique(array_filter(array_map('intval', array_merge(
fxPromoteurHubGetLegacyEventIds(intval($arrCompteClient['com_id'])),
fxEveAccesGetEventIds(intval($arrCompteClient['com_id']))
)), function ($v) {
return $v > 0;
}));
$intNb = count($arrIds);
$strNb = '';
if ($intNb > 0) {
$strNb = '<small>(' . $intNb . ' ';
$strNb .= ($intNb > 1) ? afficheTexte('compte_evenement_pluriel', 0) : afficheTexte('compte_evenement_singulier', 0);
$strNb .= ')</small>';
}
return '
<li class="nav-item">
<a class="nav-list" href="' . fxPromoteurHubEsc(fxPromoteurHubUrl($strLangue)) . '">
<i class="fa fa-tachometer" aria-hidden="true"></i> ' . fxPromoteurHubEsc(afficheTexte('promoteur_hub_menu', 0)) . $strNb . '
</a>
</li>
';
}