Files
ms1inscription-v5/php/inc_fx_promoteur_hub.php
stephan 4cf2ef98d0 Enhance fxPromoteurHubGetEventLinks function to conditionally load access management and update version code to 4.72.746
This commit modifies the `fxPromoteurHubGetEventLinks` function to include a conditional check for the existence of the `fxInscrGestionCanAccess` function. If it does not exist, the `inc_fx_inscriptions_gestion.php` file is loaded to evaluate access rights. This change improves the modularity and functionality of the promoteur hub by ensuring that access management is properly handled in AJAX contexts.
2026-07-08 09:50:09 -04:00

1516 lines
59 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 — comptes migres acces evenement v2 uniquement.
* Legacy (com_eve_promoteur) : comportement inchange, pas de 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')
{
global $vDomaine;
return $vDomaine . ($strLangue === 'fr' ? '/compte/promoteur_hub' : '/account/promoteur_hub');
}
function fxPromoteurHubParticipantUrl($strLangue = 'fr')
{
global $vDomaine;
return $vDomaine . ($strLangue === 'fr' ? '/compte/accueil' : '/account/accueil');
}
function fxPromoteurHubPromoteurTableUrl($intEveId, $strLangue = 'fr')
{
global $vDomaine;
$strBase = ($strLangue === 'fr') ? '/compte/inc_tableau_promoteur' : '/account/inc_tableau_promoteur';
return $vDomaine . $strBase . '?promoteur_eve_id=' . urlencode(base64_encode(intval($intEveId)));
}
function fxPromoteurHubPermKeysWeb()
{
return array(
'inscriptions.view',
'inscriptions.edit',
'dossards.view',
'dossards.manage',
'epreuves.view',
'epreuves.edit_qte',
'rabais.view',
'rabais.manage',
'emails.send',
'reports.chrono',
'reports.finances',
'reports.finances_membership',
'reports.finances_events',
'reports.payments',
'reports.adhesions',
'reports.adhesions_sales',
'reports.finances_captains',
'reports.custom',
'team.view',
'team.invite',
'team.manage',
);
}
function fxPromoteurHubCanAccessPromoteurWeb($intComId, $intEveId, $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());
}
/**
* Contrôle d'accès promoteur web en lot (évite N requêtes eve_acces par carte).
* Prérequis : $arrLegacyFlip, $arrV2EventFlip, $arrWebPermFlip préchargés.
*/
function fxPromoteurHubCanAccessPromoteurWebBatch($intComId, $intEveId, $arrLegacyFlip, $arrV2EventFlip, $arrWebPermFlip)
{
$intEveId = intval($intEveId);
if (isset($arrLegacyFlip[$intEveId])) {
return true;
}
if (!function_exists('fxIsPromoteur')) {
require_once __DIR__ . '/inc_fonctions.php';
}
if (fxIsPromoteur(intval($intComId), $intEveId)) {
return true;
}
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();
// Union : evenements legacy (com_eve_promoteur) + acces v2
$arrLegacyIds = fxPromoteurHubGetLegacyEventIds(intval($intComId));
$arrV2EventIds = fxEveAccesGetEventIds(intval($intComId));
$arrEventIds = array_merge($arrLegacyIds, $arrV2EventIds);
if (empty($arrEventIds)) {
return array('highlight' => $arrHighlight, 'archived' => $arrArchived);
}
$arrIds = array_values(array_unique(array_filter(array_map('intval', $arrEventIds), function ($v) {
return $v > 0;
})));
if (empty($arrIds)) {
return array('highlight' => $arrHighlight, 'archived' => $arrArchived);
}
$strLang = in_array($strLangue, array('fr', 'en'), true) ? $strLangue : 'fr';
$strAlt = ($strLang === 'fr') ? 'en' : 'fr';
$arrRoles = fxPromoteurHubGetRoleLabelsByEvent(intval($intComId));
$intToday = strtotime(date('Y-m-d'));
$arrLegacyFlip = array_flip(array_map('intval', $arrLegacyIds));
$arrV2EventFlip = array_flip(array_map('intval', $arrV2EventIds));
$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, $arrLegacyFlip, $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';
$tab = array(
'promoteur_hub_section_gestion' => array('fr' => 'Gestion', 'en' => 'Management'),
'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'),
);
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.
*/
function fxPromoteurHubGetEventLinks($arrItem, $strLangue)
{
global $vDomaine, $objDatabase;
$intEveId = intval($arrItem['eve_id']);
$intComId = intval($arrItem['com_id'] ?? ($_SESSION['com_id'] ?? 0));
$intTeId = intval($arrItem['te_id'] ?? 0);
$blnDon = ($intTeId == 9);
$strE = urlencode(base64_encode($intEveId));
$strRapport = $vDomaine . '/superadm/rapport_new.php?r=';
$strCompte = $vDomaine . ($strLangue === 'fr' ? '/compte/' : '/account/');
$year = date('Y');
$strDateTq = $year . '-04-01 00:00:00';
$arrRapports = array();
$arrGestion = array();
// ---- Rapports ----
if ($intTeId != 9) {
$arrRapports[] = array('label' => 'chrono + questions', 'url' => $strRapport . 'chrono&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank');
}
if ($intTeId == 13) {
$arrRapports[] = array('label' => 'Transactions-Finances-Tout', 'url' => $strRapport . 'finances-membership&e=' . $strE . '&lng=' . $strLangue . '&a-ach_maj_from=' . urlencode($strDateTq) . '&btn_search=', 'target' => '_blank');
} else {
$arrRapports[] = array('label' => 'Transactions-Finances', 'url' => $strRapport . 'finances&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank');
}
if ($intTeId == 13) {
$arrRapports[] = array('label' => 'Transactions-Finances-Adhésions Seul', 'url' => $strRapport . 'finances-membership-tq&e=' . $strE . '&lng=' . $strLangue . '&a-ach_maj_from=' . urlencode($strDateTq) . '&btn_search=', 'target' => '_blank');
$arrRapports[] = array('label' => 'Transactions-Finances-Événements Seul', 'url' => $strRapport . 'finances-membership-autre&e=' . $strE . '&lng=' . $strLangue . '&a-ach_maj_from=' . urlencode($strDateTq) . '&btn_search=', 'target' => '_blank');
$arrRapports[] = array('label' => 'adhésions actives', 'url' => $strRapport . 'adhesions&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank');
$arrRapports[] = array('label' => "liste d'adhésions annuelles", 'url' => $strRapport . 'adhesions-ventes&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank');
}
if (intval($arrItem['rapport_capitaines'] ?? 0) == 1) {
$arrRapports[] = array('label' => 'Transactions-Finances (capitaines seulement)', 'url' => $strRapport . 'finances-pnq&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank');
}
$arrRapports[] = array('label' => ($strLangue === 'fr' ? 'Paiements' : 'Payments'), 'url' => $strRapport . 'remboursements&e=' . $strE . '&lng=' . $strLangue, 'target' => '_blank');
if (function_exists('fxGetMenussuperadm')) {
$tabRapports = fxGetMenussuperadm(0, 0, 1, $intEveId);
if (is_array($tabRapports)) {
for ($intCtr = 1; $intCtr <= count($tabRapports); $intCtr++) {
$arrRapports[] = array(
'label' => fxUnescape($tabRapports[$intCtr]['men_nom']),
'url' => $vDomaine . '/superadm/index.php?t=' . urlencode(base64_encode(fxUnescape($tabRapports[$intCtr]['men_id']))) . '&a=report&lng=' . $strLangue,
'target' => '_blank',
);
}
}
}
// ---- Gestion ----
if (!$blnDon) {
$arrGestion[] = array('label' => 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 (!empty($_SESSION['usa_id'])
|| fxEveAccesHasAnyPermission($intComId, $intEveId, 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');
}
}
$arrGestion[] = array('label' => fxPromoteurHubTexte('tableau_promoteur_menueinscription', 0), 'url' => $strCompte . 'inc_tableau_inscriptions?promoteur_eve_id=' . $strE . '&lng=' . $strLangue, 'icon' => 'fa-users');
// Menu hub charge en AJAX (ajax_promoteur_hub.php) sans passer par compte.php :
// inc_fx_inscriptions_gestion.php doit etre charge ici pour evaluer les droits V2.
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');
}
$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) {
$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']) . '"' : '';
$html .= '<li class="mb-2">';
$html .= '<a class="btn btn-outline-primary btn-block text-left rounded-0" href="' . fxPromoteurHubEsc($arrLink['url']) . '"' . $strTarget . '>';
$html .= '<i class="fa ' . fxPromoteurHubEsc($strIcon) . ' mr-2" aria-hidden="true"></i>' . fxPromoteurHubEsc($arrLink['label']);
$html .= '</a></li>';
}
$html .= '</ul>';
return $html;
}
function fxPromoteurHubRenderEventPanelContent($arrItem, $strLangue)
{
$arrLinks = fxPromoteurHubGetEventLinks($arrItem, $strLangue);
$html = '<div class="row">';
// Colonne gauche : Gestion (promoteur). Colonne droite : Rapports.
$html .= '<div class="col-12 col-lg-6 mb-3">';
$html .= '<h6 class="text-uppercase text-muted mb-2"><i class="fa fa-cogs mr-2" aria-hidden="true"></i>' . fxPromoteurHubEsc(fxPromoteurHubTexte('promoteur_hub_section_gestion', 0)) . '</h6>';
$html .= fxPromoteurHubRenderLinkList($arrLinks['gestion'], 'fa-list');
$html .= '</div>';
$html .= '<div class="col-12 col-lg-6 mb-3">';
$html .= '<h6 class="text-uppercase text-muted mb-2"><i class="fa fa-file mr-2" aria-hidden="true"></i>' . fxPromoteurHubEsc(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']);
$blnFr = ($strLangue !== 'en');
$strLoadMsg = $blnFr ? 'Chargement du menu…' : 'Loading menu…';
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']);
}
}
// ---- 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_max' => $intFlashMax,
'total_restant' => max(0, $intFlashMax - $intFlashVendus),
'pct_global' => $intPctGlobal,
'revenus' => $fltFlashRevenus,
'nb_epreuves' => count($arrEpreuves),
'nb_completes' => $intFlashCompletes,
'nb_presque' => $intFlashPresque,
'total_attente' => $intFlashAttente,
'prochaine_date_limite' => $strFlashProchaineLimite,
);
return array('epreuves' => $arrEpreuves, 'flash' => $arrFlash);
}
/**
* Rendu de la ligne resume (bande flash + badges d'alerte). Toujours visible.
*/
function fxPromoteurHubRenderFlash($arrStats, $strLangue = 'fr')
{
if (empty($arrStats) || empty($arrStats['epreuves'])) {
return '';
}
$blnFr = ($strLangue !== 'en');
$arrFlash = $arrStats['flash'];
$lbl = function ($fr, $en) use ($blnFr) {
return $blnFr ? $fr : $en;
};
$html = '';
// ---- Bande flash ----
$html .= '<div class="promoteur-hub-flash">';
$html .= '<div class="phub-flash-item phub-flash-main">';
$html .= '<span class="phub-flash-num">' . intval($arrFlash['total_vendus']) . '</span>';
$html .= '<span class="phub-flash-lbl">' . fxPromoteurHubEsc($lbl('inscrits', 'registered')) . '</span>';
$html .= '</div>';
if (intval($arrFlash['total_max']) > 0) {
$html .= '<div class="phub-flash-item">';
$html .= '<span class="phub-flash-num">' . intval($arrFlash['pct_global']) . '%</span>';
$html .= '<span class="phub-flash-lbl">' . fxPromoteurHubEsc($lbl('rempli', 'filled')) . '</span>';
$html .= '</div>';
$html .= '<div class="phub-flash-item">';
$html .= '<span class="phub-flash-num">' . intval($arrFlash['total_restant']) . '</span>';
$html .= '<span class="phub-flash-lbl">' . fxPromoteurHubEsc($lbl('places restantes', 'spots left')) . '</span>';
$html .= '</div>';
}
if (floatval($arrFlash['revenus']) > 0) {
$html .= '<div class="phub-flash-item">';
$html .= '<span class="phub-flash-num">' . fxPromoteurHubEsc(fxShowPrix($arrFlash['revenus'], $strLangue)) . '</span>';
$html .= '<span class="phub-flash-lbl">' . fxPromoteurHubEsc($lbl('revenus est.', 'est. revenue')) . '</span>';
$html .= '</div>';
}
$html .= '</div>'; // .promoteur-hub-flash
// ---- Badges d'alerte ----
$arrBadges = array();
if (intval($arrFlash['nb_completes']) > 0) {
$arrBadges[] = array('cls' => 'phub-badge-danger', 'icon' => 'fa-ban',
'txt' => intval($arrFlash['nb_completes']) . ' ' . $lbl('complète(s)', 'sold out'));
}
if (intval($arrFlash['nb_presque']) > 0) {
$arrBadges[] = array('cls' => 'phub-badge-warn', 'icon' => 'fa-exclamation-triangle',
'txt' => intval($arrFlash['nb_presque']) . ' ' . $lbl('presque pleine(s)', 'almost full'));
}
if (intval($arrFlash['total_attente']) > 0) {
$arrBadges[] = array('cls' => 'phub-badge-info', 'icon' => 'fa-hourglass-half',
'txt' => intval($arrFlash['total_attente']) . ' ' . $lbl('en attente', 'waiting'));
}
if ($arrFlash['prochaine_date_limite'] !== '') {
$arrBadges[] = array('cls' => 'phub-badge-soft', 'icon' => 'fa-clock-o',
'txt' => $lbl('limite', 'deadline') . ' ' . fxShowDate($arrFlash['prochaine_date_limite'], $strLangue, 1));
}
if (!empty($arrBadges)) {
$html .= '<div class="promoteur-hub-badges">';
foreach ($arrBadges as $arrBadge) {
$html .= '<span class="phub-badge ' . $arrBadge['cls'] . '"><i class="fa ' . $arrBadge['icon'] . ' mr-1" aria-hidden="true"></i>'
. fxPromoteurHubEsc($arrBadge['txt']) . '</span>';
}
$html .= '</div>';
}
return $html;
}
/**
* Rendu de la grille de tuiles par epreuve (contenu du collapse « Tableau de bord »).
*/
function fxPromoteurHubRenderEpreuvesGrid($arrStats, $strLangue = 'fr')
{
if (empty($arrStats) || empty($arrStats['epreuves'])) {
return '';
}
$blnFr = ($strLangue !== 'en');
// ---- Titre de section ----
$html = '<h6 class="promoteur-hub-produits-title text-uppercase text-muted">'
. '<i class="fa fa-flag-checkered mr-2" aria-hidden="true"></i>'
. fxPromoteurHubEsc($blnFr ? 'Épreuves en vente' : 'Events for sale') . '</h6>';
// ---- Tuiles par epreuve ----
$html .= '<div class="promoteur-hub-epreuves-grid">';
foreach ($arrStats['epreuves'] as $arrEpr) {
$strEtat = $arrEpr['etat'];
$html .= '<div class="phub-epr-tile phub-etat-' . fxPromoteurHubEsc($strEtat) . '">';
// En-tete : titre + categorie
$html .= '<div class="phub-epr-head">';
$html .= '<span class="phub-epr-titre">' . fxPromoteurHubEsc($arrEpr['titre']) . '</span>';
if ($arrEpr['categorie'] !== '') {
$html .= '<span class="phub-epr-cat">' . fxPromoteurHubEsc($arrEpr['categorie']) . '</span>';
}
if ($arrEpr['nom'] !== '' && $arrEpr['nom'] !== $arrEpr['titre']) {
$html .= '<span class="phub-epr-sub">' . fxPromoteurHubEsc($arrEpr['nom']) . '</span>';
}
$html .= '</div>';
// Gros chiffre : nombre de dossards vendus
$html .= '<div class="phub-epr-big">';
$html .= '<span class="phub-epr-bignum">' . intval($arrEpr['vendus']) . '</span>';
if (!$arrEpr['illimitee']) {
$html .= '<span class="phub-epr-max">/ ' . intval($arrEpr['max']) . '</span>';
}
$html .= '<span class="phub-epr-biglbl">' . fxPromoteurHubEsc($blnFr ? 'dossards vendus' : 'bibs sold') . '</span>';
$html .= '</div>';
// Barre de progression
if (!$arrEpr['illimitee'] && intval($arrEpr['max']) > 0) {
$html .= '<div class="phub-epr-bar"><span style="width:' . intval($arrEpr['pct']) . '%"></span></div>';
$html .= '<div class="phub-epr-barlbl">' . intval($arrEpr['pct']) . '% &middot; '
. intval($arrEpr['restant']) . ' ' . fxPromoteurHubEsc($blnFr ? 'restant' : 'left') . '</div>';
} else {
$html .= '<div class="phub-epr-barlbl phub-epr-illimite"><span class="phub-infini mr-1" aria-hidden="true">&infin;</span>'
. fxPromoteurHubEsc($blnFr ? 'illimité' : 'unlimited') . '</div>';
}
// Pastilles secondaires
$html .= '<div class="phub-epr-chips">';
if ($arrEpr['prix_courant'] !== null) {
$html .= '<span class="phub-chip phub-chip-prix" title="' . fxPromoteurHubEsc($blnFr ? 'Prix courant' : 'Current price') . '">'
. '<i class="fa fa-tag mr-1" aria-hidden="true"></i>' . fxPromoteurHubEsc(fxShowPrix($arrEpr['prix_courant'], $strLangue)) . '</span>';
if ($arrEpr['prix_prochain'] !== null && $arrEpr['prix_date_fin'] !== '' && $arrEpr['prix_date_fin'] >= date('Y-m-d')) {
$html .= '<span class="phub-chip phub-chip-next" title="' . fxPromoteurHubEsc($blnFr ? 'Prochain prix' : 'Next price') . '">'
. '<i class="fa fa-arrow-up mr-1" aria-hidden="true"></i>'
. fxPromoteurHubEsc(fxShowPrix($arrEpr['prix_prochain'], $strLangue))
. ' ' . fxPromoteurHubEsc($blnFr ? 'dès' : 'on') . ' '
. fxShowDate($arrEpr['prix_date_fin'], $strLangue, 1) . '</span>';
}
}
if (intval($arrEpr['checkin']) > 0) {
$html .= '<span class="phub-chip phub-chip-checkin" title="' . fxPromoteurHubEsc($blnFr ? 'Dossards remis' : 'Bibs handed out') . '">'
. '<i class="fa fa-check mr-1" aria-hidden="true"></i>' . intval($arrEpr['checkin']) . '</span>';
}
if (intval($arrEpr['attente_nb']) > 0 || $arrEpr['attente_active']) {
$strAtt = $arrEpr['attente_active'] && intval($arrEpr['attente_nb']) === 0
? ($blnFr ? 'liste active' : 'list active')
: intval($arrEpr['attente_nb']) . ' ' . ($blnFr ? 'en attente' : 'waiting');
$html .= '<span class="phub-chip phub-chip-attente" title="' . fxPromoteurHubEsc($blnFr ? 'Liste d\'attente' : 'Waiting list') . '">'
. '<i class="fa fa-hourglass-half mr-1" aria-hidden="true"></i>' . fxPromoteurHubEsc($strAtt) . '</span>';
}
if ($arrEpr['date'] !== '' && strpos($arrEpr['date'], '0000') !== 0) {
$html .= '<span class="phub-chip phub-chip-date"><i class="fa fa-calendar-o mr-1" aria-hidden="true"></i>'
. fxShowDate($arrEpr['date'], $strLangue, 1) . '</span>';
}
$html .= '</div>'; // .phub-epr-chips
$html .= '</div>'; // .phub-epr-tile
}
$html .= '</div>'; // .promoteur-hub-epreuves-grid
return $html;
}
/**
* 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 disponibles).
* N'affiche rien si aucun produit.
*/
function fxPromoteurHubRenderProduits($arrProduits, $strLangue = 'fr')
{
if (empty($arrProduits)) {
return '';
}
$blnFr = ($strLangue !== 'en');
$html = '<div class="promoteur-hub-produits">';
$html .= '<h6 class="promoteur-hub-produits-title text-uppercase text-muted">'
. '<i class="fa fa-shopping-bag mr-2" aria-hidden="true"></i>'
. fxPromoteurHubEsc($blnFr ? 'Produits en vente' : 'Products for sale') . '</h6>';
$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>';
$html .= '<div class="phub-prod-big">';
if ($blnIllimite) {
$html .= '<span class="phub-prod-num"><span class="phub-infini" aria-hidden="true">&infin;</span></span>';
} else {
$html .= '<span class="phub-prod-num">' . intval($arrProd['restant']) . '</span>';
$html .= '<span class="phub-prod-tot">/ ' . intval($arrProd['total']) . '</span>';
}
$html .= '<span class="phub-prod-lbl">' . fxPromoteurHubEsc($blnFr ? 'disponible(s)' : 'available') . '</span>';
$html .= '</div>';
$html .= '<div class="phub-epr-chips">';
$html .= '<span class="phub-chip phub-chip-checkin" title="' . fxPromoteurHubEsc($blnFr ? 'Vendus' : 'Sold') . '">'
. '<i class="fa fa-check mr-1" aria-hidden="true"></i>' . intval($arrProd['vendus']) . ' '
. fxPromoteurHubEsc($blnFr ? 'vendu(s)' : 'sold') . '</span>';
if (intval($arrProd['panier']) > 0) {
$html .= '<span class="phub-chip phub-chip-attente" title="' . fxPromoteurHubEsc($blnFr ? 'Dans des paniers incomplets' : 'In incomplete carts') . '">'
. '<i class="fa fa-shopping-cart mr-1" aria-hidden="true"></i>' . intval($arrProd['panier']) . ' '
. fxPromoteurHubEsc($blnFr ? 'panier(s)' : 'cart(s)') . '</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),
);
}
/** Le compte a-t-il accès à cet événement dans le hub (legacy ou v2) ? */
function fxPromoteurHubComHasEvent($intComId, $intEveId)
{
$intEveId = intval($intEveId);
if ($intComId <= 0 || $intEveId <= 0) {
return false;
}
$arrIds = array_unique(array_filter(array_map('intval', array_merge(
fxPromoteurHubGetLegacyEventIds(intval($intComId)),
fxEveAccesGetEventIds(intval($intComId))
)), function ($v) {
return $v > 0;
}));
return in_array($intEveId, $arrIds, true);
}
function fxPromoteurHubRenderEventCard($arrItem, $strLangue, $blnArchived = false)
{
$strCardClass = 'card promoteur-hub-card mb-3' . ($blnArchived ? ' promoteur-hub-card-archived' : '');
$blnFr = ($strLangue !== 'en');
$intEveId = intval($arrItem['eve_id']);
// Stats reportées au clic (AJAX) : N événements × requêtes resultats_* = timeout avec données prod.
$strFlash = '';
$strGrid = '';
$strProduits = '';
$blnHasStats = !$blnArchived;
$blnFonctions = ($arrItem['url_promoteur'] !== '');
$strEprId = 'hub-epr-' . $intEveId;
$strFnId = 'hub-fn-' . $intEveId;
$html = '<div class="' . fxPromoteurHubEsc($strCardClass) . '">';
$html .= '<div class="card-body">';
// ---- Haut : identite (gauche) + resume flash (droite) ----
$html .= '<div class="row promoteur-hub-card-row">';
$html .= '<div class="' . ($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($blnFr ? 'Menu des fonctionnalités' : 'Features menu');
$html .= '<i class="fa fa-chevron-down ml-2 promoteur-hub-toggle-caret" aria-hidden="true"></i>';
$html .= '</button>';
}
$html .= '</div>'; // .promoteur-hub-actions
}
// ---- Collapse : stats chargées au clic (ajax_promoteur_hub.php) ----
if ($blnHasStats) {
global $vDomaine;
$strLoadMsg = $blnFr ? 'Chargement du tableau de bord…' : 'Loading dashboard…';
$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;
}
function fxShowPromoteurHubMenuLink($arrCompteClient, $strLangue)
{
global $vDomaine;
if (!fxPromoteurHubIsAvailable(intval($arrCompteClient['com_id'] ?? 0))) {
return '';
}
$arrIds = array_unique(array_filter(array_map('intval', array_merge(
fxPromoteurHubGetLegacyEventIds(intval($arrCompteClient['com_id'])),
fxEveAccesGetEventIds(intval($arrCompteClient['com_id']))
)), function ($v) {
return $v > 0;
}));
$intNb = count($arrIds);
$strNb = '';
if ($intNb > 0) {
$strNb = '<small>(' . $intNb . ' ';
$strNb .= ($intNb > 1) ? afficheTexte('compte_evenement_pluriel', 0) : afficheTexte('compte_evenement_singulier', 0);
$strNb .= ')</small>';
}
return '
<li class="nav-item">
<a class="nav-list" href="' . fxPromoteurHubEsc(fxPromoteurHubUrl($strLangue)) . '">
<i class="fa fa-tachometer" aria-hidden="true"></i> ' . fxPromoteurHubEsc(afficheTexte('promoteur_hub_menu', 0)) . $strNb . '
</a>
</li>
';
}