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'), // Hotfix hub qte=0 — filet si Info absente (épuisé même si « illimitée »). 'promoteur_hub_plus_disponible' => array('fr' => 'Plus disponible', 'en' => 'No longer available'), '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; // MSIN-4484 — kit total (role_grants_all) : menus hors $fnCan (rapports custom, mail questions…) $blnGrantsAll = fxEveAccesComEventHasGrantsAllKit($intComId, $intEveId); // MSIN-4484 — kit total : passer par HasAnyPermission (exclut perm_grants_all = 0). $fnCan = function ($arrKeys) use ($intComId, $intEveId, $blnSuper, $blnLegacyMenus) { if ($blnSuper || $blnLegacyMenus) { return true; } return fxEveAccesHasAnyPermission($intComId, $intEveId, $arrKeys); }; $arrRapports = array(); $arrGestion = array(); // MSIN-4482 — hub promoteur : pas de section Rapports si « Sans inscription en ligne » (pour l’instant) if (!function_exists('fxImportResultatsTeIdSansInscriptionEnLigne')) { require_once __DIR__ . '/inc_fx_import_resultats.php'; } $intTeSansInscr = intval(fxImportResultatsTeIdSansInscriptionEnLigne()); $blnSansInscrEnLigne = ($intTeSansInscr > 0 && $intTeId === $intTeSansInscr); // ---- Rapports (niveau 1 V2 : chrono / finances / paiements) ---- if (!$blnSansInscrEnLigne && $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 (!$blnSansInscrEnLigne && $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 (!$blnSansInscrEnLigne && $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 (!$blnSansInscrEnLigne && 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 (!$blnSansInscrEnLigne && $fnCan(array('reports.payments'))) { $arrRapports[] = array( 'label' => fxPromoteurHubTexte('promoteur_hub_rapport_paiements', 0), 'url' => $strRapport . 'remboursements&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank', ); } if (!$blnSansInscrEnLigne && ($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'); } */ // MSIN-4540 — pas de rabais sans inscription / paiement en ligne. if (!$blnSansInscrEnLigne && $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 = ''; 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 '

' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_no_features', 0)) . '

'; } $html = '
'; if ($blnGestion) { $strCol = $blnRapports ? 'col-12 col-lg-6 mb-3' : 'col-12 mb-3'; $html .= '
'; $html .= '
' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_section_gestion', 0)) . '
'; $html .= fxPromoteurHubRenderLinkList($arrLinks['gestion'], 'fa-list'); $html .= '
'; } if ($blnRapports) { $strCol = $blnGestion ? 'col-12 col-lg-6 mb-3' : 'col-12 mb-3'; $html .= '
'; $html .= '
' . fxPromoteurHubEsc(fxPromoteurHubTexte('tableau_promoteur_menurapports', 0)) . '
'; $html .= fxPromoteurHubRenderLinkList($arrLinks['rapports'], 'fa-bar-chart'); $html .= '
'; } $html .= '
'; 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 = '
'; $html .= ''; $html .= '
'; return $html; } function fxPromoteurHubRenderEventPanel($arrItem, $strLangue) { $strPanelId = 'hub-fn-' . intval($arrItem['eve_id']); $html = '
'; $html .= fxPromoteurHubRenderEventPanelContent($arrItem, $strLangue); $html .= '
'; 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; // Hotfix hub qte=0 — epr_qte=0 = plus en vente (comme le site public), jamais ∞. if ($intRestant <= 0) { $blnIllimitee = false; } $fltPct = ($blnIllimitee || $intMax <= 0) ? 0 : round(($intVendus / $intMax) * 100); if ($fltPct > 100) { $fltPct = 100; } // Etat de remplissage : complete / presque / ok / illimitee // Hotfix hub qte=0 — tester l'épuisement AVANT « illimitee ». if ($intRestant <= 0) { $strEtat = 'complete'; $intFlashCompletes++; } elseif ($blnIllimitee) { $strEtat = 'illimitee'; } 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) // Hotfix hub qte=0 — ignorer les épreuves déjà épuisées. $strLimiteInscr = trim((string)($row['epr_date_limite'] ?? '')); if ($intRestant > 0 && $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 .= '
'; // MSIN-4451 / MSIN-4419 — Duo : épreuves vendues (KPI) + participations (sous-ligne) $html .= '
'; $html .= '' . intval($arrFlash['total_vendus']) . ''; $html .= '' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_flash_inscrits', 0)) . ''; $html .= '' . intval($arrFlash['total_participants']) . ' ' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_flash_participants', 0)) . ''; $html .= '
'; if (intval($arrFlash['total_max']) > 0) { $html .= '
'; $html .= '' . intval($arrFlash['pct_global']) . '%'; $html .= '' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_flash_rempli', 0)) . ''; $html .= '
'; $html .= '
'; $html .= '' . intval($arrFlash['total_restant']) . ''; $html .= '' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_flash_places', 0)) . ''; $html .= '
'; } if (floatval($arrFlash['revenus']) > 0) { $html .= '
'; $html .= '' . fxPromoteurHubEsc(fxShowPrix($arrFlash['revenus'], $strLangue)) . ''; $html .= '' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_flash_revenus', 0)) . ''; $html .= '
'; } $html .= '
'; // .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 .= '
'; foreach ($arrBadges as $arrBadge) { $html .= '' . fxPromoteurHubEsc($arrBadge['txt']) . ''; } $html .= '
'; } 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 = '
' . '' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_epreuves_title', 0)) . '
'; // ---- Tuiles par epreuve ---- $html .= '
'; foreach ($arrStats['epreuves'] as $arrEpr) { $strEtat = $arrEpr['etat']; $html .= '
'; // En-tete : titre + categorie $html .= '
'; $html .= '' . fxPromoteurHubEsc($arrEpr['titre']) . ''; if ($arrEpr['categorie'] !== '') { $html .= '' . fxPromoteurHubEsc($arrEpr['categorie']) . ''; } if ($arrEpr['nom'] !== '' && $arrEpr['nom'] !== $arrEpr['titre']) { $html .= '' . fxPromoteurHubEsc($arrEpr['nom']) . ''; } $html .= '
'; // Gros chiffre : nombre de dossards vendus $html .= '
'; $html .= '' . intval($arrEpr['vendus']) . ''; if (!$arrEpr['illimitee']) { $html .= '/ ' . intval($arrEpr['max']) . ''; } $html .= '' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_bibs_sold', 0)) . ''; $html .= '
'; // Barre de progression / état stock // Hotfix hub qte=0 — épuisé : libellé clair (pas ∞, pas « 0 restant »). $blnEpuiseEpr = ($arrEpr['etat'] === 'complete' || intval($arrEpr['restant']) <= 0); if ($blnEpuiseEpr) { if (intval($arrEpr['max']) > 0) { $html .= '
'; } $html .= '
' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_plus_disponible', 0)) . '
'; } elseif (!$arrEpr['illimitee'] && intval($arrEpr['max']) > 0) { $html .= '
'; $html .= '
' . intval($arrEpr['pct']) . '% · ' . intval($arrEpr['restant']) . ' ' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_restant', 0)) . '
'; } else { $html .= '
' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_unlimited', 0)) . '
'; } // Pastilles secondaires $html .= '
'; if ($arrEpr['prix_courant'] !== null) { $html .= '' . '' . fxPromoteurHubEsc(fxShowPrix($arrEpr['prix_courant'], $strLangue)) . ''; // Hotfix hub qte=0 — pas de hausse de prix annoncée si plus rien à vendre. if (!$blnEpuiseEpr && $arrEpr['prix_prochain'] !== null && $arrEpr['prix_date_fin'] !== '' && $arrEpr['prix_date_fin'] >= date('Y-m-d')) { $html .= '' . '' . fxPromoteurHubEsc(fxShowPrix($arrEpr['prix_prochain'], $strLangue)) . ' ' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_des', 0)) . ' ' . fxShowDate($arrEpr['prix_date_fin'], $strLangue, 1) . ''; } } if (intval($arrEpr['checkin']) > 0) { $html .= '' . '' . intval($arrEpr['checkin']) . ''; } 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 .= '' . '' . fxPromoteurHubEsc($strAtt) . ''; } if ($arrEpr['date'] !== '' && strpos($arrEpr['date'], '0000') !== 0) { $html .= '' . fxShowDate($arrEpr['date'], $strLangue, 1) . ''; } $html .= '
'; // .phub-epr-chips $html .= '
'; // .phub-epr-tile } $html .= '
'; // .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, p1.pro_date_limite, 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); // MSIN-4454 — fin de vente (vide / 0000-00-00 = sans limite) $strDateLimite = trim((string)($row['pro_date_limite'] ?? '')); if ($strDateLimite === '' || strpos($strDateLimite, '0000-00-00') === 0) { $strDateLimite = ''; } else { $strDateLimite = substr($strDateLimite, 0, 10); } $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, 'date_limite' => $strDateLimite, ); } return $arrProduits; } /** * Rendu de la section « Produits en vente » (liste produits + quantites). * MSIN-4454 — Gros chiffre = vendus / total ; pastille = disponibles ; date fin vente. * N'affiche rien si aucun produit. */ function fxPromoteurHubRenderProduits($arrProduits, $strLangue = 'fr') { if (empty($arrProduits)) { return ''; } $html = '
'; $html .= '
' . '' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_produits_title', 0)) . '
'; $html .= '
'; foreach ($arrProduits as $arrProd) { $blnIllimite = !empty($arrProd['illimite']); $blnEpuise = (!$blnIllimite && intval($arrProd['restant']) <= 0); $strState = $blnIllimite ? 'illimitee' : ($blnEpuise ? 'complete' : 'ok'); $strDateLimite = trim((string)($arrProd['date_limite'] ?? '')); $strLblLimite = fxPromoteurHubTexte('promoteur_hub_badge_limite', 0); $html .= '
'; $html .= '' . fxPromoteurHubEsc($arrProd['nom']) . ''; // MSIN-4454 — Gros chiffre : vendus (aligné sur les tuiles épreuves) $html .= '
'; $html .= '' . intval($arrProd['vendus']) . ''; if (!$blnIllimite) { $html .= '/ ' . intval($arrProd['total']) . ''; } $html .= '' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_vendus_lbl', 0)) . ''; $html .= '
'; $html .= '
'; $html .= ''; if ($blnIllimite) { $html .= ''; } else { $html .= intval($arrProd['restant']) . ' '; } $html .= fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_disponible', 0)) . ''; if (intval($arrProd['panier']) > 0) { $html .= '' . '' . intval($arrProd['panier']) . ' ' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_paniers_lbl', 0)) . ''; } // MSIN-4454 — date fin de vente (pro_date_limite) ; clé Info déjà en place if ($strDateLimite !== '') { $html .= '' . '' . fxPromoteurHubEsc($strLblLimite) . ' ' . fxShowDate($strDateLimite, $strLangue, 1) . ''; } $html .= '
'; // .phub-epr-chips $html .= '
'; // .phub-prod-tile } $html .= '
'; // .promoteur-hub-produits-grid $html .= '
'; // .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'; } // MSIN-4484 — kit total respecté via HasPermission (perm_grants_all). if (fxEveAccesHasPermission($intComId, $intEveId, 'hub.dashboard')) { return true; } $blnHasV2 = fxEveAccesComHasV2ForEvent($intComId, $intEveId); if (!$blnHasV2 && fxIsPromoteur($intComId, $intEveId)) { return true; } return false; } 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 = '
'; $html .= '
'; // ---- Haut : identite (gauche) + resume flash (droite) ---- $html .= '
'; $html .= '
'; $html .= '
'; $html .= '
'; $html .= '
'; $html .= '

' . fxPromoteurHubEsc($arrItem['nom']) . '

'; if ($arrItem['date_label'] !== '') { $html .= '
' . fxPromoteurHubEsc($arrItem['date_label']) . '
'; } if ($arrItem['lieu'] !== '') { $html .= '
' . fxPromoteurHubEsc($arrItem['lieu']) . '
'; } if (!empty($arrItem['roles'])) { $html .= '
' . fxPromoteurHubEsc(implode(' · ', $arrItem['roles'])) . '
'; } $html .= '
'; $html .= '
'; // .promoteur-hub-card-main if ($blnHasStats) { $html .= '
'; } elseif ($strFlash !== '') { $html .= '
' . $strFlash . '
'; } $html .= '
'; // .promoteur-hub-card-row // ---- Boutons d'action (pleine largeur) ---- if ($blnHasStats || $blnFonctions) { $html .= '
'; if ($blnHasStats) { $html .= ''; } if ($blnFonctions) { $html .= ''; } $html .= '
'; // .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 .= '
'; $html .= '
' . '' . '' . fxPromoteurHubEsc($strLoadMsg) . '
'; $html .= '
'; } elseif ($strGrid !== '' || $strProduits !== '') { $html .= '
' . $strGrid . $strProduits . '
'; } // ---- Collapse : menu des fonctionnalites (gestion + rapports) — chargé en AJAX ---- if ($blnFonctions) { $html .= fxPromoteurHubRenderEventPanelShell($arrItem, $strLangue); } $html .= '
'; return $html; } function fxPromoteurHubRenderEventSection($arrItems, $strTitleClef, $strLangue, $blnArchived = false) { if (empty($arrItems)) { return ''; } $html = '

' . fxPromoteurHubEsc(afficheTexte($strTitleClef, 0)) . '

'; $html .= '
'; foreach ($arrItems as $arrItem) { $html .= fxPromoteurHubRenderEventCard($arrItem, $strLangue, $blnArchived); } $html .= '
'; return $html; } function fxShowPromoteurHub($intComId, $strLangue = 'fr') { if (function_exists('fxV2RegisterScript')) { fxV2RegisterScript('promoteur-hub'); } $arrBuckets = fxPromoteurHubGetEvents(intval($intComId), $strLangue); $html = ''; $html .= '
'; $html .= '

' . fxPromoteurHubEsc(afficheTexte('promoteur_hub_intro', 0)) . '

'; $html .= ''; $html .= '' . fxPromoteurHubEsc(afficheTexte('promoteur_hub_link_participant', 0)); $html .= ''; $html .= '
'; // 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 .= '
'; $html .= '
' . fxPromoteurHubEsc(afficheTexte('promoteur_hub_annonces_titre', 0)) . '
'; $html .= fxDocRenderTrigger('promoteur_hub_annonces'); $html .= $strDocModule; $html .= '
'; } } $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 .= '
' . fxPromoteurHubEsc(afficheTexte('promoteur_hub_empty', 0)) . '
'; } 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 = '
'; $html .= ''; $html .= '' . fxPromoteurHubEsc($strLabel); $html .= ''; $html .= '
'; 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 = '(' . $intNb . ' '; $strNb .= ($intNb > 1) ? afficheTexte('compte_evenement_pluriel', 0) : afficheTexte('compte_evenement_singulier', 0); $strNb .= ')'; } $strLabel = afficheTexte('promoteur_hub_menu', 0); if ($strLabel === '' || $strLabel === 'promoteur_hub_menu' || $strLabel === '*promoteur_hub_menu*') { $strLabel = fxPromoteurHubStaticFallback('promoteur_hub_menu', $strLangue); } return ' '; }