Files
ms1inscription-v5/php/inc_fx_promoteur_hub.php

1813 lines
73 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* Hub promoteur — MSIN-4401
* Liste / droits du hub = accès V2 uniquement (inscriptions_eve_acces).
* Legacy (com_eve_promoteur) reste dans le menu « Info promoteur », pas dans le hub.
*/
require_once __DIR__ . '/inc_fx_eve_acces.php';
// Seuil de quantite a partir duquel une epreuve est consideree "illimitee"
// (ancien usage : plafond geant pour suivre les ventes sans gerer la limite).
if (!defined('PHUB_SEUIL_ILLIMITE')) {
define('PHUB_SEUIL_ILLIMITE', 10000);
}
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;
}
/** Hub v2 disponible (acces v2 + page CMS) — pour lien menu, pas pour forcer /compte. */
function fxPromoteurHubIsAvailable($intComId)
{
return fxPromoteurHubComUsesV2(intval($intComId)) && fxPromoteurHubPageExists();
}
/**
* Atterrissage par défaut sur le hub v2 (/compte, redirect après login public).
* Compte avec accès v2 + page hub installée → tableau de bord promoteur.
* Le toggle promoteur_legacy_actif ne s'applique qu'aux droits com_eve_promoteur (fxIsPromoteur), pas ici.
*/
function fxPromoteurHubShouldUse($intComId)
{
return fxPromoteurHubIsAvailable($intComId);
}
function fxPromoteurHubUrl($strLangue = 'fr', $intEveId = 0)
{
global $vDomaine;
$strUrl = $vDomaine . ($strLangue === 'fr' ? '/compte/promoteur_hub' : '/account/promoteur_hub');
// MSIN-4399 — Deep-link : hub_eve=id pour rouvrir la carte / menu de l'événement.
$intEveId = intval($intEveId);
if ($intEveId > 0) {
$strUrl .= '?hub_eve=' . $intEveId;
}
return $strUrl;
}
/**
* MSIN-4399 — Retour « tableau promoteur » depuis un menu V2 :
* hub + focus eve si dispo, sinon ancien inc_tableau_promoteur.
*/
function fxPromoteurHubOrLegacyBackUrl($intEveId, $strLangue = 'fr')
{
global $vDomaine;
$strLangue = ($strLangue === 'en') ? 'en' : 'fr';
$intEveId = intval($intEveId);
$intComId = intval($_SESSION['com_id'] ?? 0);
if ($intComId > 0 && fxPromoteurHubIsAvailable($intComId)) {
return fxPromoteurHubUrl($strLangue, $intEveId);
}
$strPage = ($strLangue === 'en') ? '/account' : '/compte';
return $vDomaine . $strPage . '/inc_tableau_promoteur'
. '?promoteur_eve_id=' . urlencode(base64_encode((string)$intEveId));
}
/**
* MSIN-4399 — eve_id à ouvrir automatiquement sur le hub (?hub_eve= ou promoteur_eve_id).
*/
function fxPromoteurHubParseFocusEveId()
{
if (!empty($_GET['hub_eve'])) {
return max(0, intval($_GET['hub_eve']));
}
if (!empty($_GET['promoteur_eve_id'])) {
$mix = $_GET['promoteur_eve_id'];
$decoded = base64_decode(urldecode((string)$mix), true);
if ($decoded !== false && $decoded !== '' && ctype_digit((string)$decoded)) {
return intval($decoded);
}
if (ctype_digit((string)$mix)) {
return intval($mix);
}
}
return 0;
}
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()
{
// MSIN-4401 — pages hub + détails V2 (plus d'anciennes clés API)
return array(
'hub.dashboard',
'inscriptions_gestion.view',
'inscriptions_gestion.qr_scan',
'inscriptions_gestion.statut_edit',
'inscriptions_gestion.bib_edit',
'inscriptions_gestion.bib_remis',
'inscriptions_gestion.edit',
'inscriptions_gestion.cancel',
'inscriptions_gestion.restore',
'inscriptions_gestion.refund',
'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',
);
}
/**
* Accès web promoteur pour un événement : Legacy (com_eve_promoteur) OU permissions V2.
* Utilisé hors hub (ex. modes paiement promoteur). Le hub lui-même ne passe plus par Legacy.
*/
function fxPromoteurHubCanAccessPromoteurWeb($intComId, $intEveId, $arrLegacyFlip = null)
{
if (is_array($arrLegacyFlip) && isset($arrLegacyFlip[intval($intEveId)])) {
return true;
}
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());
}
/**
* MSIN-4401 — Accès menu fonctionnalités hub : V2 uniquement (pas de bypass Legacy).
* Prérequis : $arrV2EventFlip, $arrWebPermFlip préchargés.
*/
function fxPromoteurHubCanAccessPromoteurWebBatch($intComId, $intEveId, $arrV2EventFlip, $arrWebPermFlip)
{
$intEveId = intval($intEveId);
if (!isset($arrV2EventFlip[$intEveId])) {
return false;
}
return isset($arrWebPermFlip[$intEveId]);
}
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)
{
if (!function_exists('fxPromoteurLegacySiteEnabled')) {
require_once __DIR__ . '/inc_fonctions.php';
}
if (!fxPromoteurLegacySiteEnabled()) {
return array();
}
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();
// MSIN-4401 — Hub V2 : uniquement les événements avec accès V2 (pas de fantômes Legacy).
$arrV2EventIds = fxEveAccesGetEventIds(intval($intComId));
$arrIds = array_values(array_unique(array_filter(array_map('intval', $arrV2EventIds), 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'));
$arrV2EventFlip = array_flip($arrIds);
$arrWebPermFlip = array_flip(fxEveAccesGetEventIdsWithAnyPermission(intval($intComId), fxPromoteurHubPermKeysWeb()));
if (!function_exists('fxInscrGestionGetPromoteurEveIds')) {
require_once __DIR__ . '/inc_fx_inscriptions_gestion.php';
}
$arrInscrGestionFlip = array_flip(array_map('intval', fxInscrGestionGetPromoteurEveIds(intval($intComId))));
$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' => fxPromoteurHubCanAccessPromoteurWebBatch($intComId, $intEveId, $arrV2EventFlip, $arrWebPermFlip)
? fxPromoteurHubPromoteurTableUrl($intEveId, $strLang)
: '',
'url_mobile' => '',
);
if (isset($arrInscrGestionFlip[$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');
}
/** Page info / i18n hub promoteur — toujours compte.php (y compris AJAX). */
function fxPromoteurHubInfoPage()
{
return 'compte.php';
}
/**
* Lit info_texte depuis $vtexte_page ou la BD (compte.php / global).
* N'écrit jamais dans info — évite les doublons info_prg=ajax_promoteur_hub.php.
*/
function fxPromoteurHubGetInfoRow($clef)
{
global $vtexte_page, $objDatabase, $strLangue;
if (!empty($vtexte_page[$clef])) {
return $vtexte_page[$clef];
}
static $hubInfoCache = array();
if (array_key_exists($clef, $hubInfoCache)) {
return $hubInfoCache[$clef];
}
if (!isset($objDatabase)) {
$hubInfoCache[$clef] = array();
return $hubInfoCache[$clef];
}
$strLang = !empty($strLangue) ? $strLangue : 'fr';
$strPage = fxPromoteurHubInfoPage();
$sql = "SELECT info_texte, info_aide, info_prg FROM info
WHERE info_actif = 1
AND info_clef = '" . $objDatabase->fxEscape($clef) . "'
AND info_langue = '" . $objDatabase->fxEscape($strLang) . "'
AND info_prg IN ('" . $strPage . "', 'global')
ORDER BY FIELD(info_prg, '" . $strPage . "', 'global')
LIMIT 1";
$row = $objDatabase->fxGetRow($sql);
if (!is_array($row) || empty($row)) {
// Entrées legacy créées par erreur sous ajax_promoteur_hub.php — lecture seule.
$sqlLegacy = "SELECT info_texte, info_aide, info_prg FROM info
WHERE info_actif = 1
AND info_clef = '" . $objDatabase->fxEscape($clef) . "'
AND info_langue = '" . $objDatabase->fxEscape($strLang) . "'
AND info_prg = 'ajax_promoteur_hub.php'
LIMIT 1";
$row = $objDatabase->fxGetRow($sqlLegacy);
}
$hubInfoCache[$clef] = is_array($row) ? $row : array();
return $hubInfoCache[$clef];
}
/** Repli FR/EN si clé absente ou mal saisie en BD (menu fonctionnalités AJAX). */
function fxPromoteurHubStaticFallback($clef, $strLangParam = '')
{
if ($strLangParam === '') {
global $strLangue;
$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'),
'tableau_promoteur_menuepreuves' => array('fr' => 'Épreuves', 'en' => 'Races'),
'tableau_promoteur_menu_gestion_qte_dossards' => array('fr' => 'Gestion des quantités et dossards', 'en' => 'Quantities and bib management'),
'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'),
// MSIN-4451 — total = somme des inscriptions par épreuve (pas d'humains uniques)
'promoteur_hub_flash_inscrits' => array('fr' => 'épreuves vendues', 'en' => 'entries sold'),
'promoteur_hub_flash_participants' => array('fr' => 'participants', 'en' => 'participants'),
'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] ?? '';
}
/**
* Libellé hub — sur compte.php délègue à afficheTexte ; en AJAX, lecture seule.
*/
function fxPromoteurHubTexte($clef, $mem_echo = 0)
{
if (!defined('MS1_PROMOTEUR_HUB_AJAX') || !MS1_PROMOTEUR_HUB_AJAX) {
return afficheTexte($clef, $mem_echo);
}
$row = fxPromoteurHubGetInfoRow($clef);
$str = trim($row['info_texte'] ?? '');
if ($str === '' || $str === $clef || $str === '*' . $clef . '*') {
$str = fxPromoteurHubStaticFallback($clef);
if ($str === '') {
$str = $clef;
}
}
if ($mem_echo == 1) {
echo $str;
}
return $str;
}
/**
* Liste des fonctions d'un evenement (groupes : rapports + gestion),
* fidele a inc_tableau_promoteur.php.
*/
/**
* MSIN-4401 — liens hub :
* - Super admin / kit total (grants_all) → tout
* - Accès V2 présent → menus selon les pages du kit (prioritaire sur legacy)
* - Legacy seul (com_eve_promoteur, sans V2) → menus complets comme avant
*/
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';
if (!function_exists('fxEveAccesHasAnyPermission')) {
require_once __DIR__ . '/inc_fx_eve_acces.php';
}
if (!function_exists('fxIsPromoteur')) {
require_once __DIR__ . '/inc_fonctions.php';
}
$blnSuper = !empty($_SESSION['usa_id']);
$blnHasV2 = fxEveAccesComHasV2ForEvent($intComId, $intEveId);
$blnLegacyPromoteur = fxIsPromoteur($intComId, $intEveId);
// Menus « tout ouvert » legacy seulement si PAS de kit V2 sur cet événement
$blnLegacyMenus = $blnLegacyPromoteur && !$blnHasV2;
$blnGrantsAll = $blnHasV2 && fxEveAccesComEventHasGrantsAllKit($intComId, $intEveId);
$fnCan = function ($arrKeys) use ($intComId, $intEveId, $blnSuper, $blnLegacyMenus, $blnGrantsAll) {
if ($blnSuper || $blnLegacyMenus || $blnGrantsAll) {
return true;
}
return fxEveAccesHasAnyPermission($intComId, $intEveId, $arrKeys);
};
$arrRapports = array();
$arrGestion = array();
// ---- Rapports (niveau 1 V2 : chrono / finances / paiements) ----
if ($intTeId != 9 && $fnCan(array('reports.chrono'))) {
$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' => 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' => 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' => 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' => 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' => fxPromoteurHubTexte('promoteur_hub_rapport_adhesions', 0),
'url' => $strRapport . 'adhesions&e=' . $strE . '&lng=' . $strLangue,
'target' => '_blank',
);
}
if ($fnCan(array('reports.adhesions_sales'))) {
$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' => 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' => 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')) {
$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 ----
// MSIN-4399 — Entrées V2 en tête de liste, puis legacy / autres.
if (!$blnDon && $fnCan(array('dossards.manage', 'epreuves.edit_qte'))) {
$strMenuQte = fxPromoteurHubTexte('tableau_promoteur_menu_gestion_qte_dossards', 0);
$arrGestion[] = array('label' => $strMenuQte, 'url' => $strCompte . 'inc_tableau_gestion_epreuves?promoteur_eve_id=' . $strE . '&lng=' . $strLangue, 'icon' => 'fa-pencil-square-o');
}
if (!function_exists('fxInscrGestionCanAccess')) {
require_once __DIR__ . '/inc_fx_inscriptions_gestion.php';
}
if (fxInscrGestionCanAccess($intComId, $intEveId)) {
$arrGestion[] = array('label' => fxInscrGestionPromoteurMenuLabel(), 'url' => $strCompte . 'inc_tableau_inscriptions_gestion?promoteur_eve_id=' . $strE . '&lng=' . $strLangue, 'icon' => 'fa-users');
}
if (!function_exists('fxEveAccesCanTeamInvite')) {
require_once __DIR__ . '/inc_fx_eve_acces.php';
}
if (!function_exists('fxEveAccesPromoteurMenuLabel')) {
require_once __DIR__ . '/inc_fx_eve_acces_promoteur.php';
}
if (fxEveAccesCanTeamInvite($intComId, $intEveId)) {
$arrGestion[] = array(
'label' => fxEveAccesPromoteurMenuLabel($strLangue),
'url' => $strCompte . 'inc_tableau_eve_acces?promoteur_eve_id=' . $strE . '&lng=' . $strLangue,
'icon' => 'fa-key',
);
}
/*
* MSIN-4465 — Menus hub legacy masqués (pages PHP toujours là).
* Pour réactiver : décommenter le bloc ci-dessous.
* - Dossards → inc_tableau_dossards
* - Quantités → inc_tableau_epreuves (si qte_modifiable)
* - Inscriptions V1 → inc_tableau_inscriptions
*
if (!$blnDon) {
if ($blnLegacyMenus || $blnSuper || $blnGrantsAll) {
$arrGestion[] = array('label' => fxPromoteurHubTexte('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' => fxPromoteurHubTexte('tableau_promoteur_menuepreuves', 0), 'url' => $strCompte . 'inc_tableau_epreuves?promoteur_eve_id=' . $strE . '&lng=' . $strLangue, 'icon' => 'fa-pencil-square-o');
}
}
}
if ($blnLegacyMenus || $blnSuper || $blnGrantsAll) {
$arrGestion[] = array('label' => fxPromoteurHubTexte('tableau_promoteur_menueinscription', 0), 'url' => $strCompte . 'inc_tableau_inscriptions?promoteur_eve_id=' . $strE . '&lng=' . $strLangue, 'icon' => 'fa-users');
}
*/
if ($fnCan(array('rabais.view', 'rabais.manage'))) {
$arrGestion[] = array('label' => fxPromoteurHubTexte('tableau_promoteur_rabais', 0), 'url' => $strCompte . 'inc_tableau_rabais?promoteur_eve_id=' . $strE . '&lng=' . $strLangue, 'icon' => 'fa-money');
}
if ($intTeId == 13 && ($blnLegacyMenus || $blnSuper || $blnGrantsAll)) {
$arrGestion[] = array('label' => fxPromoteurHubTexte('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']) . '"' : '';
// MSIN-4473 — coins alignés sur les tuiles outils dossards (border-radius 8px)
$html .= '<li class="mb-2">';
$html .= '<a class="btn btn-outline-primary btn-block text-left promoteur-hub-link" 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;
}
/** MSIN-4401 — le menu fonctionnalités a-t-il au moins un lien (gestion ou rapports) ? */
function fxPromoteurHubEventHasFeatureLinks($arrItem, $strLangue)
{
$arrLinks = fxPromoteurHubGetEventLinks($arrItem, $strLangue);
return !empty($arrLinks['gestion']) || !empty($arrLinks['rapports']);
}
function fxPromoteurHubRenderEventPanelContent($arrItem, $strLangue)
{
$arrLinks = fxPromoteurHubGetEventLinks($arrItem, $strLangue);
$blnGestion = !empty($arrLinks['gestion']);
$blnRapports = !empty($arrLinks['rapports']);
// MSIN-4401 — pas d'en-têtes vides si aucun droit dans la section
if (!$blnGestion && !$blnRapports) {
return '<p class="text-muted small mb-0">'
. fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_no_features', 0))
. '</p>';
}
$html = '<div class="row">';
if ($blnGestion) {
$strCol = $blnRapports ? 'col-12 col-lg-6 mb-3' : 'col-12 mb-3';
$html .= '<div class="' . $strCol . '">';
$html .= '<h6 class="text-uppercase text-muted mb-2"><i class="fa fa-cogs mr-2" aria-hidden="true"></i>'
. fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_section_gestion', 0)) . '</h6>';
$html .= fxPromoteurHubRenderLinkList($arrLinks['gestion'], 'fa-list');
$html .= '</div>';
}
if ($blnRapports) {
$strCol = $blnGestion ? 'col-12 col-lg-6 mb-3' : 'col-12 mb-3';
$html .= '<div class="' . $strCol . '">';
$html .= '<h6 class="text-uppercase text-muted mb-2"><i class="fa fa-file mr-2" aria-hidden="true"></i>'
. fxPromoteurHubEsc(fxPromoteurHubTexte('tableau_promoteur_menurapports', 0)) . '</h6>';
$html .= fxPromoteurHubRenderLinkList($arrLinks['rapports'], 'fa-bar-chart');
$html .= '</div>';
}
$html .= '</div>';
return $html;
}
/** Coquille du menu fonctionnalités — contenu chargé en AJAX au premier clic. */
function fxPromoteurHubRenderEventPanelShell($arrItem, $strLangue)
{
$strPanelId = 'hub-fn-' . intval($arrItem['eve_id']);
$intEveId = intval($arrItem['eve_id']);
$strLoadMsg = fxPromoteurHubTexte('promoteur_hub_load_menu', 0);
global $vDomaine;
$strRunnerSrc = $vDomaine . '/images/ms1-runner-loader.gif?v=' . _VERSION_CODE;
$html = '<div class="collapse promoteur-hub-functions mt-3" id="' . fxPromoteurHubEsc($strPanelId) . '"'
. ' data-hub-eve-id="' . $intEveId . '" data-hub-lang="' . fxPromoteurHubEsc($strLangue) . '">';
$html .= '<div class="promoteur-hub-links-placeholder ms1-loader ms1-loader--inline" role="status" aria-live="polite" aria-busy="true">'
. '<img class="ms1-loader__runner" src="' . fxPromoteurHubEsc($strRunnerSrc) . '" alt="" role="presentation" decoding="async">'
. '<span class="ms1-loader__label">' . fxPromoteurHubEsc($strLoadMsg) . '</span></div>';
$html .= '</div>';
return $html;
}
function fxPromoteurHubRenderEventPanel($arrItem, $strLangue)
{
$strPanelId = 'hub-fn-' . intval($arrItem['eve_id']);
$html = '<div class="collapse promoteur-hub-functions mt-3" id="' . fxPromoteurHubEsc($strPanelId) . '">';
$html .= fxPromoteurHubRenderEventPanelContent($arrItem, $strLangue);
$html .= '</div>';
return $html;
}
/** Métadonnées événement pour le menu AJAX (1 requête légère). */
function fxPromoteurHubGetEventItemForLinks($intComId, $intEveId)
{
global $objDatabase;
$intEveId = intval($intEveId);
if ($intEveId <= 0 || !isset($objDatabase)) {
return null;
}
$row = $objDatabase->fxGetRow(
'SELECT eve_id, te_id, eve_qte_modifiable, eve_rapport_finances_capitaines
FROM inscriptions_evenements
WHERE eve_id = ' . $intEveId . ' AND eve_actif = 1'
);
if ($row === null) {
return null;
}
return array(
'eve_id' => intval($row['eve_id']),
'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),
);
}
/**
* 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']);
}
}
// MSIN-4419 — Participations (1 humain × 1 épreuve = 1). Même personne sur 5 épreuves = 5.
// Pas de dédup par courriel (contrairement à MSIN-4451).
$intFlashParticipants = 0;
$sqlParticipants = "
SELECT COUNT(*) 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 . ")
";
$tabParticipants = $objDatabase->fxGetResults($sqlParticipants);
if (is_array($tabParticipants) && isset($tabParticipants[1]['nb'])) {
$intFlashParticipants = intval($tabParticipants[1]['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']);
// Max courant = places vendues + restantes ; origine = max initial fixe.
$intMax = $intVendus + $intRestant;
$intOrigine = intval($row['epr_qte_origine']);
// Ancien usage : un plafond "geant" (>= 10000) servait de faux maximum pour
// suivre les ventes sans gerer la limite. On le traite comme illimite.
$blnIllimitee = (intval($row['epr_qte_limitee']) !== 1)
|| $intMax >= PHUB_SEUIL_ILLIMITE
|| $intOrigine >= PHUB_SEUIL_ILLIMITE;
$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_participants' => $intFlashParticipants,
'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 '';
}
$arrFlash = $arrStats['flash'];
$html = '';
// ---- Bande flash ----
$html .= '<div class="promoteur-hub-flash">';
// MSIN-4451 / MSIN-4419 — Duo : épreuves vendues (KPI) + participations (sous-ligne)
$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(fxPromoteurHubTexte('promoteur_hub_flash_inscrits', 0)) . '</span>';
$html .= '<span class="phub-flash-sub">'
. intval($arrFlash['total_participants']) . ' '
. fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_flash_participants', 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(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(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(fxPromoteurHubTexte('promoteur_hub_flash_revenus', 0)) . '</span>';
$html .= '</div>';
}
$html .= '</div>'; // .promoteur-hub-flash
// ---- Badges d'alerte (MSIN-4452 : texte seul, sans icône) ----
$arrBadges = array();
if (intval($arrFlash['nb_completes']) > 0) {
$arrBadges[] = array('cls' => 'phub-badge-danger',
'txt' => intval($arrFlash['nb_completes']) . ' ' . fxPromoteurHubTexte('promoteur_hub_badge_completes', 0));
}
if (intval($arrFlash['nb_presque']) > 0) {
$arrBadges[] = array('cls' => 'phub-badge-warn',
'txt' => intval($arrFlash['nb_presque']) . ' ' . fxPromoteurHubTexte('promoteur_hub_badge_presque', 0));
}
if (intval($arrFlash['total_attente']) > 0) {
$arrBadges[] = array('cls' => 'phub-badge-info',
'txt' => intval($arrFlash['total_attente']) . ' ' . fxPromoteurHubTexte('promoteur_hub_badge_attente', 0));
}
if ($arrFlash['prochaine_date_limite'] !== '') {
$arrBadges[] = array('cls' => 'phub-badge-soft',
'txt' => fxPromoteurHubTexte('promoteur_hub_badge_limite', 0) . ' ' . 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'] . '">'
. 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 '';
}
// ---- 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(fxPromoteurHubTexte('promoteur_hub_epreuves_title', 0)) . '</h6>';
// ---- 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(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(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(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(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(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(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(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
? 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>';
}
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;
}
/**
* Produits a vendre (avec quantites) pour un evenement.
* Meme logique que superadm/check_qte_produits.php :
* restant = total (pro_qte) - vendus (commandes non annulees) - panier (paniers incomplets).
*
* Retourne un tableau de produits ou null si l'evenement n'a aucun produit actif.
*/
function fxPromoteurHubGetProduits($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';
$sql = "
SELECT p1.pro_id, p1.pro_nom_fr, p1.pro_nom_en, p1.pro_qte AS dispo,
COALESCE(v.vendus, 0) AS vendus,
COALESCE(pa.panier, 0) AS panier
FROM inscriptions_produits_new p1
LEFT JOIN (
SELECT p2.pro_id, COUNT(*) AS vendus
FROM resultats_produits_new p2
JOIN resultats_epreuves_commandees c
ON c.pec_id_original = p2.pec_id AND c.is_cancelled = 0
JOIN inscriptions_produits_new px
ON px.pro_id = p2.pro_id AND px.eve_id = " . $intEveId . "
GROUP BY p2.pro_id
) v ON v.pro_id = p1.pro_id
LEFT JOIN (
SELECT p3.pro_id, COUNT(*) AS panier
FROM inscriptions_panier_produits_new p3
JOIN inscriptions_panier_acheteurs a
ON a.no_panier = p3.no_panier AND a.sta_id = 1
JOIN inscriptions_produits_new px
ON px.pro_id = p3.pro_id AND px.eve_id = " . $intEveId . "
GROUP BY p3.pro_id
) pa ON pa.pro_id = p1.pro_id
WHERE p1.eve_id = " . $intEveId . " AND p1.pro_actif = 1
ORDER BY p1.pro_nom_" . $strLang . ", p1.pro_nom_fr
";
$tabProduits = $objDatabase->fxGetResults($sql);
if (!is_array($tabProduits) || count($tabProduits) === 0) {
return null;
}
$arrProduits = array();
for ($i = 1; $i <= count($tabProduits); $i++) {
$row = $tabProduits[$i];
$strNom = trim((string)($row['pro_nom_' . $strLang] ?? ''));
if ($strNom === '') {
$strNom = trim((string)($row['pro_nom_' . $strAlt] ?? ''));
}
$intTotal = intval($row['dispo']);
$intVendus = intval($row['vendus']);
$intPanier = intval($row['panier']);
$intRestant = $intTotal - $intVendus - $intPanier;
if ($intRestant < 0) {
$intRestant = 0;
}
// Ancien usage : un plafond "geant" (>= 10000) sert de faux maximum
// pour suivre les ventes sans gerer la limite -> illimite.
$blnIllimite = ($intTotal >= PHUB_SEUIL_ILLIMITE);
$arrProduits[] = array(
'pro_id' => intval($row['pro_id']),
'nom' => ($strNom !== '') ? $strNom : ('#' . intval($row['pro_id'])),
'total' => $intTotal,
'vendus' => $intVendus,
'panier' => $intPanier,
'restant' => $intRestant,
'illimite' => $blnIllimite,
);
}
return $arrProduits;
}
/**
* Rendu de la section « Produits en vente » (liste produits + quantites).
* MSIN-4454 — Gros chiffre = vendus / total ; pastille = disponibles.
* N'affiche rien si aucun produit.
*/
function fxPromoteurHubRenderProduits($arrProduits, $strLangue = 'fr')
{
if (empty($arrProduits)) {
return '';
}
$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(fxPromoteurHubTexte('promoteur_hub_produits_title', 0)) . '</h6>';
$html .= '<div class="promoteur-hub-produits-grid">';
foreach ($arrProduits as $arrProd) {
$blnIllimite = !empty($arrProd['illimite']);
$blnEpuise = (!$blnIllimite && intval($arrProd['restant']) <= 0);
$strState = $blnIllimite ? 'illimitee' : ($blnEpuise ? 'complete' : 'ok');
$html .= '<div class="phub-prod-tile phub-etat-' . $strState . '">';
$html .= '<span class="phub-prod-nom">' . fxPromoteurHubEsc($arrProd['nom']) . '</span>';
// MSIN-4454 — Gros chiffre : vendus (aligné sur les tuiles épreuves)
$html .= '<div class="phub-prod-big">';
$html .= '<span class="phub-prod-num">' . intval($arrProd['vendus']) . '</span>';
if (!$blnIllimite) {
$html .= '<span class="phub-prod-tot">/ ' . intval($arrProd['total']) . '</span>';
}
$html .= '<span class="phub-prod-lbl">' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_vendus_lbl', 0)) . '</span>';
$html .= '</div>';
$html .= '<div class="phub-epr-chips">';
$html .= '<span class="phub-chip phub-chip-checkin" title="' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_disponible', 0)) . '">';
if ($blnIllimite) {
$html .= '<span class="phub-infini mr-1" aria-hidden="true">&infin;</span>';
} else {
$html .= intval($arrProd['restant']) . ' ';
}
$html .= fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_disponible', 0)) . '</span>';
if (intval($arrProd['panier']) > 0) {
$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(fxPromoteurHubTexte('promoteur_hub_paniers_lbl', 0)) . '</span>';
}
$html .= '</div>'; // .phub-epr-chips
$html .= '</div>'; // .phub-prod-tile
}
$html .= '</div>'; // .promoteur-hub-produits-grid
$html .= '</div>'; // .promoteur-hub-produits
return $html;
}
/** Stats lourdes (resultats_*) pour une carte hub — utilisé par AJAX au clic. */
function fxPromoteurHubRenderEventStatsBundle($intEveId, $strLangue = 'fr')
{
$arrStats = fxPromoteurHubGetEpreuveStats(intval($intEveId), $strLangue);
$arrProduits = fxPromoteurHubGetProduits(intval($intEveId), $strLangue);
return array(
'flash' => fxPromoteurHubRenderFlash($arrStats, $strLangue),
'html' => fxPromoteurHubRenderEpreuvesGrid($arrStats, $strLangue)
. fxPromoteurHubRenderProduits($arrProduits, $strLangue),
);
}
/** MSIN-4401 — Accès hub à cet événement : V2 uniquement (pas Legacy). */
function fxPromoteurHubComHasEvent($intComId, $intEveId)
{
$intEveId = intval($intEveId);
if ($intComId <= 0 || $intEveId <= 0) {
return false;
}
return fxEveAccesComHasV2ForEvent(intval($intComId), $intEveId);
}
/**
* MSIN-4401 — droit de voir le Tableau de bord (chiffres hub).
* Super admin / kit total / legacy seul → oui ; sinon hub.dashboard.
*/
function fxPromoteurHubCanViewDashboard($intComId, $intEveId)
{
if (!empty($_SESSION['usa_id'])) {
return true;
}
$intComId = intval($intComId);
$intEveId = intval($intEveId);
if ($intComId <= 0 || $intEveId <= 0) {
return false;
}
if (!function_exists('fxEveAccesHasPermission')) {
require_once __DIR__ . '/inc_fx_eve_acces.php';
}
if (!function_exists('fxIsPromoteur')) {
require_once __DIR__ . '/inc_fonctions.php';
}
if (fxEveAccesComEventHasGrantsAllKit($intComId, $intEveId)) {
return true;
}
$blnHasV2 = fxEveAccesComHasV2ForEvent($intComId, $intEveId);
if (!$blnHasV2 && fxIsPromoteur($intComId, $intEveId)) {
return true;
}
return fxEveAccesHasPermission($intComId, $intEveId, 'hub.dashboard');
}
function fxPromoteurHubRenderEventCard($arrItem, $strLangue, $blnArchived = false)
{
$strCardClass = 'card promoteur-hub-card mb-3' . ($blnArchived ? ' promoteur-hub-card-archived' : '');
$intEveId = intval($arrItem['eve_id']);
$intComId = intval($arrItem['com_id'] ?? ($_SESSION['com_id'] ?? 0));
// Stats reportées au clic (AJAX) : N événements × requêtes resultats_* = timeout avec données prod.
$strFlash = '';
$strGrid = '';
$strProduits = '';
$blnHasStats = !$blnArchived && fxPromoteurHubCanViewDashboard($intComId, $intEveId);
// MSIN-4401 — bouton menu seulement s'il y a au moins un lien (pas d'en-têtes vides)
$blnFonctions = fxPromoteurHubEventHasFeatureLinks($arrItem, $strLangue);
$strEprId = 'hub-epr-' . $intEveId;
$strFnId = 'hub-fn-' . $intEveId;
$html = '<div class="' . fxPromoteurHubEsc($strCardClass) . '" id="hub-eve-' . $intEveId . '"'
. ' data-hub-eve-id="' . $intEveId . '">';
$html .= '<div class="card-body">';
// ---- Haut : identite (gauche) + resume flash (droite) ----
$html .= '<div class="row promoteur-hub-card-row">';
$html .= '<div class="' . ($blnHasStats || $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 ($blnHasStats) {
$html .= '<div class="col-12 col-lg-7 promoteur-hub-card-flash-wrap"></div>';
} elseif ($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(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>';
}
$html .= '</div>'; // .promoteur-hub-actions
}
// ---- Collapse : stats chargées au clic (ajax_promoteur_hub.php) ----
if ($blnHasStats) {
global $vDomaine;
$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) . '">';
$html .= '<div class="promoteur-hub-stats-placeholder ms1-loader ms1-loader--inline" role="status" aria-live="polite" aria-busy="true">'
. '<img class="ms1-loader__runner" src="' . fxPromoteurHubEsc($strRunnerSrc) . '" alt="" role="presentation" decoding="async">'
. '<span class="ms1-loader__label">' . fxPromoteurHubEsc($strLoadMsg) . '</span></div>';
$html .= '</div>';
} elseif ($strGrid !== '' || $strProduits !== '') {
$html .= '<div class="collapse promoteur-hub-epreuves-collapse mt-2" id="' . fxPromoteurHubEsc($strEprId) . '">' . $strGrid . $strProduits . '</div>';
}
// ---- Collapse : menu des fonctionnalites (gestion + rapports) — chargé en AJAX ----
if ($blnFonctions) {
$html .= fxPromoteurHubRenderEventPanelShell($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')
{
if (function_exists('fxV2RegisterScript')) {
fxV2RegisterScript('promoteur-hub');
}
$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>';
}
return $html;
}
/**
* MSIN-4401 — Bouton retour hub V2 depuis l'espace client (miroir de « Mon espace client MS1 »).
*/
function fxShowPromoteurHubEspaceClientToolbar($intComId, $strLangue = 'fr')
{
if (!fxPromoteurHubIsAvailable(intval($intComId))) {
return '';
}
$strLabel = afficheTexte('promoteur_hub_link_dashboard', 0);
if ($strLabel === '' || $strLabel === 'promoteur_hub_link_dashboard' || $strLabel === '*promoteur_hub_link_dashboard*') {
$strLabel = fxPromoteurHubStaticFallback('promoteur_hub_link_dashboard', $strLangue);
}
$html = '<div class="promoteur-hub-toolbar mb-4 d-flex flex-wrap align-items-center justify-content-end">';
$html .= '<a class="btn btn-outline-primary rounded-pill" href="' . fxPromoteurHubEsc(fxPromoteurHubUrl($strLangue)) . '">';
$html .= '<i class="fa fa-tachometer mr-2" aria-hidden="true"></i>' . fxPromoteurHubEsc($strLabel);
$html .= '</a>';
$html .= '</div>';
return $html;
}
function fxShowPromoteurHubMenuLink($arrCompteClient, $strLangue)
{
global $vDomaine;
if (!fxPromoteurHubIsAvailable(intval($arrCompteClient['com_id'] ?? 0))) {
return '';
}
// MSIN-4401 — compteur menu hub = événements V2 seulement
$arrIds = array_unique(array_filter(array_map('intval', 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>';
}
$strLabel = afficheTexte('promoteur_hub_menu', 0);
if ($strLabel === '' || $strLabel === 'promoteur_hub_menu' || $strLabel === '*promoteur_hub_menu*') {
$strLabel = fxPromoteurHubStaticFallback('promoteur_hub_menu', $strLangue);
}
return '
<li class="nav-item">
<a class="nav-list" href="' . fxPromoteurHubEsc(fxPromoteurHubUrl($strLangue)) . '">
<i class="fa fa-tachometer" aria-hidden="true"></i> ' . fxPromoteurHubEsc($strLabel) . $strNb . '
</a>
</li>
';
}