2784 lines
125 KiB
PHP
2784 lines
125 KiB
PHP
<?php
|
||
|
||
/**
|
||
* Gestion des inscriptions — recherche, filtres, cartes, fiche.
|
||
*
|
||
* Deux entrees distinctes (meme module, navigation differente) :
|
||
* - Promoteur : /compte/inc_tableau_inscriptions_gestion → retour hub (ou inc_tableau_promoteur legacy)
|
||
* - Acces v2 seul : /mobile → retour /mobile ou Mon compte
|
||
*
|
||
* MSIN-4436 — Parametre inscr_return (URL interne encodee) : retour contextuel depuis la fiche
|
||
* (ex. panneau anomalies dossards) au lieu de la liste des inscriptions.
|
||
*
|
||
* Droits : v2 uniquement (fxInscrGestionCanAccess). i18n : toujours info_prg compte.php.
|
||
*/
|
||
|
||
function fxInscrGestionIsEntry() {
|
||
return defined('MS1_INSCR_GESTION_ENTRY') && MS1_INSCR_GESTION_ENTRY;
|
||
}
|
||
|
||
/** Menu promoteur legacy (tachometre) visible sur Mon compte ? */
|
||
function fxInscrGestionUserHasLegacyPromoteurMenu($intComId)
|
||
{
|
||
if (!function_exists('fxPromoteurLegacySiteEnabled')) {
|
||
require_once __DIR__ . '/inc_fonctions.php';
|
||
}
|
||
|
||
if (!fxPromoteurLegacySiteEnabled()) {
|
||
return false;
|
||
}
|
||
|
||
global $objDatabase;
|
||
|
||
$row = $objDatabase->fxGetRow(
|
||
'SELECT com_eve_promoteur FROM inscriptions_comptes WHERE com_id = ' . intval($intComId)
|
||
);
|
||
|
||
return $row !== null && trim((string)$row['com_eve_promoteur']) !== '';
|
||
}
|
||
|
||
/** Utilisateur avec menu promoteur legacy (section Info promoteur). */
|
||
function fxInscrGestionUserHasPromoteurMenu($intComId) {
|
||
if (fxInscrGestionUserHasLegacyPromoteurMenu($intComId)) {
|
||
return true;
|
||
}
|
||
|
||
global $objDatabase;
|
||
|
||
$row = $objDatabase->fxGetRow(
|
||
'SELECT com_rapports FROM inscriptions_comptes WHERE com_id = ' . intval($intComId)
|
||
);
|
||
|
||
return $row !== null && trim((string)$row['com_rapports']) !== '';
|
||
}
|
||
|
||
/** Permissions v2 requises pour la fiche de gestion des inscriptions (web). */
|
||
function fxInscrGestionPermKeysView() {
|
||
// MSIN-4401 — page V2 seulement (plus d'ancienne clé API registrations.view)
|
||
return array('inscriptions_gestion.view');
|
||
}
|
||
|
||
function fxInscrGestionPermKeysScan() {
|
||
// MSIN-4401 — lecteur QR = détail Inscriptions V2 (même droit sur /compte et /mobile)
|
||
return array('inscriptions_gestion.qr_scan');
|
||
}
|
||
|
||
/** Permission action (niveau fonction dans la fiche de gestion). */
|
||
function fxInscrGestionCanDo($intComId, $intEveId, $strActionPerm) {
|
||
fxInscrGestionLoadEveAcces();
|
||
|
||
if (!fxInscrGestionCanAccess($intComId, $intEveId)) {
|
||
return false;
|
||
}
|
||
|
||
// MSIN-4401 — kit propriétaire / total (grants_all) = toutes les actions, sans cocher chaque droit.
|
||
if (fxEveAccesComEventHasGrantsAllKit(intval($intComId), intval($intEveId))) {
|
||
return true;
|
||
}
|
||
|
||
if (fxEveAccesHasPermission(intval($intComId), intval($intEveId), $strActionPerm)) {
|
||
return true;
|
||
}
|
||
|
||
// MSIN-4401 — Héritage kit desktop « inscriptions.edit » : actions de gestion courantes,
|
||
// PAS les actions sensibles (remboursement, etc.) — celles-ci exigent le droit explicite.
|
||
$tabNoInheritFromInscrEdit = array(
|
||
'inscriptions_gestion.refund' => true,
|
||
);
|
||
if (empty($tabNoInheritFromInscrEdit[$strActionPerm])
|
||
&& fxEveAccesHasPermission(intval($intComId), intval($intEveId), 'inscriptions.edit')) {
|
||
return true;
|
||
}
|
||
|
||
/* dossards.manage autorise aussi la modification du no dossard sur la fiche de gestion. */
|
||
if ($strActionPerm === 'inscriptions_gestion.bib_edit'
|
||
&& fxEveAccesHasPermission(intval($intComId), intval($intEveId), 'dossards.manage')) {
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/** Badge cadenas (mode inspecteur super admin) sur la fiche de gestion. */
|
||
function fxInscrGestionPermInspectEcho($strPermKey) {
|
||
if (!function_exists('fxAdminPermInspectModeActive') || !fxAdminPermInspectModeActive()) {
|
||
return;
|
||
}
|
||
fxInscrGestionLoadEveAcces();
|
||
echo fxEveAccesPermInspectBadge($strPermKey, 'inscr_gestion');
|
||
}
|
||
|
||
/** Badge + libelle pour un controle masque par les droits (mode inspecteur). */
|
||
function fxInscrGestionPermInspectEchoHidden($strPermKey, $strLabel) {
|
||
if (!function_exists('fxAdminPermInspectModeActive') || !fxAdminPermInspectModeActive()) {
|
||
return;
|
||
}
|
||
fxInscrGestionLoadEveAcces();
|
||
echo '<span class="ms1-perm-inspect-slot">';
|
||
echo fxEveAccesPermInspectBadge($strPermKey, 'inscr_gestion');
|
||
echo ' <span class="text-muted small">' . fxInscrGestionEsc($strLabel) . '</span>';
|
||
echo '</span>';
|
||
}
|
||
|
||
/** MSIN-4401 — Enveloppe action + cadenas (toujours a droite du controle). */
|
||
/**
|
||
* MSIN-4464 — Bouton « ! » historique (seulement s'il y a au moins 1 log pour le champ).
|
||
*
|
||
* @param array<string,true> $arrFieldsWithLogs
|
||
*/
|
||
function fxInscrGestionRenderAuditBang($intParId, $strFieldKey, $blnCanAudit, $arrFieldsWithLogs)
|
||
{
|
||
$intParId = intval($intParId);
|
||
$strFieldKey = preg_replace('/[^a-z0-9_]/i', '', (string)$strFieldKey);
|
||
if (!$blnCanAudit || $intParId <= 0 || $strFieldKey === '' || empty($arrFieldsWithLogs[$strFieldKey])) {
|
||
return;
|
||
}
|
||
|
||
$strTitle = fxInscrGestionT('inscr_gestion_audit_bang_title');
|
||
$strAria = fxInscrGestionT('inscr_gestion_audit_bang_aria');
|
||
echo ' <button type="button" class="inscr-gestion-audit-bang"'
|
||
. ' data-par_id="' . $intParId . '"'
|
||
. ' data-field="' . fxInscrGestionEsc($strFieldKey) . '"'
|
||
. ' title="' . fxInscrGestionEsc($strTitle) . '"'
|
||
. ' aria-label="' . fxInscrGestionEsc($strAria) . '">!</button>';
|
||
}
|
||
|
||
function fxInscrGestionActionItemOpen($strExtraClass = '') {
|
||
$strExtra = trim((string)$strExtraClass);
|
||
echo '<span class="inscr-gestion-action-item'
|
||
. ($strExtra !== '' ? ' ' . $strExtra : '')
|
||
. '">';
|
||
}
|
||
|
||
function fxInscrGestionActionItemClose() {
|
||
echo '</span>';
|
||
}
|
||
|
||
function fxInscrGestionLoadEveAcces() {
|
||
require_once __DIR__ . '/inc_fx_eve_acces.php';
|
||
}
|
||
|
||
/**
|
||
* Evenements accessibles via acces v2 uniquement (pas com_eve_promoteur, pas super admin).
|
||
*/
|
||
function fxInscrGestionGetPromoteurEveIds($intComId) {
|
||
fxInscrGestionLoadEveAcces();
|
||
return fxEveAccesGetEventIdsWithInscrGestionAccess(intval($intComId));
|
||
}
|
||
|
||
function fxInscrGestionCanAccess($intComId, $intEveId) {
|
||
// MSIN-4401 — Super-admin (usa_id) ne bypass plus : kits / extras seulement.
|
||
fxInscrGestionLoadEveAcces();
|
||
return fxEveAccesHasInscrGestionWebAccess(intval($intComId), intval($intEveId));
|
||
}
|
||
|
||
function fxInscrGestionCanScan($intComId, $intEveId) {
|
||
fxInscrGestionLoadEveAcces();
|
||
$intComId = intval($intComId);
|
||
$intEveId = intval($intEveId);
|
||
|
||
// MSIN-4401 — kit propriétaire / total (grants_all) = tout, sans cocher chaque droit.
|
||
if (fxEveAccesComEventHasGrantsAllKit($intComId, $intEveId)) {
|
||
return true;
|
||
}
|
||
|
||
// Même règle hub / compte /mobile : droit explicite scanner QR.
|
||
if (fxEveAccesHasAnyPermission($intComId, $intEveId, fxInscrGestionPermKeysScan())) {
|
||
return true;
|
||
}
|
||
|
||
// Transition : clé absente en BD → fallback page Inscriptions V2.
|
||
if (!fxEveAccesCatalogPermIsActive('inscriptions_gestion.qr_scan')
|
||
&& fxEveAccesHasPermission($intComId, $intEveId, 'inscriptions_gestion.view')) {
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
function fxInscrGestionBaseUrl($strLangue, $blnMobileEntry = null) {
|
||
global $vDomaine;
|
||
|
||
if ($blnMobileEntry === null) {
|
||
$blnMobileEntry = fxInscrGestionIsEntry();
|
||
}
|
||
if ($blnMobileEntry) {
|
||
return $vDomaine . '/mobile';
|
||
}
|
||
|
||
return $vDomaine . ($strLangue === 'fr' ? '/compte/inc_tableau_inscriptions_gestion' : '/account/inc_tableau_inscriptions_gestion');
|
||
}
|
||
|
||
/**
|
||
* MSIN-4436 — Encode une URL interne relative (/compte/... ou /account/...) pour inscr_return.
|
||
*/
|
||
function fxInscrGestionEncodeReturnUrl($strRelativePath) {
|
||
$strRelativePath = trim((string)$strRelativePath);
|
||
if ($strRelativePath === '' || $strRelativePath[0] !== '/') {
|
||
return '';
|
||
}
|
||
|
||
return rtrim(strtr(base64_encode($strRelativePath), '+/', '-_'), '=');
|
||
}
|
||
|
||
/**
|
||
* MSIN-4436 — Décode inscr_return ; retourne le chemin relatif validé ou ''.
|
||
*/
|
||
function fxInscrGestionDecodeReturnPath($strToken) {
|
||
$strToken = trim((string)$strToken);
|
||
if ($strToken === '') {
|
||
return '';
|
||
}
|
||
|
||
$strB64 = strtr($strToken, '-_', '+/');
|
||
$intPad = strlen($strB64) % 4;
|
||
if ($intPad > 0) {
|
||
$strB64 .= str_repeat('=', 4 - $intPad);
|
||
}
|
||
|
||
$strPath = base64_decode($strB64, true);
|
||
if ($strPath === false || $strPath === '' || $strPath[0] !== '/') {
|
||
return '';
|
||
}
|
||
if (preg_match('#^https?://#i', $strPath)) {
|
||
return '';
|
||
}
|
||
if (!preg_match('#^/(compte|account)/#', $strPath)) {
|
||
return '';
|
||
}
|
||
|
||
return $strPath;
|
||
}
|
||
|
||
/** MSIN-4436 — URL absolue de retour depuis inscr_return, ou '' si invalide. */
|
||
function fxInscrGestionDecodeReturnUrl($strToken) {
|
||
global $vDomaine;
|
||
|
||
$strPath = fxInscrGestionDecodeReturnPath($strToken);
|
||
if ($strPath === '') {
|
||
return '';
|
||
}
|
||
|
||
return $vDomaine . $strPath;
|
||
}
|
||
|
||
/** MSIN-4436 — Bouton retour fiche : URL externe (inscr_return) ou liste filtrée. */
|
||
function fxInscrGestionResolveBackUrl($strBaseUrl, $intEveId, $arrReq) {
|
||
if (!empty($arrReq['inscr_return'])) {
|
||
$strReturn = fxInscrGestionDecodeReturnUrl($arrReq['inscr_return']);
|
||
if ($strReturn !== '') {
|
||
return $strReturn;
|
||
}
|
||
}
|
||
|
||
return fxInscrGestionBuildUrl($strBaseUrl, $intEveId, $arrReq, array('pec_id' => null, 'pg' => $arrReq['pg']));
|
||
}
|
||
|
||
/** MSIN-4436 — Libellé du bouton retour selon le contexte d'origine. */
|
||
function fxInscrGestionResolveBackLabel($arrReq, $strLangue = 'fr') {
|
||
if (!empty($arrReq['inscr_return'])) {
|
||
$strPath = fxInscrGestionDecodeReturnPath($arrReq['inscr_return']);
|
||
if ($strPath !== '' && strpos($strPath, 'inc_tableau_gestion_epreuves') !== false) {
|
||
return fxInscrGestionT('inscr_gestion_back_bib');
|
||
}
|
||
|
||
return fxInscrGestionT('inscr_gestion_back_prev');
|
||
}
|
||
|
||
return fxInscrGestionT('inscr_gestion_back_list');
|
||
}
|
||
|
||
function fxInscrGestionEsc($str) {
|
||
return htmlspecialchars((string)$str, ENT_QUOTES, 'UTF-8');
|
||
}
|
||
|
||
/** Page info / i18n des libelles de gestion des inscriptions (toujours compte.php). */
|
||
function fxInscrGestionTextesPage() {
|
||
return 'compte.php';
|
||
}
|
||
|
||
function fxInscrGestionLoadTextes($strLangue) {
|
||
static $tabCache = array();
|
||
static $strCacheLang = '';
|
||
|
||
if ($strCacheLang === $strLangue && !empty($tabCache)) {
|
||
return $tabCache;
|
||
}
|
||
|
||
$tabCache = obtenirTextepage(fxInscrGestionTextesPage(), $strLangue, 2);
|
||
if (!is_array($tabCache)) {
|
||
$tabCache = array();
|
||
}
|
||
$strCacheLang = $strLangue;
|
||
|
||
return $tabCache;
|
||
}
|
||
|
||
/**
|
||
* Recherche un libelle info par clef (toutes pages), priorite compte.php > global > mobile.php.
|
||
*/
|
||
function fxInscrGestionLookupTexteDb($strClef, $strLangue) {
|
||
global $objDatabase;
|
||
|
||
static $tabMemo = array();
|
||
$strMemoKey = $strClef . "\0" . $strLangue;
|
||
|
||
if (array_key_exists($strMemoKey, $tabMemo)) {
|
||
return $tabMemo[$strMemoKey];
|
||
}
|
||
|
||
$sql = "SELECT info_texte FROM info
|
||
WHERE info_clef = '" . $objDatabase->fxEscape($strClef) . "'
|
||
AND info_langue = '" . $objDatabase->fxEscape($strLangue) . "'
|
||
AND info_actif = 1
|
||
ORDER BY FIELD(info_prg, 'compte.php', 'global', 'mobile.php', 'mobile')
|
||
LIMIT 1";
|
||
$strTexte = $objDatabase->fxGetVar($sql);
|
||
|
||
if ($strTexte === null || trim((string)$strTexte) === '' || trim((string)$strTexte) === $strClef
|
||
|| fxInscrGestionIsRawI18nKey($strTexte)) {
|
||
$tabMemo[$strMemoKey] = null;
|
||
return null;
|
||
}
|
||
|
||
$tabMemo[$strMemoKey] = (string)$strTexte;
|
||
return $tabMemo[$strMemoKey];
|
||
}
|
||
|
||
/**
|
||
* True si $str ressemble a une clef info technique (ex. inscr_gestion_bib_scan_tap),
|
||
* pas a un vrai libelle (espaces/accents/points). Ignore marqueurs ZWSP trad.
|
||
*/
|
||
function fxInscrGestionIsRawI18nKey($str) {
|
||
$str = preg_replace('/[\x{200B}\x{200C}]/u', '', (string)$str);
|
||
// Payload base64 eventuel (mode edition textes) entre ZWSP deja retire.
|
||
$str = trim($str);
|
||
if ($str === '') {
|
||
return false;
|
||
}
|
||
return (bool)preg_match('/^[a-z][a-z0-9]*(_[a-z0-9]+)+$/i', $str);
|
||
}
|
||
|
||
function fxInscrGestionResolveTexte($strClef, $strLangue) {
|
||
global $vtexte_page;
|
||
|
||
$tab = fxInscrGestionLoadTextes($strLangue);
|
||
|
||
if (isset($tab[$strClef]['info_texte'])) {
|
||
$str = trim((string)$tab[$strClef]['info_texte']);
|
||
if ($str !== '' && $str !== $strClef && !fxInscrGestionIsRawI18nKey($str)) {
|
||
return $str;
|
||
}
|
||
}
|
||
|
||
if (is_array($vtexte_page) && isset($vtexte_page[$strClef]['info_texte'])) {
|
||
$str = trim((string)$vtexte_page[$strClef]['info_texte']);
|
||
if ($str !== '' && $str !== $strClef && !fxInscrGestionIsRawI18nKey($str)) {
|
||
return $str;
|
||
}
|
||
}
|
||
|
||
$strDb = fxInscrGestionLookupTexteDb($strClef, $strLangue);
|
||
if ($strDb !== null && !fxInscrGestionIsRawI18nKey($strDb)) {
|
||
return $strDb;
|
||
}
|
||
|
||
if (function_exists('fxAdminCanUseTextEditTools') && fxAdminCanUseTextEditTools()) {
|
||
global $vPage;
|
||
$memPage = $vPage;
|
||
$vPage = fxInscrGestionTextesPage();
|
||
$str = afficheTexte($strClef, 0);
|
||
$vPage = $memPage;
|
||
if (strpos($str, '*') === 0 && substr($str, -1) === '*') {
|
||
return fxInscrGestionFallbackTexte($strClef, $strLangue);
|
||
}
|
||
$strPlain = trim(preg_replace('/[\x{200B}\x{200C}]/u', '', (string)$str));
|
||
if ($strPlain === '' || $strPlain === $strClef || fxInscrGestionIsRawI18nKey($strPlain)) {
|
||
return fxInscrGestionFallbackTexte($strClef, $strLangue);
|
||
}
|
||
return $str;
|
||
}
|
||
|
||
return fxInscrGestionFallbackTexte($strClef, $strLangue);
|
||
}
|
||
|
||
/** Libelles integres (secours si table info non a jour). */
|
||
function fxInscrGestionFallbackTexte($strClef, $strLangue) {
|
||
static $tabMemo = array();
|
||
|
||
if (!isset($tabMemo[$strLangue])) {
|
||
$blnFr = ($strLangue !== 'en');
|
||
$tabMemo[$strLangue] = array(
|
||
'inscr_gestion_qr_title' => $blnFr ? 'Code QR — Lecteur' : 'QR code reader',
|
||
'inscr_gestion_qr_hint' => $blnFr ? 'Pointez la caméra vers le code QR du participant.' : 'Point the camera at the participant\'s QR code.',
|
||
'inscr_gestion_qr_invalid' => $blnFr ? 'Code QR invalide ou introuvable.' : 'Invalid or unknown QR code.',
|
||
'inscr_gestion_qr_not_found' => $blnFr ? 'Participant introuvable pour cet événement.' : 'Participant not found for this event.',
|
||
'inscr_gestion_qr_wrong_event' => $blnFr ? 'Ce code QR appartient à un autre événement : %s' : 'This QR code belongs to another event: %s',
|
||
'inscr_gestion_qr_camera_error' => $blnFr ? 'Impossible d\'accéder à la caméra.' : 'Unable to access the camera.',
|
||
'inscr_gestion_bib_scan_btn' => $blnFr ? 'Scanner le numéro' : 'Scan number',
|
||
'inscr_gestion_bib_scan_hint' => $blnFr
|
||
? 'Cadrez le numéro dans le cadre, puis touchez l\'image.'
|
||
: 'Frame the number in the box, then tap the image.',
|
||
'inscr_gestion_bib_scan_ready' => $blnFr ? 'Touchez l\'image pour lire' : 'Tap the image to read',
|
||
'inscr_gestion_bib_scan_tap' => $blnFr
|
||
? 'Touchez pour lire'
|
||
: 'Tap to read',
|
||
'inscr_gestion_bib_scan_scanning' => $blnFr ? 'Lecture en cours...' : 'Reading...',
|
||
'inscr_gestion_bib_scan_loading' => $blnFr ? 'Initialisation (1re fois)...' : 'Initializing (first time)...',
|
||
'inscr_gestion_bib_scan_found' => $blnFr ? 'Numéro %s détecté — validez avec OK.' : 'Number %s detected — press OK.',
|
||
'inscr_gestion_bib_scan_invalid' => $blnFr ? 'Numéro illisible. Cadrez un seul numéro.' : 'Unreadable number. Frame a single number.',
|
||
// Libelles de la barre de boutons (fiche de gestion) — editables via la table info (page compte.php).
|
||
'inscr_gestion_show_invoice' => $blnFr ? 'Afficher la facture' : 'Show invoice',
|
||
'inscr_gestion_show_transaction' => $blnFr ? 'Afficher la transaction' : 'Show transaction',
|
||
'inscr_gestion_hide_transaction' => $blnFr ? 'Masquer la transaction' : 'Hide transaction',
|
||
'inscr_gestion_renvoi' => $blnFr ? 'Retourner confirmation' : 'Resend confirmation',
|
||
'inscr_gestion_modifier_participant' => $blnFr ? 'Modifier l\'inscription' : 'Edit registration',
|
||
// Invite affichee quand la page arrive vide (aucune recherche lancee).
|
||
'inscr_gestion_search_prompt' => $blnFr
|
||
? 'Utilisez la recherche ci-dessus pour afficher les inscriptions. Laissez les champs vides et lancez la recherche pour toutes les afficher.'
|
||
: 'Use the search above to display registrations. Leave the fields empty and search to show them all.',
|
||
// MSIN-4405 — Detail du statut annule (transferee / annulee), derive en lecture.
|
||
'inscr_gestion_annulation_titre' => $blnFr ? 'Statut de l\'inscription' : 'Registration status',
|
||
'inscr_gestion_statut_annule' => $blnFr ? 'Annulée' : 'Cancelled',
|
||
'inscr_gestion_statut_transfert' => $blnFr ? 'Transférée' : 'Transferred',
|
||
'inscr_gestion_back_bib' => $blnFr ? 'Retour aux dossards' : 'Back to bibs',
|
||
'inscr_gestion_back_prev' => $blnFr ? 'Retour' : 'Back',
|
||
// MSIN-4401 — Sortie de /mobile vers hub ou Mon compte (usagers avec plus de droits).
|
||
'inscr_gestion_back_full_account' => $blnFr ? 'Retour au tableau de bord' : 'Back to dashboard',
|
||
'inscr_gestion_back_mon_compte' => $blnFr ? 'Retour à Mon compte' : 'Back to My account',
|
||
'inscr_gestion_chk_cancelled' => $blnFr
|
||
? 'Montrer les inscriptions annulées ou transférées'
|
||
: 'Show cancelled or transferred registrations',
|
||
// MSIN-4401 — Filtre recherche par statut de course (multi-sélection).
|
||
'inscr_gestion_rech_statut' => $blnFr ? 'Statut de course' : 'Race status',
|
||
'inscr_gestion_rech_statut_all' => $blnFr ? 'Tous les statuts' : 'All statuses',
|
||
'inscr_gestion_rech_statut_n' => $blnFr ? '%d sélectionnés' : '%d selected',
|
||
'inscr_gestion_mobile_title' => $blnFr ? 'Inscriptions mobile' : 'Mobile registrations',
|
||
'inscr_gestion_login_user' => $blnFr ? 'Nom d\'utilisateur' : 'Username',
|
||
'inscr_gestion_login_pass' => $blnFr ? 'Mot de passe' : 'Password',
|
||
'inscr_gestion_login_btn' => $blnFr ? 'Connexion' : 'Sign in',
|
||
'inscr_gestion_login_intro' => $blnFr
|
||
? 'Connectez-vous pour accéder aux inscriptions mobile.'
|
||
: 'Sign in to access mobile registrations.',
|
||
'inscr_gestion_login_err_user' => $blnFr
|
||
? 'Veuillez entrer votre nom d\'usager'
|
||
: 'Please enter your username',
|
||
'inscr_gestion_login_err_pass' => $blnFr
|
||
? 'Veuillez entrer votre mot de passe'
|
||
: 'Please enter your password',
|
||
'inscr_gestion_choose_event' => $blnFr ? 'Choisir un événement' : 'Choose an event',
|
||
'inscr_gestion_back_events' => $blnFr ? 'Événements' : 'Events',
|
||
'tableau_promoteur_menueinscription_v2' => $blnFr ? 'Inscriptions V2' : 'Registrations V2',
|
||
'mobile_no_acces_v2' => $blnFr
|
||
? 'Aucun accès inscriptions mobile (v2) n\'est actif pour ce compte.'
|
||
: 'No active mobile registration access (v2) for this account.',
|
||
'mobile_invite_conflict_signed_as' => $blnFr
|
||
? 'Vous êtes actuellement connecté en tant que %name%.'
|
||
: 'You are currently signed in as %name%.',
|
||
'mobile_invite_conflict_for' => $blnFr
|
||
? 'Cette invitation concerne le compte %name%%mail_part%. Déconnectez-vous d\'abord, puis reconnectez-vous avec ce compte.'
|
||
: 'This invitation is for %name%%mail_part%. Sign out first, then sign in with that account.',
|
||
'mobile_invite_conflict_btn_logout' => $blnFr
|
||
? 'Se déconnecter et continuer'
|
||
: 'Sign out and continue',
|
||
'mobile_invite_conflict_btn_stay' => $blnFr
|
||
? 'Rester sur mon compte actuel'
|
||
: 'Stay on my current account',
|
||
'inscr_gestion_bib_scan_found_search' => $blnFr
|
||
? 'Numéro %s détecté — recherche...'
|
||
: 'Number %s detected — searching...',
|
||
'inscr_gestion_bib_ocr_not_configured' => $blnFr
|
||
? 'OCR non configuré (clé Vision absente sur le serveur).'
|
||
: 'OCR not configured (Vision API key missing on server).',
|
||
'inscr_gestion_bib_ocr_vision_prefix' => $blnFr ? 'Erreur Vision : ' : 'Vision error: ',
|
||
'inscr_gestion_bib_ocr_image_invalid' => $blnFr ? 'Image invalide.' : 'Invalid image capture.',
|
||
'inscr_gestion_bib_ocr_network' => $blnFr ? 'Erreur réseau ou capture.' : 'Network or capture error.',
|
||
'inscr_gestion_err_load' => $blnFr ? 'Erreur de chargement.' : 'Loading error.',
|
||
'inscr_gestion_err_network' => $blnFr ? 'Erreur réseau.' : 'Network error.',
|
||
'inscr_gestion_err_refund' => $blnFr ? 'Erreur remboursement.' : 'Refund error.',
|
||
'inscr_gestion_refund_ok' => $blnFr ? 'Remboursement effectué.' : 'Refund completed.',
|
||
'inscr_gestion_refund_cancel_hint' => $blnFr
|
||
? 'Votre transaction a été remboursée. Si vous voulez aussi l\'annuler dans le système d\'inscription :'
|
||
: 'Your transaction has been refunded. If you also want to cancel it in the registration system:',
|
||
'inscr_gestion_col_participant' => $blnFr ? 'Participant' : 'Participant',
|
||
'inscr_gestion_col_commande' => $blnFr ? 'Commande' : 'Order',
|
||
'inscr_gestion_col_dossard' => $blnFr ? 'Dossard' : 'Bib',
|
||
// MSIN-4399 — Colonne statut de course (entre dossard et remis).
|
||
'inscr_gestion_col_statut' => $blnFr ? 'Statut' : 'Status',
|
||
'inscr_gestion_col_remis' => $blnFr ? 'Remis' : 'Issued',
|
||
// MSIN-4448 — Edition inline liste (OK dossard + confirmation remis).
|
||
'inscr_gestion_bib_ok' => 'OK',
|
||
'inscr_gestion_remis_confirm_title' => $blnFr ? 'Confirmation' : 'Confirmation',
|
||
'inscr_gestion_remis_confirm_msg' => $blnFr
|
||
? 'Êtes-vous certain de vouloir retirer la mention « dossard récupéré » pour ce participant ?'
|
||
: 'Are you sure you want to remove the "bib retrieved" status for this participant?',
|
||
'inscr_gestion_remis_confirm_ok' => $blnFr ? 'Retirer' : 'Remove',
|
||
'inscr_gestion_remis_confirm_cancel' => $blnFr ? 'Annuler' : 'Cancel',
|
||
// MSIN-4464 — historique fiche ( ! )
|
||
'inscr_gestion_audit_bang_title' => $blnFr ? 'Voir les derniers changements' : 'View recent changes',
|
||
'inscr_gestion_audit_bang_aria' => $blnFr ? 'Historique des modifications' : 'Change history',
|
||
'inscr_gestion_audit_popup_title' => $blnFr ? 'Derniers changements' : 'Recent changes',
|
||
'inscr_gestion_audit_popup_empty' => $blnFr ? 'Aucun historique pour ce champ.' : 'No history for this field.',
|
||
'inscr_gestion_audit_popup_close' => $blnFr ? 'Fermer' : 'Close',
|
||
'inscr_gestion_audit_col_when' => $blnFr ? 'Quand' : 'When',
|
||
'inscr_gestion_audit_col_who' => $blnFr ? 'Par' : 'By',
|
||
'inscr_gestion_audit_col_from' => $blnFr ? 'Avant' : 'From',
|
||
'inscr_gestion_audit_col_to' => $blnFr ? 'Après' : 'To',
|
||
'inscr_gestion_audit_field_par_statut_course' => $blnFr ? 'Statut de course' : 'Race status',
|
||
'inscr_gestion_audit_field_no_bib' => $blnFr ? 'Numéro de dossard' : 'Bib number',
|
||
'inscr_gestion_audit_field_no_bib_remis' => $blnFr ? 'Dossard remis' : 'Bib handed out',
|
||
'inscr_gestion_audit_field_is_cancelled' => $blnFr ? 'Annulation' : 'Cancellation',
|
||
// MSIN-4328 — bouton superadmin vers fiche ChronoTrack
|
||
'inscr_gestion_btn_chronotrack' => $blnFr ? 'Voir dans ChronoTrack' : 'View in ChronoTrack',
|
||
'inscr_gestion_list_count_one' => $blnFr ? '%d inscription' : '%d registration',
|
||
'inscr_gestion_list_count_many' => $blnFr ? '%d inscriptions' : '%d registrations',
|
||
'inscr_gestion_list_page' => $blnFr ? 'Page %d / %d' : 'Page %d / %d',
|
||
'inscr_gestion_list_filter_kv' => $blnFr ? '%s : %s' : '%s: %s',
|
||
'inscr_gestion_rech_bib_equipe' => $blnFr ? 'Dossard ou équipe' : 'Bib or team',
|
||
'inscr_gestion_bib_match_exact' => $blnFr ? 'Exact' : 'Exact',
|
||
'inscr_gestion_bib_match_partial' => $blnFr ? 'Partiel' : 'Partial',
|
||
'inscr_gestion_bib_match_aria' => $blnFr
|
||
? 'Mode de recherche du dossard'
|
||
: 'Bib search mode',
|
||
'chk_bib' => $blnFr ? 'Sans numéro de dossard' : 'Without bib number',
|
||
'inscr_gestion_reset_search' => $blnFr ? 'Réinitialiser la recherche' : 'Reset search',
|
||
// MSIN-4459 — Nom d'équipe + coéquipiers sous le capitaine en liste.
|
||
'inscr_gestion_list_team_name' => $blnFr ? 'Nom d\'équipe : %s' : 'Team name: %s',
|
||
'inscr_gestion_list_teammate' => $blnFr ? 'Coéquipier' : 'Teammate',
|
||
);
|
||
}
|
||
|
||
if (isset($tabMemo[$strLangue][$strClef])) {
|
||
return $tabMemo[$strLangue][$strClef];
|
||
}
|
||
|
||
return $strClef;
|
||
}
|
||
|
||
/** Charge $vtexte_page pour le module gestion (entry /mobile ou compte). */
|
||
function fxInscrGestionInitTextes($strLangue) {
|
||
global $vtexte_page, $vPage;
|
||
|
||
$vPage = fxInscrGestionTextesPage();
|
||
$vtexte_page = fxInscrGestionLoadTextes($strLangue);
|
||
}
|
||
|
||
/** Libelle i18n sans echo parasite — independant de la page PHP courante. */
|
||
function fxInscrGestionT($strClef) {
|
||
global $strLangue;
|
||
|
||
if (!isset($strLangue) || $strLangue === '') {
|
||
$strLangue = 'fr';
|
||
}
|
||
|
||
$strTexte = fxInscrGestionResolveTexte($strClef, $strLangue);
|
||
$strPlain = trim(preg_replace('/[\x{200B}\x{200C}]/u', '', (string)$strTexte));
|
||
|
||
if ($strPlain === '' || $strPlain === $strClef || fxInscrGestionIsRawI18nKey($strPlain)) {
|
||
$strFallback = fxInscrGestionFallbackTexte($strClef, $strLangue);
|
||
if ($strFallback !== $strClef && trim((string)$strFallback) !== '') {
|
||
$strTexte = $strFallback;
|
||
}
|
||
}
|
||
|
||
if (function_exists('fxAdminTradMarker')) {
|
||
$strMarker = fxAdminTradMarker($strClef, 0);
|
||
if ($strMarker !== '' && strpos($strTexte, $strMarker) === false) {
|
||
$strTexte .= $strMarker;
|
||
}
|
||
}
|
||
|
||
return $strTexte;
|
||
}
|
||
|
||
/**
|
||
* Libelle "robuste" pour le scanner : jamais de clef technique ni marqueur trad
|
||
* (affiche dans la video / data-msg lus par le JS).
|
||
*/
|
||
function fxInscrGestionScanLabel($strClef) {
|
||
global $strLangue;
|
||
|
||
$strLang = (isset($strLangue) && $strLangue !== '') ? $strLangue : 'fr';
|
||
$strTexte = trim(preg_replace('/[\x{200B}\x{200C}]/u', '', (string)fxInscrGestionResolveTexte($strClef, $strLang)));
|
||
|
||
if ($strTexte === '' || $strTexte === $strClef || fxInscrGestionIsRawI18nKey($strTexte)) {
|
||
$strTexte = fxInscrGestionFallbackTexte($strClef, $strLang);
|
||
}
|
||
|
||
return $strTexte;
|
||
}
|
||
|
||
function fxInscrGestionLoginActionUrl($strLangue) {
|
||
global $vDomaine;
|
||
|
||
if ($strLangue === 'en') {
|
||
return $vDomaine . '/mobile/en';
|
||
}
|
||
|
||
return $vDomaine . '/mobile';
|
||
}
|
||
|
||
function fxInscrGestionRenderLoginPage($strLangue) {
|
||
$strActionUrl = fxInscrGestionLoginActionUrl($strLangue);
|
||
|
||
echo '<div class="inscr-gestion-page inscr-gestion-login-wrap">';
|
||
echo '<div id="module-inscr-gestion" class="inscr-gestion-shell inscr-gestion-shell--login">';
|
||
fxInscrGestionRenderPageHead(fxInscrGestionT('inscr_gestion_mobile_title'));
|
||
echo '<p class="inscr-gestion-login-intro text-muted">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_login_intro')) . '</p>';
|
||
echo '<form action="' . fxInscrGestionEsc($strActionUrl) . '" id="frm_inscr_gestion_login" method="post" class="inscr-gestion-login-form">';
|
||
echo '<input type="hidden" name="form_action" value="' . fxInscrGestionEsc(urlencode(base64_encode('login'))) . '">';
|
||
echo '<label class="inscr-gestion-label" for="txt_login_mobile">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_login_user')) . '</label>';
|
||
echo '<input class="form-control form-control-sm rounded-0" type="text" id="txt_login_mobile" name="txt_login" required autofocus>';
|
||
echo '<label class="inscr-gestion-label" for="txt_password_mobile">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_login_pass')) . '</label>';
|
||
echo '<input class="form-control form-control-sm rounded-0" type="password" id="txt_password_mobile" name="txt_password" required>';
|
||
echo '<button class="btn btn-primary btn-sm rounded-0 btn-block mt-3" type="submit" id="btn_login_mobile" name="btn_login">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_login_btn')) . '</button>';
|
||
echo '</form></div></div>';
|
||
}
|
||
|
||
/** Libellé menu promoteur V2 (Info). */
|
||
function fxInscrGestionPromoteurMenuLabel() {
|
||
return fxInscrGestionT('tableau_promoteur_menueinscription_v2');
|
||
}
|
||
|
||
function fxInscrGestionListBackLabel($strLangue) {
|
||
if (fxInscrGestionIsEntry()) {
|
||
return fxInscrGestionT('inscr_gestion_back_events');
|
||
}
|
||
|
||
return fxInscrGestionT('promoteur_back');
|
||
}
|
||
|
||
/**
|
||
* MSIN-4399 — URL retour depuis la liste inscriptions V2 :
|
||
* hub promoteur si dispo (avec focus sur l'événement), sinon ancien tableau promoteur.
|
||
*/
|
||
function fxInscrGestionListBackUrl($intEveId, $strLangue) {
|
||
$strLangue = ($strLangue === 'en') ? 'en' : 'fr';
|
||
|
||
if (!function_exists('fxPromoteurHubOrLegacyBackUrl')) {
|
||
require_once __DIR__ . '/inc_fx_promoteur_hub.php';
|
||
}
|
||
|
||
return fxPromoteurHubOrLegacyBackUrl($intEveId, $strLangue);
|
||
}
|
||
|
||
function fxInscrGestionShouldShowListBack($intComId) {
|
||
if (!fxInscrGestionIsEntry()) {
|
||
return true;
|
||
}
|
||
|
||
return count(fxInscrGestionGetPromoteurEveIds($intComId)) > 1;
|
||
}
|
||
|
||
function fxInscrGestionRenderNavBack($strUrl, $strLabel) {
|
||
echo '<a class="btn btn-primary rounded-pill inscr-gestion-nav-back" href="' . fxInscrGestionEsc($strUrl) . '">';
|
||
echo '<i class="fa fa-chevron-left mr-2" aria-hidden="true"></i>' . fxInscrGestionEsc($strLabel);
|
||
echo '</a>';
|
||
}
|
||
|
||
/**
|
||
* MSIN-4401 — Sur /mobile, proposer une sortie vers le hub (ou Mon compte)
|
||
* si l'usager a plus que l'outil inscriptions seul.
|
||
*/
|
||
function fxInscrGestionShouldShowFullAccountLink($intComId) {
|
||
if (!fxInscrGestionIsEntry()) {
|
||
return false;
|
||
}
|
||
|
||
$intComId = intval($intComId);
|
||
if ($intComId <= 0) {
|
||
return false;
|
||
}
|
||
|
||
if (!function_exists('fxPromoteurHubIsAvailable')) {
|
||
require_once __DIR__ . '/inc_fx_promoteur_hub.php';
|
||
}
|
||
|
||
if (fxPromoteurHubIsAvailable($intComId)) {
|
||
return true;
|
||
}
|
||
|
||
return fxInscrGestionUserHasPromoteurMenu($intComId);
|
||
}
|
||
|
||
/** MSIN-4401 — URL hub si dispo, sinon Mon compte. */
|
||
function fxInscrGestionFullAccountUrl($strLangue) {
|
||
global $vDomaine;
|
||
|
||
$strLangue = ($strLangue === 'en') ? 'en' : 'fr';
|
||
$intComId = intval($_SESSION['com_id'] ?? 0);
|
||
|
||
if (!function_exists('fxPromoteurHubIsAvailable')) {
|
||
require_once __DIR__ . '/inc_fx_promoteur_hub.php';
|
||
}
|
||
|
||
if ($intComId > 0 && fxPromoteurHubIsAvailable($intComId)) {
|
||
return fxPromoteurHubUrl($strLangue);
|
||
}
|
||
|
||
return $vDomaine . ($strLangue === 'en' ? '/account' : '/compte');
|
||
}
|
||
|
||
/** MSIN-4401 — Bouton « Retour au tableau de bord / Mon compte » (rendu dans inc_header sur /mobile). */
|
||
function fxInscrGestionRenderFullAccountLink($strLangue) {
|
||
// Conservé pour appels éventuels ; l'UI principale est dans inc_header (zone Mon compte / Déconnexion).
|
||
$intComId = intval($_SESSION['com_id'] ?? 0);
|
||
if (!fxInscrGestionShouldShowFullAccountLink($intComId)) {
|
||
return;
|
||
}
|
||
|
||
$strLangue = ($strLangue === 'en') ? 'en' : 'fr';
|
||
$strUrl = fxInscrGestionFullAccountUrl($strLangue);
|
||
|
||
if (!function_exists('fxPromoteurHubIsAvailable')) {
|
||
require_once __DIR__ . '/inc_fx_promoteur_hub.php';
|
||
}
|
||
$blnHub = ($intComId > 0 && fxPromoteurHubIsAvailable($intComId));
|
||
$strLabel = $blnHub
|
||
? fxInscrGestionT('inscr_gestion_back_full_account')
|
||
: fxInscrGestionT('inscr_gestion_back_mon_compte');
|
||
|
||
echo '<div class="inscr-gestion-full-account">';
|
||
echo '<a class="btn btn-outline-secondary btn-sm rounded-0" href="' . fxInscrGestionEsc($strUrl) . '">';
|
||
echo '<i class="fa fa-th-large mr-2" aria-hidden="true"></i>' . fxInscrGestionEsc($strLabel);
|
||
echo '</a>';
|
||
echo '</div>';
|
||
}
|
||
|
||
function fxInscrGestionRenderPageHead($strTitle, $strSub = '') {
|
||
echo '<div class="inscr-gestion-page-head">';
|
||
echo '<h1 class="inscr-gestion-page-title">' . fxInscrGestionEsc($strTitle) . '</h1>';
|
||
if (trim((string)$strSub) !== '') {
|
||
echo '<p class="inscr-gestion-page-sub text-muted mb-0">' . fxInscrGestionEsc($strSub) . '</p>';
|
||
}
|
||
echo '</div>';
|
||
}
|
||
|
||
function fxInscrGestionRenderSectionTitle($strLabel, $strIconClass) {
|
||
echo '<h2 class="inscr-gestion-fiche-section__title">';
|
||
echo '<i class="fa ' . fxInscrGestionEsc($strIconClass) . ' inscr-gestion-fiche-section__icon" aria-hidden="true"></i>';
|
||
echo '<span>' . fxInscrGestionEsc($strLabel) . '</span>';
|
||
echo '</h2>';
|
||
}
|
||
|
||
function fxInscrGestionRenderFicheSectionStart($strModifier, $strLabel, $strIconClass, $blnOpen = true) {
|
||
echo '<details class="inscr-gestion-fiche-section inscr-gestion-fiche-section--' . fxInscrGestionEsc($strModifier) . '"';
|
||
if ($blnOpen) {
|
||
echo ' open';
|
||
}
|
||
echo '>';
|
||
echo '<summary class="inscr-gestion-fiche-section__title">';
|
||
echo '<i class="fa ' . fxInscrGestionEsc($strIconClass) . ' inscr-gestion-fiche-section__icon" aria-hidden="true"></i>';
|
||
echo '<span>' . fxInscrGestionEsc($strLabel) . '</span>';
|
||
echo '</summary>';
|
||
echo '<div class="inscr-gestion-fiche-section__body">';
|
||
}
|
||
|
||
function fxInscrGestionRenderFicheSectionEnd() {
|
||
echo '</div></details>';
|
||
}
|
||
|
||
function fxInscrGestionRenderPanelSummary($strLabel, $strIconClass) {
|
||
echo '<i class="fa ' . fxInscrGestionEsc($strIconClass) . ' inscr-gestion-panel__icon" aria-hidden="true"></i>';
|
||
echo '<span>' . fxInscrGestionEsc($strLabel) . '</span>';
|
||
}
|
||
|
||
function fxInscrGestionParseEveId($strRaw) {
|
||
if ($strRaw === null || $strRaw === '') {
|
||
return 0;
|
||
}
|
||
$strDecoded = base64_decode(urldecode($strRaw), true);
|
||
if ($strDecoded === false || $strDecoded === '') {
|
||
return 0;
|
||
}
|
||
|
||
return (int)$strDecoded;
|
||
}
|
||
|
||
/**
|
||
* MSIN-4401 — Codes statut de course soumis (GET sel_rech_statut[]), whitelistes.
|
||
* Tableau vide = aucun filtre (tous les statuts).
|
||
*/
|
||
function fxInscrGestionParseStatutFilter($mixRaw) {
|
||
if (!is_array($mixRaw)) {
|
||
return array();
|
||
}
|
||
|
||
$tabOut = array();
|
||
foreach ($mixRaw as $mixCode) {
|
||
$strCode = strtoupper(trim((string)$mixCode));
|
||
$strCode = preg_replace('/[^A-Z0-9_]/', '', $strCode);
|
||
if ($strCode !== '') {
|
||
$tabOut[$strCode] = $strCode;
|
||
}
|
||
}
|
||
|
||
return array_values($tabOut);
|
||
}
|
||
|
||
function fxInscrGestionParseRequest() {
|
||
$arr = array(
|
||
'rech_prenom' => isset($_GET['rech_prenom']) ? trim($_GET['rech_prenom']) : '',
|
||
'rech_nom' => isset($_GET['rech_nom']) ? trim($_GET['rech_nom']) : '',
|
||
'rech_no_commande' => isset($_GET['rech_no_commande']) ? trim($_GET['rech_no_commande']) : '',
|
||
'rech_nom_equipe' => isset($_GET['rech_nom_equipe']) ? trim($_GET['rech_nom_equipe']) : '',
|
||
// MSIN-4399 — Match dossard : exact (défaut) | partial.
|
||
'bib_match' => (isset($_GET['bib_match']) && $_GET['bib_match'] === 'partial') ? 'partial' : 'exact',
|
||
'chk_bib' => !empty($_GET['chk_bib']) ? 1 : 0,
|
||
'chk_cancelled' => !empty($_GET['chk_cancelled']) ? 1 : 0,
|
||
'sel_rech_epreuve' => 0,
|
||
// MSIN-4401 — Filtre multi statut de course (vide = tous).
|
||
'sel_rech_statut' => fxInscrGestionParseStatutFilter(
|
||
isset($_GET['sel_rech_statut']) ? $_GET['sel_rech_statut'] : array()
|
||
),
|
||
'pg' => isset($_GET['pg']) ? max(1, intval($_GET['pg'])) : 1,
|
||
'pec_id' => isset($_GET['pec_id']) ? intval($_GET['pec_id']) : 0,
|
||
// MSIN-4436 — Page d'origine (ex. gestion dossards) pour le bouton retour de la fiche.
|
||
'inscr_return' => isset($_GET['inscr_return']) ? trim((string)$_GET['inscr_return']) : '',
|
||
// q = 1 lorsque l'utilisateur a lance une recherche/filtre. La liste reste vide tant que q = 0.
|
||
'q' => 0,
|
||
);
|
||
|
||
if (!empty($_GET['sel_rech_epreuve'])) {
|
||
$decoded = base64_decode(urldecode($_GET['sel_rech_epreuve']), true);
|
||
if ($decoded !== false && $decoded !== '') {
|
||
$arr['sel_rech_epreuve'] = intval($decoded);
|
||
} else {
|
||
$arr['sel_rech_epreuve'] = intval($_GET['sel_rech_epreuve']);
|
||
}
|
||
}
|
||
|
||
// Recherche "active" si : bouton Rechercher/Filtres soumis, marqueur q transporte par les liens,
|
||
// ou au moins un critere present (utile pour le retour depuis une fiche / la pagination).
|
||
$blnSubmit = !empty($_GET['btn_recherche']) || !empty($_GET['apply_filters']) || !empty($_GET['q']);
|
||
$blnCriteria = ($arr['rech_prenom'] !== '' || $arr['rech_nom'] !== '' || $arr['rech_no_commande'] !== ''
|
||
|| $arr['rech_nom_equipe'] !== '' || $arr['sel_rech_epreuve'] > 0 || $arr['chk_bib'] || $arr['chk_cancelled']
|
||
|| count($arr['sel_rech_statut']) > 0);
|
||
$arr['q'] = ($blnSubmit || $blnCriteria) ? 1 : 0;
|
||
|
||
return $arr;
|
||
}
|
||
|
||
function fxInscrGestionFilterActiveCount($arrReq) {
|
||
$int = 0;
|
||
if ($arrReq['sel_rech_epreuve'] > 0) {
|
||
$int++;
|
||
}
|
||
if ($arrReq['chk_bib']) {
|
||
$int++;
|
||
}
|
||
if ($arrReq['chk_cancelled']) {
|
||
$int++;
|
||
}
|
||
if (!empty($arrReq['sel_rech_statut']) && is_array($arrReq['sel_rech_statut']) && count($arrReq['sel_rech_statut']) > 0) {
|
||
$int++;
|
||
}
|
||
return $int;
|
||
}
|
||
|
||
function fxInscrGestionEmptyListRequest() {
|
||
return array(
|
||
'rech_prenom' => '',
|
||
'rech_nom' => '',
|
||
'rech_no_commande' => '',
|
||
'rech_nom_equipe' => '',
|
||
'bib_match' => 'exact',
|
||
'chk_bib' => 0,
|
||
'chk_cancelled' => 0,
|
||
'sel_rech_epreuve' => 0,
|
||
'sel_rech_statut' => array(),
|
||
'pg' => 1,
|
||
'pec_id' => 0,
|
||
'inscr_return' => '',
|
||
'q' => 0,
|
||
);
|
||
}
|
||
|
||
function fxInscrGestionSearchResetUrl($strBaseUrl, $intEveId) {
|
||
// MSIN-4399 — Réinit = champs vides + toutes les inscriptions (q=1).
|
||
// Distinct de la 1re visite (sans q) qui n'affiche que l'invite.
|
||
return fxInscrGestionBuildUrl($strBaseUrl, $intEveId, fxInscrGestionEmptyListRequest(), array('pg' => 1, 'q' => 1));
|
||
}
|
||
|
||
function fxInscrGestionBuildWhere($intEveId, $arrReq) {
|
||
global $objDatabase;
|
||
|
||
$strWhere = ' e.eve_id = ' . intval($intEveId);
|
||
|
||
if ($arrReq['rech_nom'] !== '') {
|
||
$strWhere .= ' AND p.par_nom LIKE "%' . $objDatabase->fxEscape($arrReq['rech_nom']) . '%"';
|
||
}
|
||
if ($arrReq['rech_prenom'] !== '') {
|
||
$strWhere .= ' AND p.par_prenom LIKE "%' . $objDatabase->fxEscape($arrReq['rech_prenom']) . '%"';
|
||
}
|
||
if ($arrReq['rech_no_commande'] !== '') {
|
||
$strEscCmd = $objDatabase->fxEscape($arrReq['rech_no_commande']);
|
||
$strWhere .= ' AND (e.no_commande LIKE "%' . $strEscCmd . '%" OR p.no_commande LIKE "%' . $strEscCmd . '%")';
|
||
}
|
||
if ($arrReq['rech_nom_equipe'] !== '') {
|
||
$strEscBibEquipe = $objDatabase->fxEscape($arrReq['rech_nom_equipe']);
|
||
// MSIN-4399 — Exact = égalité TRIM (défaut) ; Partiel = LIKE %…%.
|
||
if (empty($arrReq['bib_match']) || $arrReq['bib_match'] !== 'partial') {
|
||
$strWhere .= ' AND ('
|
||
. " TRIM(IFNULL(p.no_bib, '')) = '" . $strEscBibEquipe . "'"
|
||
. " OR TRIM(IFNULL(e.no_equipe, '')) = '" . $strEscBibEquipe . "'"
|
||
. " OR TRIM(IFNULL(e.pec_nom_equipe, '')) = '" . $strEscBibEquipe . "'"
|
||
. " OR TRIM(IFNULL(p.par_nom_equipe, '')) = '" . $strEscBibEquipe . "'"
|
||
. ')';
|
||
} else {
|
||
$strWhere .= ' AND ('
|
||
. ' TRIM(p.no_bib) LIKE "%' . $strEscBibEquipe . '%"'
|
||
. ' OR TRIM(e.no_equipe) LIKE "%' . $strEscBibEquipe . '%"'
|
||
. ' OR e.pec_nom_equipe LIKE "%' . $strEscBibEquipe . '%"'
|
||
. ' OR p.par_nom_equipe LIKE "%' . $strEscBibEquipe . '%"'
|
||
. ')';
|
||
}
|
||
}
|
||
if ($arrReq['sel_rech_epreuve'] > 0) {
|
||
$strWhere .= ' AND p.epr_id = ' . intval($arrReq['sel_rech_epreuve']) . ' AND p.epr_id = e.epr_id';
|
||
}
|
||
// MSIN-4401 — « Sans dossard » = le numéro affiché en liste (solo: p.no_bib, équipe: e.no_equipe).
|
||
if ($arrReq['chk_bib']) {
|
||
$strWhere .= ' AND ('
|
||
. " (IFNULL(e.pec_equipe, 0) = 0 AND TRIM(IFNULL(p.no_bib, '')) = '')"
|
||
. " OR (IFNULL(e.pec_equipe, 0) = 1 AND TRIM(IFNULL(e.no_equipe, '')) = '')"
|
||
. ')';
|
||
}
|
||
if ($arrReq['chk_cancelled']) {
|
||
$strWhere .= ' AND e.is_cancelled = 1';
|
||
} else {
|
||
$strWhere .= ' AND e.is_cancelled = 0 AND e.pec_actif = 1';
|
||
}
|
||
|
||
// MSIN-4401 — Filtre statut de course (vide = tous). CONF inclut aussi les valeurs vides (défaut).
|
||
if (!empty($arrReq['sel_rech_statut']) && is_array($arrReq['sel_rech_statut'])) {
|
||
$tabCodes = array();
|
||
foreach ($arrReq['sel_rech_statut'] as $strCode) {
|
||
$strCode = strtoupper(trim((string)$strCode));
|
||
$strCode = preg_replace('/[^A-Z0-9_]/', '', $strCode);
|
||
if ($strCode !== '') {
|
||
$tabCodes[$strCode] = "'" . $objDatabase->fxEscape($strCode) . "'";
|
||
}
|
||
}
|
||
if (count($tabCodes) > 0) {
|
||
$strIn = implode(',', $tabCodes);
|
||
if (isset($tabCodes['CONF'])) {
|
||
$strWhere .= ' AND (p.par_statut_course IN (' . $strIn . ')'
|
||
. " OR TRIM(IFNULL(p.par_statut_course, '')) = '')";
|
||
} else {
|
||
$strWhere .= ' AND p.par_statut_course IN (' . $strIn . ')';
|
||
}
|
||
}
|
||
}
|
||
|
||
return $strWhere;
|
||
}
|
||
|
||
function fxInscrGestionFetchRows($intEveId, $arrReq) {
|
||
global $objDatabase;
|
||
|
||
$strWhere = fxInscrGestionBuildWhere($intEveId, $arrReq);
|
||
// MSIN-4459 — pec_nom_equipe pour affichage du nom d'équipe en liste (1 ligne = 1 commande).
|
||
$sql = 'SELECT p.no_bib, p.no_bib_remis, p.no_bib_remis_date, p.no_bib_remis_par, p.par_statut_course, p.par_id, p.par_equipe,'
|
||
. ' p.par_prenom, p.par_nom, e.pec_id_original, e.epr_id, e.eve_id, e.pec_id, e.is_cancelled, e.pec_actif, p.rol_id,'
|
||
. ' e.no_commande, e.no_panier, e.no_equipe, e.pec_equipe, e.pec_nom_equipe'
|
||
. ' FROM resultats_epreuves_commandees e, resultats_participants p'
|
||
. ' WHERE ' . $strWhere . ' AND p.pec_id = e.pec_id_original'
|
||
. ' GROUP BY e.pec_id_original'
|
||
. ' ORDER BY p.par_nom, p.par_prenom';
|
||
|
||
return $objDatabase->fxGetResults($sql);
|
||
}
|
||
|
||
/**
|
||
* MSIN-4459 — Membres d'équipe pour enrichir la liste (sous le capitaine).
|
||
* Une requête batch par page ; exclut rol_id 3/4 (comme promoteur).
|
||
*
|
||
* @param array $arrPecIds pec_id_original
|
||
* @return array map pec_id => liste de participants (capitaine en premier si present)
|
||
*/
|
||
function fxInscrGestionFetchTeamMembersByPecs($arrPecIds) {
|
||
global $objDatabase;
|
||
|
||
$arrIds = array();
|
||
if (is_array($arrPecIds)) {
|
||
foreach ($arrPecIds as $intPec) {
|
||
$intPec = intval($intPec);
|
||
if ($intPec > 0) {
|
||
$arrIds[$intPec] = $intPec;
|
||
}
|
||
}
|
||
}
|
||
if (count($arrIds) === 0) {
|
||
return array();
|
||
}
|
||
|
||
$sql = 'SELECT pec_id, par_id, par_nom, par_prenom, rol_id, no_bib_remis, par_statut_course'
|
||
. ' FROM resultats_participants'
|
||
. ' WHERE pec_id IN (' . implode(',', $arrIds) . ')'
|
||
. ' AND rol_id NOT IN (3, 4)'
|
||
. ' ORDER BY pec_id, CASE WHEN rol_id = 1 THEN 0 ELSE 1 END, par_nom, par_prenom';
|
||
|
||
$tab = $objDatabase->fxGetResults($sql);
|
||
$map = array();
|
||
if (!is_array($tab) || count($tab) === 0) {
|
||
return $map;
|
||
}
|
||
|
||
$intNb = count($tab);
|
||
for ($i = 1; $i <= $intNb; $i++) {
|
||
$intPec = intval($tab[$i]['pec_id']);
|
||
if (!isset($map[$intPec])) {
|
||
$map[$intPec] = array();
|
||
}
|
||
$map[$intPec][] = $tab[$i];
|
||
}
|
||
|
||
return $map;
|
||
}
|
||
|
||
function fxInscrGestionFetchRowByPec($intPecId, $intEveId) {
|
||
global $objDatabase;
|
||
|
||
$sql = 'SELECT p.no_bib, p.no_bib_remis, p.no_bib_remis_date, p.no_bib_remis_par, p.par_statut_course, p.par_id, p.par_equipe,'
|
||
. ' p.par_prenom, p.par_nom, p.par_id_original, p.ct_entry_id, e.pec_id_original, e.epr_id, e.eve_id, e.pec_id, e.is_cancelled, e.pec_actif, p.rol_id,'
|
||
. ' e.no_commande, e.no_panier, e.no_equipe, e.pec_equipe'
|
||
. ' FROM resultats_epreuves_commandees e, resultats_participants p'
|
||
. ' WHERE e.eve_id = ' . intval($intEveId)
|
||
. ' AND e.pec_id_original = ' . intval($intPecId)
|
||
. ' AND p.pec_id = e.pec_id_original'
|
||
. ' LIMIT 1';
|
||
|
||
return $objDatabase->fxGetRow($sql);
|
||
}
|
||
|
||
/**
|
||
* Politique de gestion des dossards en double pour un evenement.
|
||
* Pour l'instant : toujours 'warn' (avertissement non bloquant).
|
||
*
|
||
* Hook prevu pour le futur : lire un reglage par evenement afin de renvoyer
|
||
* 'block' (interdiction totale) ou 'allow' (doublons silencieux). Exemple :
|
||
* $rec = fxGetEvenementsId($intEveId);
|
||
* if (!empty($rec['eve_bib_doublon_interdit'])) return 'block';
|
||
* if (!empty($rec['eve_bib_doublon_autorise'])) return 'allow';
|
||
*
|
||
* @return string 'warn' | 'block' | 'allow'
|
||
*/
|
||
function fxInscrGestionBibDuplicatePolicy($intEveId) {
|
||
return 'warn';
|
||
}
|
||
|
||
/**
|
||
* Participants d'un evenement deja porteurs du dossard donne (hors la commande
|
||
* courante). Alimente le garde-fou "dossard en double".
|
||
*
|
||
* @param bool $blnTeam true = dossard d'equipe (e.no_equipe), false = individuel (p.no_bib)
|
||
* @return array liste de lignes ['par_nom','par_prenom','epr_id']
|
||
*/
|
||
function fxInscrGestionFindBibDuplicates($intEveId, $strNoBib, $intExceptPec, $blnTeam, $intCurrentEprId = 0) {
|
||
global $objDatabase;
|
||
|
||
$strNoBib = trim((string)$strNoBib);
|
||
$intCurrentEprId = (int)$intCurrentEprId;
|
||
|
||
if (intval($intEveId) <= 0 || $strNoBib === '') {
|
||
return array();
|
||
}
|
||
|
||
$strBibCol = $blnTeam ? 'e.no_equipe' : 'p.no_bib';
|
||
$sql = 'SELECT p.par_nom, p.par_prenom, e.epr_id'
|
||
. ' FROM resultats_epreuves_commandees e, resultats_participants p'
|
||
. ' WHERE e.eve_id = ' . intval($intEveId)
|
||
. ' AND p.pec_id = e.pec_id_original'
|
||
. ' AND e.is_cancelled = 0 AND e.pec_actif = 1'
|
||
. " AND TRIM(" . $strBibCol . ") = '" . $objDatabase->fxEscape($strNoBib) . "'"
|
||
. ' AND e.pec_id_original <> ' . intval($intExceptPec)
|
||
. ' GROUP BY e.pec_id_original'
|
||
. ' ORDER BY p.par_nom, p.par_prenom';
|
||
|
||
$tab = $objDatabase->fxGetResults($sql);
|
||
if (!is_array($tab) || empty($tab)) {
|
||
return array();
|
||
}
|
||
|
||
$blnAllowInter = function_exists('fxBibEventAllowsInterEprDuplicates')
|
||
&& fxBibEventAllowsInterEprDuplicates($intEveId);
|
||
|
||
$arrOut = array();
|
||
foreach ($tab as $rowDup) {
|
||
$intDupEprId = (int)($rowDup['epr_id'] ?? 0);
|
||
|
||
if ($intCurrentEprId > 0 && $intDupEprId !== $intCurrentEprId && $blnAllowInter) {
|
||
continue;
|
||
}
|
||
|
||
$arrOut[] = $rowDup;
|
||
}
|
||
|
||
return $arrOut;
|
||
}
|
||
|
||
function fxInscrGestionEpreuveLabel($intEprId, $strLangue) {
|
||
$tab = fxGetEpreuve($intEprId);
|
||
if ($tab === null) {
|
||
return '';
|
||
}
|
||
|
||
$str = '';
|
||
if (trim($tab['epr_categorie_' . $strLangue]) !== '') {
|
||
$str .= $tab['epr_categorie_' . $strLangue] . ' — ';
|
||
}
|
||
$str .= $tab['epr_type_' . $strLangue];
|
||
if (trim($tab['epr_nom_' . $strLangue]) !== '') {
|
||
$str .= ' — ' . $tab['epr_nom_' . $strLangue];
|
||
}
|
||
|
||
return fxUnescape($str);
|
||
}
|
||
|
||
function fxInscrGestionBibDisplay($arrRow) {
|
||
if (intval($arrRow['pec_equipe']) === 1) {
|
||
return fxShowBibNumber($arrRow['no_equipe'], $arrRow['epr_id'], 'equipe');
|
||
}
|
||
return fxShowBibNumber($arrRow['no_bib'], $arrRow['epr_id'], 'participant');
|
||
}
|
||
|
||
/** Mode rendu fiche condensee (grille 2 colonnes dans l'onglet Details). */
|
||
function fxInscrGestionKvFicheCardMode($blnSet = null) {
|
||
static $blnMode = false;
|
||
if ($blnSet !== null) {
|
||
$blnMode = (bool)$blnSet;
|
||
}
|
||
return $blnMode;
|
||
}
|
||
|
||
/** Valeur longue ou courriel : occupe toute la largeur de la fiche. */
|
||
function fxInscrGestionKvValueWantsFullWidth($strValue, $blnHtml = false) {
|
||
if ($blnHtml) {
|
||
$strPlain = trim(strip_tags((string)$strValue));
|
||
} else {
|
||
$strPlain = trim((string)$strValue);
|
||
}
|
||
if ($strPlain === '') {
|
||
return false;
|
||
}
|
||
$intLen = function_exists('mb_strlen') ? mb_strlen($strPlain) : strlen($strPlain);
|
||
|
||
return $intLen > 48;
|
||
}
|
||
|
||
function fxInscrGestionRenderKvListOpen($blnFicheCard = false) {
|
||
fxInscrGestionKvFicheCardMode($blnFicheCard);
|
||
$strClass = 'inscr-gestion-kv-list';
|
||
if ($blnFicheCard) {
|
||
$strClass .= ' inscr-gestion-fiche-card';
|
||
}
|
||
echo '<div class="' . fxInscrGestionEsc($strClass) . '">';
|
||
}
|
||
|
||
function fxInscrGestionRenderKvListClose() {
|
||
fxInscrGestionKvFicheCardMode(false);
|
||
echo '</div>';
|
||
}
|
||
|
||
function fxInscrGestionRenderKvRow($strLabel, $strValue, $blnHtml = false, $strExtraClass = '') {
|
||
if (!$blnHtml && trim((string)$strValue) === '') {
|
||
return;
|
||
}
|
||
|
||
$strClass = 'inscr-gestion-kv-row';
|
||
if ($strExtraClass !== '') {
|
||
$strClass .= ' ' . $strExtraClass;
|
||
}
|
||
if (fxInscrGestionKvFicheCardMode() && fxInscrGestionKvValueWantsFullWidth($strValue, $blnHtml)) {
|
||
$strClass .= ' inscr-gestion-kv-row--full';
|
||
}
|
||
|
||
echo '<div class="' . fxInscrGestionEsc($strClass) . '">';
|
||
echo '<div class="inscr-gestion-kv-row__label">' . fxInscrGestionEsc($strLabel) . '</div>';
|
||
echo '<div class="inscr-gestion-kv-row__value">';
|
||
if ($blnHtml) {
|
||
echo $strValue;
|
||
} else {
|
||
echo fxInscrGestionEsc($strValue);
|
||
}
|
||
echo '</div></div>';
|
||
}
|
||
|
||
function fxInscrGestionFetchFicheProfile($arrRow, $strLangue) {
|
||
global $objDatabase;
|
||
|
||
$intParId = (int)$arrRow['par_id'];
|
||
$intPecId = (int)$arrRow['pec_id_original'];
|
||
$intEveId = (int)$arrRow['eve_id'];
|
||
$strLangCol = ($strLangue === 'en') ? 'en' : 'fr';
|
||
|
||
// Le titre "Capitaine" n'a de sens qu'en epreuve d'equipe : meme condition que le champ "Type"
|
||
// de la fiche (cf. fxInscrGestionRenderFiche). En individuel, on ne l'affiche pas, meme si rol_id=1.
|
||
$blnEquipe = (intval($arrRow['pec_equipe']) === 1)
|
||
&& fxIsParEquipe((int)$arrRow['pec_id_original'], (int)$arrRow['epr_id']);
|
||
$blnCapitaine = ((int)$arrRow['rol_id'] === 1) && $blnEquipe;
|
||
|
||
$tabProfile = $objDatabase->fxGetRow(
|
||
'SELECT p.par_courriel, p.par_naissance, p.par_sexe, p.par_id_original'
|
||
. ' FROM resultats_participants p'
|
||
. ' WHERE p.par_id = ' . $intParId
|
||
. ' LIMIT 1'
|
||
);
|
||
|
||
if ($tabProfile === null) {
|
||
return array(
|
||
'bln_capitaine' => $blnCapitaine,
|
||
'email' => '',
|
||
'birth_gender' => '',
|
||
'questions' => array(),
|
||
);
|
||
}
|
||
|
||
$strEmail = trim((string)$tabProfile['par_courriel']);
|
||
|
||
$strBirthGender = '';
|
||
$strNaissance = trim((string)$tabProfile['par_naissance']);
|
||
if ($strNaissance !== '' && $strNaissance !== '0000-00-00') {
|
||
$strBirthGender = fxShowDate($strNaissance, $strLangue)
|
||
. ' - '
|
||
. fxGetGender($tabProfile['par_sexe'], $strLangue);
|
||
}
|
||
|
||
$intParIdOriginal = (int)$tabProfile['par_id_original'];
|
||
$sqlQuestions = 'SELECT rq.que_choix_' . $strLangCol . ' AS que_choix,'
|
||
. ' iq.que_cart_label_' . $strLangCol . ' AS que_label,'
|
||
. ' rq.que_question_' . $strLangCol . ' AS que_question,'
|
||
. ' iq.que_tri, iq.que_id'
|
||
. ' FROM resultats_questions rq'
|
||
. ' INNER JOIN inscriptions_questions iq ON iq.que_id = rq.que_id'
|
||
. ' WHERE iq.que_cart_show = 1'
|
||
. ' AND iq.eve_id = ' . $intEveId
|
||
. ' AND rq.pec_id = ' . $intPecId
|
||
. ' AND (rq.par_id = ' . $intParId . ' OR rq.par_id = ' . $intParIdOriginal . ')'
|
||
. ' AND rq.que_actif = 1'
|
||
. " AND TRIM(COALESCE(rq.que_choix_" . $strLangCol . ", '')) <> ''"
|
||
. ' ORDER BY iq.que_tri, iq.que_id';
|
||
|
||
$tabQuestionsRaw = $objDatabase->fxGetResults($sqlQuestions);
|
||
$tabQuestions = array();
|
||
|
||
if ($tabQuestionsRaw !== null) {
|
||
for ($i = 1; $i <= count($tabQuestionsRaw); $i++) {
|
||
$tabQ = $tabQuestionsRaw[$i];
|
||
$strLabel = trim(fxUnescape($tabQ['que_label']));
|
||
if ($strLabel === '') {
|
||
$strLabel = trim(fxUnescape($tabQ['que_question']));
|
||
}
|
||
$strValue = trim(fxUnescape($tabQ['que_choix']));
|
||
if ($strLabel === '' || $strValue === '') {
|
||
continue;
|
||
}
|
||
$tabQuestions[] = array(
|
||
'label' => $strLabel,
|
||
'value' => $strValue,
|
||
);
|
||
}
|
||
}
|
||
|
||
return array(
|
||
'bln_capitaine' => $blnCapitaine,
|
||
'email' => $strEmail,
|
||
'birth_gender' => $strBirthGender,
|
||
'questions' => $tabQuestions,
|
||
);
|
||
}
|
||
|
||
function fxInscrGestionRenderKvRowFlag($strText, $strExtraClass = '') {
|
||
$strClass = 'inscr-gestion-kv-row inscr-gestion-kv-row--flag';
|
||
if ($strExtraClass !== '') {
|
||
$strClass .= ' ' . $strExtraClass;
|
||
}
|
||
if (fxInscrGestionKvFicheCardMode()) {
|
||
$strClass .= ' inscr-gestion-kv-row--full';
|
||
}
|
||
echo '<div class="' . fxInscrGestionEsc($strClass) . '">';
|
||
echo '<span class="inscr-gestion-kv-flag">' . fxInscrGestionEsc($strText) . '</span>';
|
||
echo '</div>';
|
||
}
|
||
|
||
function fxInscrGestionRenderFicheProfileKvRows($arrRow, $strLangue) {
|
||
$tabProfile = fxInscrGestionFetchFicheProfile($arrRow, $strLangue);
|
||
$blnFirstProfile = true;
|
||
$strProfileSep = 'inscr-gestion-kv-row--profile';
|
||
|
||
if ($tabProfile['bln_capitaine']) {
|
||
$strCaptainLabel = trim(fxInscrGestionT('participant-titre-capitaine'));
|
||
if ($strCaptainLabel === '') {
|
||
$strCaptainLabel = ($strLangue === 'fr') ? 'Capitaine' : 'Captain';
|
||
}
|
||
fxInscrGestionRenderKvRowFlag(
|
||
$strCaptainLabel,
|
||
$blnFirstProfile ? $strProfileSep : ''
|
||
);
|
||
$blnFirstProfile = false;
|
||
}
|
||
|
||
if ($tabProfile['email'] !== '') {
|
||
fxInscrGestionRenderKvRow(
|
||
fxInscrGestionT('inscr_gestion_email'),
|
||
$tabProfile['email'],
|
||
false,
|
||
$blnFirstProfile ? 'inscr-gestion-kv-row--profile' : ''
|
||
);
|
||
$blnFirstProfile = false;
|
||
}
|
||
|
||
if ($tabProfile['birth_gender'] !== '') {
|
||
fxInscrGestionRenderKvRow(
|
||
fxInscrGestionT('inscr_gestion_birthdate'),
|
||
$tabProfile['birth_gender'],
|
||
false,
|
||
$blnFirstProfile ? 'inscr-gestion-kv-row--profile' : ''
|
||
);
|
||
$blnFirstProfile = false;
|
||
}
|
||
|
||
foreach ($tabProfile['questions'] as $tabQuestion) {
|
||
fxInscrGestionRenderKvRow(
|
||
$tabQuestion['label'],
|
||
$tabQuestion['value'],
|
||
false,
|
||
$blnFirstProfile ? 'inscr-gestion-kv-row--profile' : ''
|
||
);
|
||
$blnFirstProfile = false;
|
||
}
|
||
}
|
||
|
||
/** Code statut course par défaut (Confirmé). */
|
||
function fxInscrGestionParStatutCourseDefault() {
|
||
return 'CONF';
|
||
}
|
||
|
||
function fxInscrGestionResolveParStatutCourse($strCurrent) {
|
||
$strCurrent = trim((string)$strCurrent);
|
||
return $strCurrent !== '' ? $strCurrent : fxInscrGestionParStatutCourseDefault();
|
||
}
|
||
|
||
/**
|
||
* MSIN-4399 — Libellé affichable du statut de course (Confirmé, DNS, …).
|
||
* Valeur vide → Confirmé (même défaut que la fiche).
|
||
*/
|
||
function fxInscrGestionStatutCourseLabel($strCode, $strLangue) {
|
||
$strCode = fxInscrGestionResolveParStatutCourse($strCode);
|
||
$tabOptions = fxInscrGestionGetStatutCourseOptions($strLangue);
|
||
foreach ($tabOptions as $arrOpt) {
|
||
if (trim((string)$arrOpt['info_option2']) === $strCode) {
|
||
$strLabel = trim((string)$arrOpt['info_texte']);
|
||
return $strLabel !== '' ? $strLabel : $strCode;
|
||
}
|
||
}
|
||
|
||
return $strCode;
|
||
}
|
||
|
||
/** Options statut course depuis info (info_description = opt). */
|
||
function fxInscrGestionGetStatutCourseOptions($strLangue) {
|
||
global $objDatabase;
|
||
|
||
static $tabCache = array();
|
||
if (isset($tabCache[$strLangue])) {
|
||
return $tabCache[$strLangue];
|
||
}
|
||
|
||
$sql = "SELECT info_texte, info_option2 FROM info"
|
||
. " WHERE info_actif = 1 AND info_clef = 'inscr_statut_course'"
|
||
. " AND info_langue = '" . $objDatabase->fxEscape($strLangue) . "'"
|
||
. " AND info_description = 'opt'"
|
||
. " ORDER BY info_trie";
|
||
$tabRows = $objDatabase->fxGetResults($sql);
|
||
$tabCache[$strLangue] = ($tabRows !== null) ? $tabRows : array();
|
||
|
||
return $tabCache[$strLangue];
|
||
}
|
||
|
||
function fxInscrGestionRenderStatutField($intParId, $strCurrent, $intEveId, $strLangue) {
|
||
$tabOptions = fxInscrGestionGetStatutCourseOptions($strLangue);
|
||
if (count($tabOptions) === 0) {
|
||
echo '<div class="inscr-gestion-muted">' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_statut_label')) . '</div>';
|
||
return;
|
||
}
|
||
|
||
$strCurrent = fxInscrGestionResolveParStatutCourse($strCurrent);
|
||
$strSelectId = 'inscr_statut_' . (int)$intParId;
|
||
|
||
echo '<select class="form-control rounded-0 inscr-gestion-statut-select" id="' . fxInscrGestionEsc($strSelectId) . '"'
|
||
. ' data-par_id="' . (int)$intParId . '"'
|
||
. ' data-eve_id="' . rawurlencode(base64_encode((string)$intEveId)) . '">';
|
||
|
||
foreach ($tabOptions as $arrOpt) {
|
||
$strCode = trim((string)$arrOpt['info_option2']);
|
||
$strLabel = trim((string)$arrOpt['info_texte']);
|
||
if ($strCode === '') {
|
||
continue;
|
||
}
|
||
|
||
echo '<option value="' . fxInscrGestionEsc($strCode) . '"'
|
||
. ($strCurrent === $strCode ? ' selected' : '') . '>'
|
||
. fxInscrGestionEsc($strLabel) . '</option>';
|
||
}
|
||
|
||
echo '</select>';
|
||
}
|
||
|
||
/**
|
||
* MSIN-4401 — Multi-sélection statut de course (mobile-friendly : panneau + cases à cocher).
|
||
* Aucune case cochée = tous les statuts.
|
||
*/
|
||
function fxInscrGestionRenderStatutFilter($arrReq, $strLangue) {
|
||
$tabOptions = fxInscrGestionGetStatutCourseOptions($strLangue);
|
||
if (count($tabOptions) === 0) {
|
||
return;
|
||
}
|
||
|
||
$tabSelected = (!empty($arrReq['sel_rech_statut']) && is_array($arrReq['sel_rech_statut']))
|
||
? $arrReq['sel_rech_statut']
|
||
: array();
|
||
$tabSelectedMap = array();
|
||
foreach ($tabSelected as $strCode) {
|
||
$tabSelectedMap[$strCode] = true;
|
||
}
|
||
$intNb = count($tabSelected);
|
||
$strSummary = ($intNb === 0)
|
||
? fxInscrGestionT('inscr_gestion_rech_statut_all')
|
||
: sprintf(fxInscrGestionT('inscr_gestion_rech_statut_n'), $intNb);
|
||
|
||
echo '<label class="inscr-gestion-label" for="inscr-gestion-statut-multi-summary">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_rech_statut')) . '</label>';
|
||
echo '<details class="inscr-gestion-statut-multi"'
|
||
. ' data-label-all="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_rech_statut_all')) . '"'
|
||
. ' data-label-n="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_rech_statut_n')) . '">';
|
||
echo '<summary class="inscr-gestion-statut-multi__summary" id="inscr-gestion-statut-multi-summary">'
|
||
. '<span class="inscr-gestion-statut-multi__summary-text">' . fxInscrGestionEsc($strSummary) . '</span>'
|
||
. '</summary>';
|
||
echo '<div class="inscr-gestion-statut-multi__panel" role="group"'
|
||
. ' aria-label="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_rech_statut')) . '">';
|
||
|
||
foreach ($tabOptions as $arrOpt) {
|
||
$strCode = trim((string)$arrOpt['info_option2']);
|
||
$strLabel = trim((string)$arrOpt['info_texte']);
|
||
if ($strCode === '') {
|
||
continue;
|
||
}
|
||
$strId = 'sel_rech_statut_' . preg_replace('/[^A-Za-z0-9_]/', '_', $strCode);
|
||
$blnChecked = isset($tabSelectedMap[$strCode]);
|
||
echo '<label class="inscr-gestion-statut-multi__opt" for="' . fxInscrGestionEsc($strId) . '">';
|
||
echo '<input type="checkbox" id="' . fxInscrGestionEsc($strId) . '"'
|
||
. ' name="sel_rech_statut[]" value="' . fxInscrGestionEsc($strCode) . '"'
|
||
. ($blnChecked ? ' checked' : '') . '>';
|
||
echo '<span>' . fxInscrGestionEsc($strLabel) . '</span>';
|
||
echo '</label>';
|
||
}
|
||
|
||
echo '</div></details>';
|
||
}
|
||
|
||
/** Paramètres GET pour liens (sans double-encodage). */
|
||
function fxInscrGestionQueryParams($intEveId, $arrReq, $arrExtra = array()) {
|
||
$tab = array(
|
||
'promoteur_eve_id' => base64_encode((string)$intEveId),
|
||
);
|
||
|
||
if ($arrReq['rech_prenom'] !== '') {
|
||
$tab['rech_prenom'] = $arrReq['rech_prenom'];
|
||
}
|
||
if ($arrReq['rech_nom'] !== '') {
|
||
$tab['rech_nom'] = $arrReq['rech_nom'];
|
||
}
|
||
if ($arrReq['rech_no_commande'] !== '') {
|
||
$tab['rech_no_commande'] = $arrReq['rech_no_commande'];
|
||
}
|
||
if ($arrReq['rech_nom_equipe'] !== '') {
|
||
$tab['rech_nom_equipe'] = $arrReq['rech_nom_equipe'];
|
||
}
|
||
// MSIN-4399 — Conserver le mode partiel dans pagination / fiche (exact = défaut, pas dans l’URL).
|
||
if (!empty($arrReq['bib_match']) && $arrReq['bib_match'] === 'partial') {
|
||
$tab['bib_match'] = 'partial';
|
||
}
|
||
if ($arrReq['chk_bib']) {
|
||
$tab['chk_bib'] = 1;
|
||
}
|
||
if ($arrReq['chk_cancelled']) {
|
||
$tab['chk_cancelled'] = 1;
|
||
}
|
||
if ($arrReq['sel_rech_epreuve'] > 0) {
|
||
$tab['sel_rech_epreuve'] = base64_encode((string)$arrReq['sel_rech_epreuve']);
|
||
}
|
||
// MSIN-4401 — Conserver le filtre multi-statut dans pagination / fiche.
|
||
if (!empty($arrReq['sel_rech_statut']) && is_array($arrReq['sel_rech_statut'])) {
|
||
$tab['sel_rech_statut'] = array_values($arrReq['sel_rech_statut']);
|
||
}
|
||
if (!empty($_GET['lng'])) {
|
||
$tab['lng'] = $_GET['lng'];
|
||
}
|
||
if (!empty($arrReq['q'])) {
|
||
// Transporte l'etat "recherche lancee" pour que la pagination et le retour de fiche gardent la liste.
|
||
$tab['q'] = 1;
|
||
}
|
||
if (!empty($arrReq['pg']) && (int)$arrReq['pg'] > 1) {
|
||
$tab['pg'] = (int)$arrReq['pg'];
|
||
}
|
||
if (!empty($arrReq['inscr_return'])) {
|
||
$tab['inscr_return'] = $arrReq['inscr_return'];
|
||
}
|
||
|
||
foreach ($arrExtra as $strKey => $mixVal) {
|
||
if ($mixVal === null || $mixVal === '' || ($strKey === 'pec_id' && (int)$mixVal === 0)) {
|
||
unset($tab[$strKey]);
|
||
} else {
|
||
$tab[$strKey] = $mixVal;
|
||
}
|
||
}
|
||
|
||
return $tab;
|
||
}
|
||
|
||
function fxInscrGestionBuildUrl($strBaseUrl, $intEveId, $arrReq, $arrExtra = array()) {
|
||
$tab = fxInscrGestionQueryParams($intEveId, $arrReq, $arrExtra);
|
||
$strQs = http_build_query($tab, '', '&');
|
||
|
||
return $strBaseUrl . ($strQs !== '' ? '?' . $strQs : '');
|
||
}
|
||
|
||
function fxInscrGestionPaginationUrl($strBaseUrl, $intEveId, $arrReq) {
|
||
$tab = fxInscrGestionQueryParams($intEveId, $arrReq, array('pec_id' => null));
|
||
unset($tab['pg'], $tab['pec_id']);
|
||
$strQs = http_build_query($tab, '', '&');
|
||
|
||
return $strBaseUrl . ($strQs !== '' ? '?' . $strQs : '?promoteur_eve_id=' . rawurlencode(base64_encode((string)$intEveId)));
|
||
}
|
||
|
||
function fxInscrGestionRenderPagination($strBaseUrl, $intEveId, $arrReq, $intTotal, $intPerPage, $intPage) {
|
||
if ($intTotal <= $intPerPage) {
|
||
return '';
|
||
}
|
||
|
||
return '<div class="inscr-gestion-pager">'
|
||
. fxGetPagination(
|
||
fxInscrGestionPaginationUrl($strBaseUrl, $intEveId, $arrReq),
|
||
$intTotal,
|
||
$intPerPage,
|
||
$intPage
|
||
)
|
||
. '</div>';
|
||
}
|
||
|
||
function fxInscrGestionExtractQrToken($strRaw) {
|
||
$strRaw = trim((string)$strRaw);
|
||
if ($strRaw === '') {
|
||
return '';
|
||
}
|
||
|
||
if (preg_match('/[?&]t=([^&\s#]+)/', $strRaw, $tabMatch)) {
|
||
return urldecode($tabMatch[1]);
|
||
}
|
||
|
||
if (preg_match('/^https?:\/\//i', $strRaw)) {
|
||
$tabParts = parse_url($strRaw);
|
||
if (!empty($tabParts['query'])) {
|
||
parse_str($tabParts['query'], $tabQuery);
|
||
if (!empty($tabQuery['t'])) {
|
||
return trim((string)$tabQuery['t']);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (preg_match('/^[a-zA-Z0-9_-]{6,64}$/', $strRaw)) {
|
||
return $strRaw;
|
||
}
|
||
|
||
return '';
|
||
}
|
||
|
||
function fxInscrGestionResolveQrScan($strRaw, $intEveId, $strLangue) {
|
||
global $objDatabase;
|
||
|
||
$strToken = fxInscrGestionExtractQrToken($strRaw);
|
||
if ($strToken === '') {
|
||
return array('state' => 'error', 'message' => fxInscrGestionT('inscr_gestion_qr_invalid'));
|
||
}
|
||
|
||
$tabToken = $objDatabase->fxGetRow(
|
||
'SELECT qr_public_id, pec_id_original, eve_id'
|
||
. ' FROM qr_tokens'
|
||
. " WHERE qr_public_id = '" . $objDatabase->fxEscape($strToken) . "'"
|
||
. ' LIMIT 1'
|
||
);
|
||
|
||
if ($tabToken === null) {
|
||
return array('state' => 'error', 'message' => fxInscrGestionT('inscr_gestion_qr_invalid'));
|
||
}
|
||
|
||
$intPecId = (int)$tabToken['pec_id_original'];
|
||
if ($intPecId <= 0) {
|
||
return array('state' => 'error', 'message' => fxInscrGestionT('inscr_gestion_qr_invalid'));
|
||
}
|
||
|
||
$tabPec = $objDatabase->fxGetRow(
|
||
'SELECT ec.pec_id_original'
|
||
. ' FROM resultats_epreuves_commandees ec'
|
||
. ' WHERE ec.pec_id_original = ' . $intPecId
|
||
. ' AND ec.eve_id = ' . intval($intEveId)
|
||
. ' AND ec.pec_actif = 1'
|
||
. ' LIMIT 1'
|
||
);
|
||
|
||
if ($tabPec !== null) {
|
||
$arrRow = fxInscrGestionFetchRowByPec($intPecId, $intEveId);
|
||
if ($arrRow === null) {
|
||
return array('state' => 'error', 'message' => fxInscrGestionT('inscr_gestion_qr_not_found'));
|
||
}
|
||
|
||
return array(
|
||
'state' => 'success',
|
||
'pec_id' => $intPecId,
|
||
);
|
||
}
|
||
|
||
$intQrEveId = (int)$tabToken['eve_id'];
|
||
if ($intQrEveId <= 0) {
|
||
$tabOther = $objDatabase->fxGetRow(
|
||
'SELECT ec.eve_id FROM resultats_epreuves_commandees ec'
|
||
. ' WHERE ec.pec_id_original = ' . $intPecId
|
||
. ' AND ec.pec_actif = 1'
|
||
. ' ORDER BY ec.pec_id_original DESC LIMIT 1'
|
||
);
|
||
if ($tabOther !== null) {
|
||
$intQrEveId = (int)$tabOther['eve_id'];
|
||
}
|
||
}
|
||
|
||
$strEveNom = '';
|
||
if ($intQrEveId > 0) {
|
||
$arrEv = fxGetEvenementsId($intQrEveId);
|
||
if (!empty($arrEv)) {
|
||
$strEveNom = fxUnescape($arrEv['eve_nom_' . $strLangue]);
|
||
}
|
||
}
|
||
|
||
if ($strEveNom === '') {
|
||
$strEveNom = '#' . $intQrEveId;
|
||
}
|
||
|
||
return array(
|
||
'state' => 'wrong_event',
|
||
'message' => str_replace('%s', $strEveNom, fxInscrGestionT('inscr_gestion_qr_wrong_event')),
|
||
'eve_nom' => $strEveNom,
|
||
);
|
||
}
|
||
|
||
function fxInscrGestionRenderEventPicker($tabEveIds, $strLangue, $strBaseUrl) {
|
||
global $objDatabase;
|
||
|
||
$strTitle = fxInscrGestionT('inscr_gestion_choose_event');
|
||
|
||
echo '<div id="module-inscr-gestion" class="inscr-gestion-shell">';
|
||
fxInscrGestionRenderPageHead($strTitle);
|
||
echo '<div class="inscr-gestion-event-list">';
|
||
|
||
foreach ($tabEveIds as $intEveId) {
|
||
$qry = 'SELECT e.* FROM inscriptions_evenements e WHERE e.eve_actif = 1 AND e.eve_id = ' . intval($intEveId);
|
||
$tabEv = $objDatabase->fxGetRow($qry);
|
||
if ($tabEv === null) {
|
||
continue;
|
||
}
|
||
|
||
$strUrl = fxInscrGestionBuildUrl($strBaseUrl, $intEveId, fxInscrGestionParseRequest(), array());
|
||
echo '<a class="inscr-gestion-event-item" href="' . fxInscrGestionEsc($strUrl) . '">';
|
||
echo '<span class="inscr-gestion-event-item__name">' . fxInscrGestionEsc(fxUnescape($tabEv['eve_nom_' . $strLangue])) . '</span>';
|
||
echo '<i class="fa fa-chevron-right" aria-hidden="true"></i>';
|
||
echo '</a>';
|
||
}
|
||
|
||
echo '</div></div>';
|
||
}
|
||
|
||
function fxInscrGestionRenderQrPanel($intEveId, $strLangue) {
|
||
global $vDomaine;
|
||
|
||
$intComId = intval($_SESSION['com_id'] ?? 0);
|
||
if (!fxInscrGestionCanScan($intComId, $intEveId)) {
|
||
return;
|
||
}
|
||
|
||
$strQrAjaxUrl = $vDomaine . '/ajax_inscr_gestion_qr.php';
|
||
|
||
echo '<details class="inscr-gestion-panel inscr-gestion-panel--qr" id="inscr-gestion-qr-panel"';
|
||
echo ' data-qr-resolve-url="' . fxInscrGestionEsc($strQrAjaxUrl) . '"';
|
||
echo ' data-qr-eve-id="' . fxInscrGestionEsc(base64_encode((string)$intEveId)) . '"';
|
||
echo ' data-qr-lang="' . fxInscrGestionEsc($strLangue) . '"';
|
||
echo ' data-qr-msg-invalid="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_qr_invalid')) . '"';
|
||
echo ' data-qr-msg-wrong="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_qr_wrong_event')) . '"';
|
||
echo ' data-qr-msg-camera="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_qr_camera_error')) . '">';
|
||
echo '<summary class="inscr-gestion-panel__head">';
|
||
fxInscrGestionRenderPanelSummary(fxInscrGestionT('inscr_gestion_qr_title'), 'fa-qrcode');
|
||
echo '</summary>';
|
||
echo '<div class="inscr-gestion-panel__body">';
|
||
echo '<p class="inscr-gestion-qr-hint text-muted small mb-2">' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_qr_hint')) . '</p>';
|
||
echo '<div id="inscr-gestion-qr-feedback" class="alert d-none mb-2" role="alert"></div>';
|
||
echo '<div id="inscr-gestion-qr-reader-wrap" class="inscr-gestion-qr-reader-wrap">';
|
||
echo '<div id="inscr-gestion-qr-reader"></div>';
|
||
echo '</div></div></details>';
|
||
}
|
||
|
||
/**
|
||
* MSIN-4401 — OCR dossard via Google Cloud Vision (serveur).
|
||
* Solution produit : ~95 % sur photos reelles vs ~50 % Tesseract.js mobile.
|
||
*/
|
||
function fxInscrGestionBibOcrCloudEnabled() {
|
||
return defined('MSIN_BIB_OCR_VISION_API_KEY')
|
||
&& trim((string)MSIN_BIB_OCR_VISION_API_KEY) !== '';
|
||
}
|
||
|
||
/** Extraire un numero de dossard depuis du texte OCR (plus long groupe 1–6 chiffres). */
|
||
function fxInscrGestionExtractBibNumberFromOcr($strRaw) {
|
||
if (!preg_match_all('/\d+/', (string)$strRaw, $arrM) || empty($arrM[0])) {
|
||
return '';
|
||
}
|
||
$arrMatches = $arrM[0];
|
||
$blnAllSingles = true;
|
||
foreach ($arrMatches as $strM) {
|
||
if (strlen($strM) !== 1) {
|
||
$blnAllSingles = false;
|
||
break;
|
||
}
|
||
}
|
||
if ($blnAllSingles) {
|
||
$strJoined = implode('', $arrMatches);
|
||
return (strlen($strJoined) >= 1 && strlen($strJoined) <= 6) ? $strJoined : '';
|
||
}
|
||
$strBest = '';
|
||
foreach ($arrMatches as $strM) {
|
||
$intLen = strlen($strM);
|
||
if ($intLen <= 6 && $intLen > strlen($strBest)) {
|
||
$strBest = $strM;
|
||
}
|
||
}
|
||
return $strBest;
|
||
}
|
||
|
||
/**
|
||
* Appel REST Vision TEXT_DETECTION (sans SDK Composer).
|
||
* $strImageBase64 = contenu image (jpeg/png), sans prefixe data:.
|
||
* @return array{ok:bool, text?:string, number?:string, message?:string}
|
||
*/
|
||
function fxInscrGestionVisionReadBibNumber($strImageBase64) {
|
||
$strKey = defined('MSIN_BIB_OCR_VISION_API_KEY')
|
||
? trim((string)MSIN_BIB_OCR_VISION_API_KEY)
|
||
: '';
|
||
if ($strKey === '') {
|
||
return array('ok' => false, 'message' => 'ocr_not_configured');
|
||
}
|
||
|
||
$strImageBase64 = preg_replace('#^data:image/[^;]+;base64,#', '', (string)$strImageBase64);
|
||
$strImageBase64 = preg_replace('/\s+/', '', $strImageBase64);
|
||
if ($strImageBase64 === '' || strlen($strImageBase64) > 5 * 1024 * 1024) {
|
||
return array('ok' => false, 'message' => 'image_invalid');
|
||
}
|
||
|
||
$strUrl = 'https://vision.googleapis.com/v1/images:annotate?key=' . rawurlencode($strKey);
|
||
$arrPayload = array(
|
||
'requests' => array(
|
||
array(
|
||
'image' => array('content' => $strImageBase64),
|
||
'features' => array(
|
||
array('type' => 'TEXT_DETECTION', 'maxResults' => 10),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
$strBody = json_encode($arrPayload);
|
||
|
||
$strResponse = '';
|
||
$intHttp = 0;
|
||
if (function_exists('curl_init')) {
|
||
$ch = curl_init($strUrl);
|
||
curl_setopt_array($ch, array(
|
||
CURLOPT_POST => true,
|
||
CURLOPT_HTTPHEADER => array('Content-Type: application/json'),
|
||
CURLOPT_POSTFIELDS => $strBody,
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_TIMEOUT => 25,
|
||
CURLOPT_CONNECTTIMEOUT => 10,
|
||
));
|
||
$mixResponse = curl_exec($ch);
|
||
$intHttp = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
$strCurlErr = (string)curl_error($ch);
|
||
curl_close($ch);
|
||
if ($mixResponse === false) {
|
||
error_log('[bib_ocr] Vision curl fail: ' . $strCurlErr);
|
||
return array('ok' => false, 'message' => 'vision_curl', 'detail' => $strCurlErr);
|
||
}
|
||
$strResponse = (string)$mixResponse;
|
||
} else {
|
||
$ctx = stream_context_create(array(
|
||
'http' => array(
|
||
'method' => 'POST',
|
||
'header' => "Content-Type: application/json\r\n",
|
||
'content' => $strBody,
|
||
'timeout' => 25,
|
||
'ignore_errors' => true,
|
||
),
|
||
));
|
||
$strResponse = (string)@file_get_contents($strUrl, false, $ctx);
|
||
if (isset($http_response_header[0]) && preg_match('/\s(\d{3})\s/', $http_response_header[0], $m)) {
|
||
$intHttp = (int)$m[1];
|
||
}
|
||
}
|
||
|
||
if ($strResponse === '' || ($intHttp > 0 && $intHttp >= 400)) {
|
||
$arrErr = json_decode($strResponse, true);
|
||
$strDetail = '';
|
||
if (is_array($arrErr) && !empty($arrErr['error']['message'])) {
|
||
$strDetail = (string)$arrErr['error']['message'];
|
||
}
|
||
error_log('[bib_ocr] Vision HTTP ' . $intHttp . ' body=' . substr($strResponse, 0, 400));
|
||
return array('ok' => false, 'message' => 'vision_http', 'detail' => $strDetail !== '' ? $strDetail : ('HTTP ' . $intHttp));
|
||
}
|
||
|
||
$arrJson = json_decode($strResponse, true);
|
||
if (!is_array($arrJson)) {
|
||
return array('ok' => false, 'message' => 'vision_parse');
|
||
}
|
||
if (!empty($arrJson['error']['message'])) {
|
||
error_log('[bib_ocr] Vision error: ' . $arrJson['error']['message']);
|
||
return array('ok' => false, 'message' => 'vision_api', 'detail' => (string)$arrJson['error']['message']);
|
||
}
|
||
|
||
$strText = '';
|
||
if (!empty($arrJson['responses'][0]['textAnnotations'][0]['description'])) {
|
||
$strText = (string)$arrJson['responses'][0]['textAnnotations'][0]['description'];
|
||
} elseif (!empty($arrJson['responses'][0]['fullTextAnnotation']['text'])) {
|
||
$strText = (string)$arrJson['responses'][0]['fullTextAnnotation']['text'];
|
||
}
|
||
|
||
$strNum = fxInscrGestionExtractBibNumberFromOcr($strText);
|
||
if ($strNum === '') {
|
||
return array('ok' => false, 'message' => 'no_number', 'text' => $strText);
|
||
}
|
||
return array('ok' => true, 'number' => $strNum, 'text' => $strText);
|
||
}
|
||
|
||
function fxInscrGestionRenderBibScanBlock($strBibInputId, $strLangue) {
|
||
echo '<div class="inscr-gestion-bib-scan-panel" hidden';
|
||
echo ' data-bib-input-id="' . fxInscrGestionEsc($strBibInputId) . '"';
|
||
// MSIN-4401 — ScanLabel : jamais de clef brute ni marqueur trad dans data-* / pastille.
|
||
echo ' data-msg-scanning="' . fxInscrGestionEsc(fxInscrGestionScanLabel('inscr_gestion_bib_scan_scanning')) . '"';
|
||
echo ' data-msg-loading="' . fxInscrGestionEsc(fxInscrGestionScanLabel('inscr_gestion_bib_scan_loading')) . '"';
|
||
echo ' data-msg-ready="' . fxInscrGestionEsc(fxInscrGestionScanLabel('inscr_gestion_bib_scan_ready')) . '"';
|
||
echo ' data-msg-found="' . fxInscrGestionEsc(fxInscrGestionScanLabel('inscr_gestion_bib_scan_found')) . '"';
|
||
echo ' data-msg-found-search="' . fxInscrGestionEsc(fxInscrGestionScanLabel('inscr_gestion_bib_scan_found_search')) . '"';
|
||
echo ' data-msg-invalid="' . fxInscrGestionEsc(fxInscrGestionScanLabel('inscr_gestion_bib_scan_invalid')) . '"';
|
||
echo ' data-msg-camera="' . fxInscrGestionEsc(fxInscrGestionScanLabel('inscr_gestion_qr_camera_error')) . '">';
|
||
echo '<p class="inscr-gestion-bib-scan-hint text-muted small mb-1">'
|
||
. fxInscrGestionEsc(fxInscrGestionScanLabel('inscr_gestion_bib_scan_hint')) . '</p>';
|
||
echo '<div class="inscr-gestion-bib-scan-status small mb-1" aria-live="polite"></div>';
|
||
echo '<div class="inscr-gestion-bib-scan-capture" role="button" tabindex="0">';
|
||
echo '<div class="inscr-gestion-bib-scan-video"></div>';
|
||
echo '<div class="inscr-gestion-bib-scan-target" aria-hidden="true"></div>';
|
||
echo '<span class="inscr-gestion-bib-scan-capture-label">'
|
||
. fxInscrGestionEsc(fxInscrGestionScanLabel('inscr_gestion_bib_scan_tap')) . '</span>';
|
||
echo '</div>';
|
||
echo '</div>';
|
||
}
|
||
|
||
/**
|
||
* MSIN-4401 — Pastilles des filtres actifs (non vides) pour la barre de resultats.
|
||
* @return array liste de libelles deja pret a afficher (non escapes).
|
||
*/
|
||
function fxInscrGestionActiveFilterLabels($arrReq, $strLangue) {
|
||
$tab = array();
|
||
$strKv = fxInscrGestionT('inscr_gestion_list_filter_kv');
|
||
if ($strKv === 'inscr_gestion_list_filter_kv' || fxInscrGestionIsRawI18nKey($strKv)) {
|
||
$strKv = ($strLangue === 'en') ? '%s: %s' : '%s : %s';
|
||
}
|
||
|
||
if ($arrReq['rech_prenom'] !== '') {
|
||
$tab[] = sprintf($strKv, fxInscrGestionT('rech_prenom'), $arrReq['rech_prenom']);
|
||
}
|
||
if ($arrReq['rech_nom'] !== '') {
|
||
$tab[] = sprintf($strKv, fxInscrGestionT('rech_nom'), $arrReq['rech_nom']);
|
||
}
|
||
if ($arrReq['rech_no_commande'] !== '') {
|
||
$tab[] = sprintf($strKv, fxInscrGestionT('rech_no_commande'), $arrReq['rech_no_commande']);
|
||
}
|
||
if ($arrReq['rech_nom_equipe'] !== '') {
|
||
$tab[] = sprintf(
|
||
$strKv,
|
||
fxInscrGestionT('inscr_gestion_rech_bib_equipe'),
|
||
$arrReq['rech_nom_equipe']
|
||
);
|
||
// MSIN-4399 — Chip seulement si hors défaut (partiel).
|
||
if (!empty($arrReq['bib_match']) && $arrReq['bib_match'] === 'partial') {
|
||
$tab[] = fxInscrGestionT('inscr_gestion_bib_match_partial');
|
||
}
|
||
}
|
||
if ($arrReq['sel_rech_epreuve'] > 0) {
|
||
$tab[] = sprintf(
|
||
$strKv,
|
||
fxInscrGestionT('sel_rech_epreuve'),
|
||
fxInscrGestionEpreuveLabel($arrReq['sel_rech_epreuve'], $strLangue)
|
||
);
|
||
}
|
||
if (!empty($arrReq['sel_rech_statut']) && is_array($arrReq['sel_rech_statut'])) {
|
||
$tabOptions = fxInscrGestionGetStatutCourseOptions($strLangue);
|
||
$tabMap = array();
|
||
foreach ($tabOptions as $arrOpt) {
|
||
$strCode = trim((string)$arrOpt['info_option2']);
|
||
if ($strCode !== '') {
|
||
$tabMap[$strCode] = trim((string)$arrOpt['info_texte']);
|
||
}
|
||
}
|
||
$tabStatLabels = array();
|
||
foreach ($arrReq['sel_rech_statut'] as $strCode) {
|
||
$tabStatLabels[] = isset($tabMap[$strCode]) && $tabMap[$strCode] !== ''
|
||
? $tabMap[$strCode]
|
||
: $strCode;
|
||
}
|
||
if (count($tabStatLabels) > 0) {
|
||
$tab[] = sprintf(
|
||
$strKv,
|
||
fxInscrGestionT('inscr_gestion_rech_statut'),
|
||
implode(', ', $tabStatLabels)
|
||
);
|
||
}
|
||
}
|
||
if ($arrReq['chk_bib']) {
|
||
$tab[] = fxInscrGestionT('chk_bib');
|
||
}
|
||
if ($arrReq['chk_cancelled']) {
|
||
$tab[] = fxInscrGestionT('inscr_gestion_chk_cancelled');
|
||
}
|
||
|
||
return $tab;
|
||
}
|
||
|
||
function fxInscrGestionRenderList($intEveId, $strLangue, $strBaseUrl, $arrReq) {
|
||
global $vDomaine;
|
||
|
||
$arrEvenement = fxGetEvenementsId($intEveId);
|
||
$strEveNom = fxUnescape($arrEvenement['eve_nom_' . $strLangue]);
|
||
$blnSearchActive = !empty($arrReq['q']);
|
||
$blnFreshSearch = !empty($_GET['btn_recherche']) || !empty($_GET['apply_filters']);
|
||
$tabRows = null;
|
||
|
||
// Un seul resultat apres soumission : redirection JS (header() impossible ici, la page est deja entamee).
|
||
if ($blnSearchActive && $blnFreshSearch) {
|
||
$tabRows = fxInscrGestionFetchRows($intEveId, $arrReq);
|
||
if ($tabRows !== null && count($tabRows) === 1) {
|
||
$strFicheUrl = fxInscrGestionBuildUrl($strBaseUrl, $intEveId, $arrReq, array(
|
||
'pec_id' => (int)$tabRows[1]['pec_id_original'],
|
||
'pg' => 1,
|
||
));
|
||
echo '<div id="module-inscr-gestion" class="inscr-gestion-shell" data-inscr-gestion="1">';
|
||
echo '<script>window.location.replace(' . json_encode($strFicheUrl, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE) . ');</script>';
|
||
echo '<noscript><meta http-equiv="refresh" content="0;url=' . fxInscrGestionEsc($strFicheUrl) . '"></noscript>';
|
||
echo '</div>';
|
||
return;
|
||
}
|
||
}
|
||
|
||
$strBackUrl = fxInscrGestionIsEntry()
|
||
? $strBaseUrl
|
||
: fxInscrGestionListBackUrl($intEveId, $strLangue);
|
||
|
||
echo '<div id="module-inscr-gestion" class="inscr-gestion-shell" data-inscr-gestion="1">';
|
||
|
||
if (fxInscrGestionShouldShowListBack((int)$_SESSION['com_id'])) {
|
||
fxInscrGestionRenderNavBack($strBackUrl, fxInscrGestionListBackLabel($strLangue));
|
||
}
|
||
fxInscrGestionRenderPageHead(fxInscrGestionT('inscr_gestion_title'), $strEveNom);
|
||
|
||
fxInscrGestionRenderQrPanel($intEveId, $strLangue);
|
||
|
||
// Recherche + filtres dans un seul panneau (repliable — ouvert par défaut tant qu'aucune recherche n'a été lancée).
|
||
global $objDatabase;
|
||
$tabEpreuves = $objDatabase->fxGetResults(
|
||
'SELECT * FROM inscriptions_epreuves e WHERE e.eve_id = ' . intval($intEveId) . ' AND epr_actif = 1 ORDER BY epr_id'
|
||
);
|
||
|
||
echo '<details class="inscr-gestion-panel inscr-gestion-panel--search"' . ($blnSearchActive ? '' : ' open') . '>';
|
||
echo '<summary class="inscr-gestion-panel__head">';
|
||
fxInscrGestionRenderPanelSummary(fxInscrGestionT('txt_recherche'), 'fa-search');
|
||
echo '</summary>';
|
||
echo '<div class="inscr-gestion-panel__body">';
|
||
echo '<form action="' . fxInscrGestionEsc($strBaseUrl) . '" method="get" class="inscr-gestion-form">';
|
||
echo '<input type="hidden" name="promoteur_eve_id" value="' . fxInscrGestionEsc(base64_encode((string)$intEveId)) . '">';
|
||
// MSIN-4401 — Grille responsive (1 col mobile, 2–3 cols bureau) comme la fiche Détails.
|
||
echo '<div class="inscr-gestion-form-grid">';
|
||
|
||
echo '<div class="inscr-gestion-form-field">';
|
||
echo '<label class="inscr-gestion-label" for="rech_prenom">' . fxInscrGestionEsc(fxInscrGestionT('rech_prenom')) . '</label>';
|
||
echo '<input class="form-control form-control-sm rounded-0" type="text" id="rech_prenom" name="rech_prenom" value="'
|
||
. fxInscrGestionEsc($arrReq['rech_prenom']) . '">';
|
||
echo '</div>';
|
||
|
||
echo '<div class="inscr-gestion-form-field">';
|
||
echo '<label class="inscr-gestion-label" for="rech_nom">' . fxInscrGestionEsc(fxInscrGestionT('rech_nom')) . '</label>';
|
||
echo '<input class="form-control form-control-sm rounded-0" type="text" id="rech_nom" name="rech_nom" value="'
|
||
. fxInscrGestionEsc($arrReq['rech_nom']) . '">';
|
||
echo '</div>';
|
||
|
||
echo '<div class="inscr-gestion-form-field">';
|
||
echo '<label class="inscr-gestion-label" for="rech_no_commande">' . fxInscrGestionEsc(fxInscrGestionT('rech_no_commande')) . '</label>';
|
||
echo '<input class="form-control form-control-sm rounded-0" type="text" id="rech_no_commande" name="rech_no_commande" value="'
|
||
. fxInscrGestionEsc($arrReq['rech_no_commande']) . '">';
|
||
echo '</div>';
|
||
|
||
echo '<div class="inscr-gestion-form-field inscr-gestion-form-field--bib">';
|
||
echo '<label class="inscr-gestion-label" for="rech_nom_equipe">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_rech_bib_equipe')) . '</label>';
|
||
echo '<div class="inscr-gestion-field-block inscr-gestion-field-block--search-bib" data-inscr-field="bib">';
|
||
fxInscrGestionRenderBibScanBlock('rech_nom_equipe', $strLangue);
|
||
echo '<div class="inscr-gestion-bib-row">';
|
||
echo '<input class="form-control form-control-sm rounded-0" type="text" id="rech_nom_equipe" name="rech_nom_equipe" value="'
|
||
. fxInscrGestionEsc($arrReq['rech_nom_equipe']) . '">';
|
||
// MSIN-4401 — Exact vs Partiel (entre champ et caméra).
|
||
// MSIN-4399 — Exact sélectionné par défaut.
|
||
$blnBibExact = (empty($arrReq['bib_match']) || $arrReq['bib_match'] !== 'partial');
|
||
echo '<div class="inscr-gestion-bib-match" role="group"'
|
||
. ' aria-label="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_bib_match_aria')) . '">';
|
||
echo '<label class="inscr-gestion-bib-match__opt">';
|
||
echo '<input type="radio" name="bib_match" value="exact"' . ($blnBibExact ? ' checked' : '') . '>';
|
||
echo '<span>' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_bib_match_exact')) . '</span>';
|
||
echo '</label>';
|
||
echo '<label class="inscr-gestion-bib-match__opt">';
|
||
echo '<input type="radio" name="bib_match" value="partial"' . ($blnBibExact ? '' : ' checked') . '>';
|
||
echo '<span>' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_bib_match_partial')) . '</span>';
|
||
echo '</label>';
|
||
echo '</div>';
|
||
echo '<button class="btn btn-secondary btn-sm rounded-0 inscr-gestion-bib-scan-toggle" type="button" aria-expanded="false"'
|
||
. ' title="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_bib_scan_btn')) . '"'
|
||
. ' aria-label="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_bib_scan_btn')) . '">';
|
||
echo '<i class="fa fa-camera" aria-hidden="true"></i>';
|
||
echo '</button>';
|
||
echo '</div></div></div>';
|
||
|
||
echo '<div class="inscr-gestion-form-field">';
|
||
echo '<label class="inscr-gestion-label" for="sel_rech_epreuve">' . fxInscrGestionEsc(fxInscrGestionT('sel_rech_epreuve')) . '</label>';
|
||
echo '<select class="form-control form-control-sm rounded-0" id="sel_rech_epreuve" name="sel_rech_epreuve">';
|
||
echo '<option value="">—</option>';
|
||
for ($i = 1; $i <= count($tabEpreuves); $i++) {
|
||
$strOpt = fxInscrGestionEpreuveLabel($tabEpreuves[$i]['epr_id'], $strLangue);
|
||
$strSel = ($arrReq['sel_rech_epreuve'] == $tabEpreuves[$i]['epr_id']) ? ' selected' : '';
|
||
$strVal = base64_encode((string)$tabEpreuves[$i]['epr_id']);
|
||
echo '<option value="' . fxInscrGestionEsc($strVal) . '"' . $strSel . '>' . fxInscrGestionEsc($strOpt) . '</option>';
|
||
}
|
||
echo '</select></div>';
|
||
|
||
echo '<div class="inscr-gestion-form-field inscr-gestion-form-field--statut">';
|
||
// MSIN-4401 — Filtre multi statut de course (tous si aucune sélection).
|
||
fxInscrGestionRenderStatutFilter($arrReq, $strLangue);
|
||
echo '</div>';
|
||
|
||
echo '<div class="inscr-gestion-form-field inscr-gestion-form-field--checks">';
|
||
echo '<label class="inscr-gestion-check"><input type="checkbox" name="chk_bib" value="1"'
|
||
. ($arrReq['chk_bib'] ? ' checked' : '') . '> ' . fxInscrGestionEsc(fxInscrGestionT('chk_bib')) . '</label>';
|
||
echo '<label class="inscr-gestion-check"><input type="checkbox" name="chk_cancelled" value="1"'
|
||
. ($arrReq['chk_cancelled'] ? ' checked' : '') . '> '
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_chk_cancelled')) . '</label>';
|
||
echo '</div>';
|
||
|
||
echo '<div class="inscr-gestion-form-field inscr-gestion-form-field--actions">';
|
||
echo '<div class="inscr-gestion-form-actions">';
|
||
echo '<a class="btn btn-secondary btn-sm rounded-0" href="' . fxInscrGestionEsc(fxInscrGestionSearchResetUrl($strBaseUrl, $intEveId)) . '">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_reset_search')) . '</a>';
|
||
echo '<button class="btn btn-primary btn-sm rounded-0" type="submit" name="btn_recherche" value="1">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('txt_recherche')) . '</button>';
|
||
echo '</div></div>';
|
||
|
||
echo '</div></form></div></details>';
|
||
|
||
echo '<div id="inscr-gestion-list" class="inscr-gestion-list-anchor">';
|
||
|
||
// Tant qu'aucune recherche n'a été lancée, on n'affiche aucune inscription : seulement une invite.
|
||
if (!$blnSearchActive) {
|
||
echo '<div class="alert alert-light border inscr-gestion-empty">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_search_prompt')) . '</div>';
|
||
echo '</div>';
|
||
return;
|
||
}
|
||
|
||
// Résultats (la recherche a été lancée — une recherche vide affiche toutes les inscriptions).
|
||
if ($tabRows === null) {
|
||
$tabRows = fxInscrGestionFetchRows($intEveId, $arrReq);
|
||
}
|
||
$intNbItemsParPage = 50;
|
||
$intNbTotal = ($tabRows !== null) ? count($tabRows) : 0;
|
||
$intPage = 1;
|
||
$intStart = 1;
|
||
$intEnd = $intNbItemsParPage;
|
||
if (!empty($_GET['pg'])) {
|
||
$intPage = max(1, intval($_GET['pg']));
|
||
}
|
||
if (intval($intPage) > 1) {
|
||
$intStart = ($intPage - 1) * $intNbItemsParPage + 1;
|
||
$intEnd = $intStart + $intNbItemsParPage - 1;
|
||
}
|
||
if ($intEnd > $intNbTotal) {
|
||
$intEnd = $intNbTotal;
|
||
}
|
||
|
||
echo '<div class="inscr-gestion-list-head">';
|
||
echo '<div class="inscr-gestion-list-head__main">';
|
||
$strCountKey = ($intNbTotal === 1) ? 'inscr_gestion_list_count_one' : 'inscr_gestion_list_count_many';
|
||
echo '<span class="inscr-gestion-list-head__count">'
|
||
. fxInscrGestionEsc(sprintf(fxInscrGestionT($strCountKey), $intNbTotal)) . '</span>';
|
||
// MSIN-4401 — Rappel des filtres / criteres non vides a cote du compteur.
|
||
$tabFilters = fxInscrGestionActiveFilterLabels($arrReq, $strLangue);
|
||
if (count($tabFilters) > 0) {
|
||
echo '<span class="inscr-gestion-list-head__filters">';
|
||
foreach ($tabFilters as $strFilt) {
|
||
echo '<span class="inscr-gestion-list-head__chip">' . fxInscrGestionEsc($strFilt) . '</span>';
|
||
}
|
||
echo '</span>';
|
||
}
|
||
echo '</div>';
|
||
if ($intNbTotal > $intNbItemsParPage) {
|
||
$intNbPages = (int)ceil($intNbTotal / $intNbItemsParPage);
|
||
echo '<span class="inscr-gestion-list-head__pg">'
|
||
. fxInscrGestionEsc(sprintf(fxInscrGestionT('inscr_gestion_list_page'), $intPage, $intNbPages))
|
||
. '</span>';
|
||
}
|
||
echo '</div>';
|
||
|
||
if ($intNbTotal > 0) {
|
||
// MSIN-4448 — Edition inline dossard / remis selon droits granulaires (sans camera).
|
||
$intComId = intval($_SESSION['com_id'] ?? 0);
|
||
$blnCanBib = fxInscrGestionCanDo($intComId, $intEveId, 'inscriptions_gestion.bib_edit');
|
||
$blnCanRemis = fxInscrGestionCanDo($intComId, $intEveId, 'inscriptions_gestion.bib_remis');
|
||
|
||
$strListClass = 'inscr-gestion-list';
|
||
if ($blnCanRemis) {
|
||
$strListClass .= ' inscr-gestion-list--can-edit-remis';
|
||
}
|
||
echo '<div class="' . $strListClass . '">';
|
||
|
||
// En-tete de colonnes (memes colonnes que les lignes ; masque en vue cellulaire).
|
||
// aria-hidden : purement visuel — libellés Info (pas de FR/EN en dur).
|
||
echo '<div class="inscr-gestion-list-cols" aria-hidden="true">';
|
||
echo '<span class="inscr-gestion-list-cols__name">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_col_participant')) . '</span>';
|
||
echo '<span class="inscr-gestion-list-cols__race">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_epreuve')) . '</span>';
|
||
echo '<span class="inscr-gestion-list-cols__order">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_col_commande')) . '</span>';
|
||
echo '<span class="inscr-gestion-list-cols__bib">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_col_dossard')) . '</span>';
|
||
// MSIN-4399 — Statut de course entre dossard et remis.
|
||
echo '<span class="inscr-gestion-list-cols__statut">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_col_statut')) . '</span>';
|
||
echo '<span class="inscr-gestion-list-cols__check">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_col_remis')) . '</span>';
|
||
echo '<span class="inscr-gestion-list-cols__arrow"></span>';
|
||
echo '</div>';
|
||
|
||
$strRemisLabel = fxInscrGestionT('promoteur_bib_enregistre');
|
||
$strBibOkLabel = fxInscrGestionT('inscr_gestion_bib_ok');
|
||
$strTeammateLabel = fxInscrGestionT('inscr_gestion_list_teammate');
|
||
|
||
// MSIN-4459 — Membres d'équipe de la page courante (1 requête batch).
|
||
$arrTeamPecs = array();
|
||
for ($jTeam = $intStart; $jTeam <= $intEnd; $jTeam++) {
|
||
if (!empty($tabRows[$jTeam]) && intval($tabRows[$jTeam]['pec_equipe']) === 1) {
|
||
$arrTeamPecs[] = (int)$tabRows[$jTeam]['pec_id_original'];
|
||
}
|
||
}
|
||
$tabTeamMembersByPec = fxInscrGestionFetchTeamMembersByPecs($arrTeamPecs);
|
||
|
||
for ($j = $intStart; $j <= $intEnd; $j++) {
|
||
$row = $tabRows[$j];
|
||
$strFicheUrl = fxInscrGestionBuildUrl($strBaseUrl, $intEveId, $arrReq, array(
|
||
'pec_id' => (int)$row['pec_id_original'],
|
||
'pg' => $intPage,
|
||
));
|
||
$strFicheHref = fxInscrGestionEsc($strFicheUrl);
|
||
|
||
$blnEquipe = (intval($row['pec_equipe']) === 1);
|
||
$intPecId = (int)$row['pec_id_original'];
|
||
$tabMates = array();
|
||
$strTeamName = '';
|
||
|
||
// MSIN-4459 — Ligne parente = capitaine ; coéquipiers en sous-liste.
|
||
if ($blnEquipe) {
|
||
$strTeamName = trim(fxUnescape(isset($row['pec_nom_equipe']) ? $row['pec_nom_equipe'] : ''));
|
||
$tabMembers = isset($tabTeamMembersByPec[$intPecId]) ? $tabTeamMembersByPec[$intPecId] : array();
|
||
$arrCaptain = null;
|
||
foreach ($tabMembers as $arrMem) {
|
||
if ((int)$arrMem['rol_id'] === 1 && $arrCaptain === null) {
|
||
$arrCaptain = $arrMem;
|
||
} else {
|
||
$tabMates[] = $arrMem;
|
||
}
|
||
}
|
||
if ($arrCaptain !== null) {
|
||
$row['par_nom'] = $arrCaptain['par_nom'];
|
||
$row['par_prenom'] = $arrCaptain['par_prenom'];
|
||
$row['par_id'] = $arrCaptain['par_id'];
|
||
$row['no_bib_remis'] = $arrCaptain['no_bib_remis'];
|
||
$row['par_statut_course'] = $arrCaptain['par_statut_course'];
|
||
$row['rol_id'] = 1;
|
||
} else {
|
||
// Pas de capitaine : garder la ligne GROUP BY ; exclure ce par_id des mates.
|
||
$intMainPar = (int)$row['par_id'];
|
||
$tabMates = array();
|
||
foreach ($tabMembers as $arrMem) {
|
||
if ((int)$arrMem['par_id'] !== $intMainPar) {
|
||
$tabMates[] = $arrMem;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
$strBib = fxInscrGestionBibDisplay($row);
|
||
$strName = fxUnescape($row['par_nom']) . ', ' . fxUnescape($row['par_prenom']);
|
||
$strRace = fxInscrGestionEpreuveLabel($row['epr_id'], $strLangue);
|
||
$strStatutLabel = fxInscrGestionStatutCourseLabel(
|
||
isset($row['par_statut_course']) ? $row['par_statut_course'] : '',
|
||
$strLangue
|
||
);
|
||
$strStatutCode = fxInscrGestionResolveParStatutCourse(
|
||
isset($row['par_statut_course']) ? $row['par_statut_course'] : ''
|
||
);
|
||
$strCardClass = 'inscr-gestion-list-item';
|
||
$strCancelBadge = '';
|
||
if ($row['is_cancelled'] == '1') {
|
||
// MSIN-4405 — Cause derivee en lecture seule (aucune ecriture en base).
|
||
$strCause = fxGetAnnulationCause($row['pec_id_original'], $row['pec_actif']);
|
||
$strCardClass .= ' inscr-gestion-list-item--cancellation inscr-gestion-list-item--' . $strCause;
|
||
$strCancelBadge = ' <span class="inscr-gestion-cancel-badge inscr-gestion-cancel-badge--' . $strCause . '">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_statut_' . $strCause)) . '</span>';
|
||
}
|
||
|
||
$blnRemis = (intval($row['no_bib_remis']) === 1);
|
||
$intParId = (int)$row['par_id'];
|
||
|
||
// MSIN-4448 — Ligne en div : cellules nav (lien) vs dossard/remis editables.
|
||
echo '<div class="' . $strCardClass . '">';
|
||
// MSIN-4459 — Bloc nom : capitaine + nom d'équipe + coéquipiers.
|
||
echo '<div class="inscr-gestion-list-item__who">';
|
||
echo '<a class="inscr-gestion-list-item__name" href="' . $strFicheHref . '">'
|
||
. fxInscrGestionEsc($strName) . $strCancelBadge . '</a>';
|
||
if ($blnEquipe && $strTeamName !== '') {
|
||
echo '<span class="inscr-gestion-list-item__team">'
|
||
. fxInscrGestionEsc(sprintf(fxInscrGestionT('inscr_gestion_list_team_name'), $strTeamName))
|
||
. '</span>';
|
||
}
|
||
if ($blnEquipe && count($tabMates) > 0) {
|
||
echo '<ul class="inscr-gestion-list-mates">';
|
||
foreach ($tabMates as $arrMate) {
|
||
$strMateName = fxUnescape($arrMate['par_nom']) . ', ' . fxUnescape($arrMate['par_prenom']);
|
||
echo '<li class="inscr-gestion-list-mates__item">';
|
||
echo '<a class="inscr-gestion-list-mates__link" href="' . $strFicheHref . '">';
|
||
echo '<span class="inscr-gestion-list-mates__role">'
|
||
. fxInscrGestionEsc($strTeammateLabel) . '</span> ';
|
||
echo fxInscrGestionEsc($strMateName);
|
||
echo '</a></li>';
|
||
}
|
||
echo '</ul>';
|
||
}
|
||
echo '</div>';
|
||
echo '<a class="inscr-gestion-list-item__race" href="' . $strFicheHref . '" title="'
|
||
. fxInscrGestionEsc($strRace) . '">' . fxInscrGestionEsc($strRace) . '</a>';
|
||
echo '<a class="inscr-gestion-list-item__order" href="' . $strFicheHref . '">'
|
||
. fxInscrGestionEsc($row['no_commande']) . '</a>';
|
||
|
||
echo '<span class="inscr-gestion-list-item__bib">';
|
||
if ($blnCanBib) {
|
||
// MSIN-4448 — Pastille par defaut ; clic → champ + OK (sans camera).
|
||
$strBibAction = $blnEquipe ? 'no_equipe' : 'no_bib';
|
||
$strBibInputId = ($blnEquipe ? 'txt_no_bib_e' : 'txt_no_bib_p') . $intParId;
|
||
$blnBibEmpty = (trim($strBib) === '');
|
||
$strPillClass = 'inscr-gestion-bib-pill inscr-gestion-list-bib-pill';
|
||
if ($blnBibEmpty) {
|
||
$strPillClass .= ' inscr-gestion-bib-pill--empty';
|
||
}
|
||
$strPillText = $blnBibEmpty ? '—' : $strBib;
|
||
echo '<div class="inscr-gestion-list-bib-wrap">';
|
||
echo '<button type="button" class="' . $strPillClass . '"'
|
||
. ' title="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_col_dossard')) . '"'
|
||
. ' aria-label="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_col_dossard')) . '">'
|
||
. fxInscrGestionEsc($strPillText) . '</button>';
|
||
echo '<div class="inscr-gestion-list-bib-edit" hidden>';
|
||
echo '<input class="form-control form-control-sm rounded-0 inscr-gestion-list-bib-input" type="text"'
|
||
. ' id="' . fxInscrGestionEsc($strBibInputId) . '"'
|
||
. ' value="' . fxInscrGestionEsc($strBib) . '"'
|
||
. ' maxlength="6" autocomplete="off"'
|
||
. ' aria-label="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_col_dossard')) . '">';
|
||
echo '<button class="btn btn-primary btn-sm rounded-0 btn_bib" type="button"'
|
||
. ' data-action="' . fxInscrGestionEsc($strBibAction) . '"'
|
||
. ' data-eve_id="' . rawurlencode(base64_encode((string)$row['eve_id'])) . '"'
|
||
. ' data-epr_id="' . (int)$row['epr_id'] . '"'
|
||
. ' data-par_id="' . $intParId . '"'
|
||
. ' data-pec_id="' . (int)$row['pec_id_original'] . '"'
|
||
. ' title="' . fxInscrGestionEsc($strBibOkLabel) . '"'
|
||
. ' aria-label="' . fxInscrGestionEsc($strBibOkLabel) . '">OK</button>';
|
||
echo '</div></div>';
|
||
} elseif (trim($strBib) !== '') {
|
||
echo '<span class="inscr-gestion-bib-pill">' . fxInscrGestionEsc($strBib) . '</span>';
|
||
} else {
|
||
echo '<span class="inscr-gestion-bib-pill inscr-gestion-bib-pill--empty">—</span>';
|
||
}
|
||
echo '</span>';
|
||
|
||
// MSIN-4399 — Statut de course en lecture seule dans la liste (edition sur la fiche).
|
||
$strStatutMod = preg_replace('/[^A-Za-z0-9_-]/', '', strtolower($strStatutCode));
|
||
echo '<a class="inscr-gestion-list-item__statut" href="' . $strFicheHref . '">';
|
||
echo '<span class="inscr-gestion-statut-pill inscr-gestion-statut-pill--' . fxInscrGestionEsc($strStatutMod) . '"'
|
||
. ' title="' . fxInscrGestionEsc($strStatutLabel) . '">'
|
||
. fxInscrGestionEsc($strStatutLabel) . '</span>';
|
||
echo '</a>';
|
||
|
||
echo '<span class="inscr-gestion-list-item__check">';
|
||
if ($blnCanRemis) {
|
||
$intRemisVal = $blnRemis ? 0 : 1;
|
||
$strRemisMod = $blnRemis ? 'yes' : 'no';
|
||
echo '<button type="button" class="inscr-gestion-remis inscr-gestion-remis--'
|
||
. $strRemisMod . ' inscr-gestion-list-remis"'
|
||
. ' data-id="' . $intParId . '"'
|
||
. ' data-valeur="' . $intRemisVal . '"'
|
||
. ' title="' . fxInscrGestionEsc($strRemisLabel) . '"'
|
||
. ' aria-label="' . fxInscrGestionEsc($strRemisLabel) . '"'
|
||
. ' aria-pressed="' . ($blnRemis ? 'true' : 'false') . '">';
|
||
if ($blnRemis) {
|
||
echo '<i class="fa fa-check-circle" aria-hidden="true"></i>';
|
||
}
|
||
echo '</button>';
|
||
} elseif ($blnRemis) {
|
||
echo '<span class="inscr-gestion-remis inscr-gestion-remis--yes" title="'
|
||
. fxInscrGestionEsc($strRemisLabel) . '">';
|
||
echo '<i class="fa fa-check-circle" aria-hidden="true"></i>';
|
||
echo '<span class="sr-only">' . fxInscrGestionEsc($strRemisLabel) . '</span>';
|
||
echo '</span>';
|
||
} else {
|
||
echo '<span class="inscr-gestion-remis inscr-gestion-remis--no" aria-hidden="true"></span>';
|
||
}
|
||
echo '</span>';
|
||
|
||
echo '<a class="inscr-gestion-list-item__arrow" href="' . $strFicheHref . '"'
|
||
. ' aria-label="' . fxInscrGestionEsc($strName) . '">'
|
||
. '<i class="fa fa-chevron-right" aria-hidden="true"></i></a>';
|
||
echo '</div>';
|
||
}
|
||
echo '</div>';
|
||
|
||
echo fxInscrGestionRenderPagination($strBaseUrl, $intEveId, $arrReq, $intNbTotal, $intNbItemsParPage, $intPage);
|
||
} else {
|
||
echo '<div class="alert alert-light border inscr-gestion-empty">' . fxInscrGestionEsc(fxInscrGestionT('txt_recherche_no_result')) . '</div>';
|
||
}
|
||
|
||
echo '</div>';
|
||
}
|
||
|
||
function fxInscrGestionRenderFiche($intEveId, $strLangue, $strBaseUrl, $arrReq, $arrRow) {
|
||
global $vDomaine;
|
||
|
||
$intComId = intval($_SESSION['com_id'] ?? 0);
|
||
$intRowEveId = intval($arrRow['eve_id']);
|
||
$blnCanStatut = fxInscrGestionCanDo($intComId, $intRowEveId, 'inscriptions_gestion.statut_edit');
|
||
$blnCanBib = fxInscrGestionCanDo($intComId, $intRowEveId, 'inscriptions_gestion.bib_edit');
|
||
$blnCanRemis = fxInscrGestionCanDo($intComId, $intRowEveId, 'inscriptions_gestion.bib_remis');
|
||
$blnCanEdit = fxInscrGestionCanDo($intComId, $intRowEveId, 'inscriptions_gestion.edit');
|
||
$blnCanCancel = fxInscrGestionCanDo($intComId, $intRowEveId, 'inscriptions_gestion.cancel');
|
||
$blnCanRestore = fxInscrGestionCanDo($intComId, $intRowEveId, 'inscriptions_gestion.restore');
|
||
// Droit de remboursement PayPal (etape remboursement) : controle l'affichage du bouton + panneau.
|
||
$blnCanRefund = fxInscrGestionCanDo($intComId, $intRowEveId, 'inscriptions_gestion.refund');
|
||
// MSIN-4401 — granularite facture / transaction / renvoi (plus d'acces implicite sans droit).
|
||
$blnCanInvoice = fxInscrGestionCanDo($intComId, $intRowEveId, 'inscriptions_gestion.invoice');
|
||
$blnCanTransaction = fxInscrGestionCanDo($intComId, $intRowEveId, 'inscriptions_gestion.transaction');
|
||
$blnCanRenvoi = fxInscrGestionCanDo($intComId, $intRowEveId, 'inscriptions_gestion.renvoi');
|
||
// MSIN-4464 — historique ( ! ) sur champs whitelist avec au moins 1 log.
|
||
$blnCanAudit = fxInscrGestionCanDo($intComId, $intRowEveId, 'inscriptions_gestion.audit_view');
|
||
$arrAuditFieldsWithLogs = array();
|
||
if ($blnCanAudit) {
|
||
if (!function_exists('fxFicheAuditFieldsWithLogsForPar')) {
|
||
require_once dirname(__FILE__) . '/inc_fx_fiche_audit.php';
|
||
}
|
||
$arrAuditFieldsWithLogs = fxFicheAuditFieldsWithLogsForPar(intval($arrRow['par_id']));
|
||
}
|
||
$blnHasAuditEditable = $blnCanAudit && (
|
||
!empty($arrAuditFieldsWithLogs['no_bib'])
|
||
|| !empty($arrAuditFieldsWithLogs['par_statut_course'])
|
||
|| !empty($arrAuditFieldsWithLogs['no_bib_remis'])
|
||
);
|
||
$blnHasEditable = ($blnCanStatut || $blnCanBib || $blnCanRemis || $blnHasAuditEditable);
|
||
$blnHasGestion = ($blnCanEdit || $blnCanCancel || $blnCanRestore || $blnCanRefund
|
||
|| $blnCanInvoice || $blnCanTransaction || $blnCanRenvoi
|
||
|| ($blnCanAudit && !empty($arrAuditFieldsWithLogs['is_cancelled'])));
|
||
$blnInspect = function_exists('fxAdminPermInspectModeActive') && fxAdminPermInspectModeActive();
|
||
|
||
$strBackUrl = fxInscrGestionResolveBackUrl($strBaseUrl, $intEveId, $arrReq);
|
||
$strEveCode = fxGetEvenementsUrl($arrRow['eve_id']);
|
||
$strBib = fxInscrGestionBibDisplay($arrRow);
|
||
$intParId = $arrRow['par_id'];
|
||
$blnCancelled = ($arrRow['is_cancelled'] == '1');
|
||
$blnEquipe = (intval($arrRow['pec_equipe']) === 1);
|
||
$strBibAction = $blnEquipe ? 'no_equipe' : 'no_bib';
|
||
$strBibInputId = $blnEquipe ? 'txt_no_bib_e' : 'txt_no_bib_p';
|
||
|
||
if ($arrRow['no_bib_remis'] == 1) {
|
||
$strRemisClass = 'btn-success';
|
||
$strRemisIcon = '<i class="fa fa-check" aria-hidden="true"></i>';
|
||
$intRemisVal = 0;
|
||
} else {
|
||
$strRemisClass = 'btn-primary';
|
||
$strRemisIcon = '<i class="fa fa-sharp fa-regular fa-square fa-2xs" aria-hidden="true"></i>';
|
||
$intRemisVal = 1;
|
||
}
|
||
|
||
echo '<div id="module-inscr-gestion" class="inscr-gestion-shell inscr-gestion-fiche" data-inscr-gestion="1"';
|
||
$blnFromQr = !empty($_GET['from_qr']);
|
||
// MSIN-4401 — Kit « sections ouvertes » : force tous les onglets de la fiche.
|
||
$blnForceSectionsOpen = function_exists('fxEveAccesComEventHasFicheSectionsOpenKit')
|
||
&& fxEveAccesComEventHasFicheSectionsOpenKit($intComId, $intRowEveId);
|
||
if ($blnFromQr) {
|
||
echo ' data-from-qr="1"';
|
||
}
|
||
echo '>';
|
||
|
||
fxInscrGestionRenderNavBack($strBackUrl, fxInscrGestionResolveBackLabel($arrReq, $strLangue));
|
||
echo '<div class="inscr-gestion-fiche-head">';
|
||
echo '<h1 class="inscr-gestion-page-title">' . fxInscrGestionEsc(fxUnescape($arrRow['par_nom']) . ', ' . fxUnescape($arrRow['par_prenom'])) . '</h1>';
|
||
// MSIN-4328 — lien direct ChronoTrack (superadmin + entry_id connu)
|
||
if (!empty($_SESSION['usa_id'])) {
|
||
$strCtEntryId = trim((string)($arrRow['ct_entry_id'] ?? ''));
|
||
if ($strCtEntryId !== '') {
|
||
if (!defined('MSIN_API_CHRONOTRACK_LOADED')) {
|
||
require_once __DIR__ . '/chronotrack_api/inc_bootstrap.php';
|
||
}
|
||
$arrCtCfg = fxChronotrackApiConfigGet(intval($arrRow['eve_id'] ?? $intEveId));
|
||
$intCtEventIdLink = intval($arrCtCfg['ct_event_id'] ?? 0);
|
||
$strCtUrl = fxChronotrackApiAdminEntryUrl($intCtEventIdLink, $strCtEntryId);
|
||
if ($strCtUrl !== '') {
|
||
echo ' <a class="btn btn-outline-secondary btn-sm inscr-gestion-btn-chronotrack" href="'
|
||
. fxInscrGestionEsc($strCtUrl) . '" target="_blank" rel="noopener noreferrer">'
|
||
. '<i class="fa fa-external-link" aria-hidden="true"></i> '
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_btn_chronotrack'))
|
||
. '</a>';
|
||
}
|
||
}
|
||
}
|
||
echo '</div>';
|
||
|
||
$strDetailsType = $blnEquipe && fxIsParEquipe($arrRow['pec_id_original'], $arrRow['epr_id'])
|
||
? fxInscrGestionT('promoteur_bib_show_teamates')
|
||
: fxInscrGestionT('epreuve_individuelle');
|
||
|
||
fxInscrGestionRenderFicheSectionStart(
|
||
'details',
|
||
fxInscrGestionT('inscr_gestion_details'),
|
||
'fa-info-circle',
|
||
($blnForceSectionsOpen || !$blnFromQr)
|
||
);
|
||
fxInscrGestionRenderKvListOpen(true);
|
||
fxInscrGestionRenderKvRow(fxInscrGestionT('inscr_gestion_epreuve'), fxInscrGestionEpreuveLabel($arrRow['epr_id'], $strLangue));
|
||
fxInscrGestionRenderKvRow(fxInscrGestionT('inscr_gestion_type'), $strDetailsType);
|
||
if ($blnCancelled) {
|
||
// MSIN-4405 — Detail de l'annulation (transferee / annulee) derive en lecture seule.
|
||
$strCause = fxGetAnnulationCause($arrRow['pec_id_original'], $arrRow['pec_actif']);
|
||
$strCancelVal = '<span class="inscr-gestion-cancel-badge inscr-gestion-cancel-badge--' . $strCause . '">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_statut_' . $strCause)) . '</span>';
|
||
if ($blnCanAudit && !empty($arrAuditFieldsWithLogs['is_cancelled'])) {
|
||
ob_start();
|
||
fxInscrGestionRenderAuditBang($intParId, 'is_cancelled', $blnCanAudit, $arrAuditFieldsWithLogs);
|
||
$strCancelVal .= ob_get_clean();
|
||
}
|
||
fxInscrGestionRenderKvRow(
|
||
fxInscrGestionT('inscr_gestion_annulation_titre'),
|
||
$strCancelVal,
|
||
true
|
||
);
|
||
}
|
||
fxInscrGestionRenderKvRow(fxInscrGestionT('inscr_gestion_modified'), fxCheckMAJ($arrRow['pec_id_original'], $intParId, $strLangue), true);
|
||
fxInscrGestionRenderFicheProfileKvRows($arrRow, $strLangue);
|
||
fxInscrGestionRenderKvListClose();
|
||
fxInscrGestionRenderFicheSectionEnd();
|
||
|
||
$strStyleEdit = $strStyleCancel = '';
|
||
$strStyleRetablir = ' d-none';
|
||
if ($blnCancelled) {
|
||
$strStyleEdit = $strStyleCancel = ' d-none';
|
||
$strStyleRetablir = '';
|
||
}
|
||
// Libelle du bouton de renvoi (facture/invitation) : passe par la table info pour etre editable.
|
||
$strRenvoi = fxInscrGestionT('inscr_gestion_renvoi');
|
||
|
||
if ($blnHasGestion || $blnInspect) {
|
||
// MSIN — a l'ouverture de la fiche, seul l'onglet Détails reste deploye (sauf kit sections ouvertes).
|
||
fxInscrGestionRenderFicheSectionStart(
|
||
'gestion',
|
||
fxInscrGestionT('inscr_gestion_section_management'),
|
||
'fa-cog',
|
||
$blnForceSectionsOpen
|
||
);
|
||
echo '<div class="inscr-gestion-gestion-actions">';
|
||
// MSIN-4401 — Chaque action = wrap [controle][cadenas a droite] (evite badge orphelin a gauche).
|
||
if ($blnCanInvoice) {
|
||
fxInscrGestionActionItemOpen();
|
||
echo '<span class="inscr-gestion-facture-label">' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_show_invoice')) . '</span>';
|
||
echo '<a class="btn btn-primary btn-sm rounded-pill" href="' . $vDomaine . '/facture.php?no_panier=' . rawurlencode($arrRow['no_panier']) . '&lang=' . $strLangue . '" target="_blank">' . fxInscrGestionEsc($arrRow['no_commande']) . '</a>';
|
||
fxInscrGestionPermInspectEcho('inscriptions_gestion.invoice');
|
||
fxInscrGestionActionItemClose();
|
||
} elseif ($blnInspect) {
|
||
fxInscrGestionActionItemOpen('inscr-gestion-action-item--inspect-only');
|
||
fxInscrGestionPermInspectEchoHidden('inscriptions_gestion.invoice', fxInscrGestionT('inscr_gestion_show_invoice'));
|
||
fxInscrGestionActionItemClose();
|
||
}
|
||
if ($blnCanTransaction) {
|
||
fxInscrGestionActionItemOpen();
|
||
echo '<button class="btn btn-info btn-sm rounded-0 btn_transaction_toggle" type="button"'
|
||
. ' data-no_panier="' . fxInscrGestionEsc($arrRow['no_panier']) . '"'
|
||
. ' data-transaction-target="#inscr-gestion-transaction-panel"'
|
||
. ' data-label-show="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_show_transaction')) . '"'
|
||
. ' data-label-hide="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_hide_transaction')) . '"'
|
||
. ' aria-expanded="false">' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_show_transaction')) . '</button>';
|
||
fxInscrGestionPermInspectEcho('inscriptions_gestion.transaction');
|
||
fxInscrGestionActionItemClose();
|
||
} elseif ($blnInspect) {
|
||
fxInscrGestionActionItemOpen('inscr-gestion-action-item--inspect-only');
|
||
fxInscrGestionPermInspectEchoHidden('inscriptions_gestion.transaction', fxInscrGestionT('inscr_gestion_show_transaction'));
|
||
fxInscrGestionActionItemClose();
|
||
}
|
||
if ($blnCanEdit) {
|
||
// d-none sur le wrap entier (pas seulement le bouton) pour ne pas laisser le cadenas orphelin.
|
||
fxInscrGestionActionItemOpen(trim($strStyleEdit));
|
||
echo '<button class="btn btn-primary btn-sm rounded-0 btn_edit_event_promo" type="button"'
|
||
. ' id="modifier_fiche" data-eve_code="' . fxInscrGestionEsc($strEveCode) . '"'
|
||
. ' data-epr_id="' . (int)$arrRow['epr_id'] . '" data-eve_id="' . (int)$arrRow['eve_id'] . '"'
|
||
. ' value="' . (int)$arrRow['pec_id_original'] . '">' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_modifier_participant')) . '</button>';
|
||
fxInscrGestionPermInspectEcho('inscriptions_gestion.edit');
|
||
fxInscrGestionActionItemClose();
|
||
} elseif ($blnInspect) {
|
||
fxInscrGestionActionItemOpen('inscr-gestion-action-item--inspect-only');
|
||
fxInscrGestionPermInspectEchoHidden('inscriptions_gestion.edit', fxInscrGestionT('inscr_gestion_modifier_participant'));
|
||
fxInscrGestionActionItemClose();
|
||
}
|
||
if ($blnCanRefund) {
|
||
fxInscrGestionActionItemOpen();
|
||
echo '<button class="btn btn-warning btn-sm rounded-0 btn_refund_toggle" type="button"'
|
||
. ' data-inscr-field="remboursement"'
|
||
. ' data-no_panier="' . fxInscrGestionEsc($arrRow['no_panier']) . '"'
|
||
. ' data-eve_id="' . (int)$intRowEveId . '"'
|
||
. ' data-refund-target="#inscr-gestion-refund-panel"'
|
||
. ' aria-expanded="false">' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_remboursement')) . '</button>';
|
||
fxInscrGestionPermInspectEcho('inscriptions_gestion.refund');
|
||
fxInscrGestionActionItemClose();
|
||
} elseif ($blnInspect) {
|
||
fxInscrGestionActionItemOpen('inscr-gestion-action-item--inspect-only');
|
||
fxInscrGestionPermInspectEchoHidden('inscriptions_gestion.refund', fxInscrGestionT('inscr_gestion_remboursement'));
|
||
fxInscrGestionActionItemClose();
|
||
}
|
||
if ($blnCanCancel) {
|
||
fxInscrGestionActionItemOpen(trim($strStyleCancel));
|
||
echo '<button class="btn btn-secondary btn-sm rounded-0 btn_cancel_event_promo" type="button"'
|
||
. ' id="annuler_fiche" data-eve_code="' . fxInscrGestionEsc($strEveCode) . '"'
|
||
. ' data-epr_id="' . (int)$arrRow['epr_id'] . '" data-eve_id="' . (int)$arrRow['eve_id'] . '"'
|
||
. ' value="' . (int)$arrRow['pec_id_original'] . '">' . fxInscrGestionEsc(fxInscrGestionT('btn_annulerinscriptions')) . '</button>';
|
||
fxInscrGestionPermInspectEcho('inscriptions_gestion.cancel');
|
||
fxInscrGestionActionItemClose();
|
||
} elseif ($blnInspect) {
|
||
fxInscrGestionActionItemOpen('inscr-gestion-action-item--inspect-only');
|
||
fxInscrGestionPermInspectEchoHidden('inscriptions_gestion.cancel', fxInscrGestionT('btn_annulerinscriptions'));
|
||
fxInscrGestionActionItemClose();
|
||
}
|
||
if ($blnCanRestore) {
|
||
fxInscrGestionActionItemOpen(trim($strStyleRetablir));
|
||
echo '<button class="btn btn-secondary btn-sm rounded-0 btn_retablir_event_promo" type="button"'
|
||
. ' id="retablir_fiche" data-eve_code="' . fxInscrGestionEsc($strEveCode) . '"'
|
||
. ' data-epr_id="' . (int)$arrRow['epr_id'] . '" data-eve_id="' . (int)$arrRow['eve_id'] . '"'
|
||
. ' value="' . (int)$arrRow['pec_id_original'] . '">' . fxInscrGestionEsc(fxInscrGestionT('btn_retablirinscriptions')) . '</button>';
|
||
fxInscrGestionPermInspectEcho('inscriptions_gestion.restore');
|
||
fxInscrGestionActionItemClose();
|
||
} elseif ($blnInspect) {
|
||
fxInscrGestionActionItemOpen('inscr-gestion-action-item--inspect-only');
|
||
fxInscrGestionPermInspectEchoHidden('inscriptions_gestion.restore', fxInscrGestionT('btn_retablirinscriptions'));
|
||
fxInscrGestionActionItemClose();
|
||
}
|
||
// MSIN-4464 — historique annulation si pas deja affiche a cote du badge (fiche active).
|
||
if ($blnCanAudit && !empty($arrAuditFieldsWithLogs['is_cancelled']) && !$blnCancelled) {
|
||
fxInscrGestionActionItemOpen();
|
||
echo '<span class="inscr-gestion-audit-inline">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_audit_field_is_cancelled'));
|
||
fxInscrGestionRenderAuditBang($intParId, 'is_cancelled', $blnCanAudit, $arrAuditFieldsWithLogs);
|
||
echo '</span>';
|
||
fxInscrGestionActionItemClose();
|
||
}
|
||
if ($blnInspect) {
|
||
fxInscrGestionPermInspectEcho('inscriptions_gestion.audit_view');
|
||
}
|
||
if ($blnCanRenvoi) {
|
||
fxInscrGestionActionItemOpen();
|
||
echo '<button class="btn btn-primary btn-sm rounded-0 link_confirmation" type="button"'
|
||
. ' data-no_panier="' . fxInscrGestionEsc($arrRow['no_panier']) . '"'
|
||
. ' data-no_commande="' . fxInscrGestionEsc($arrRow['no_commande']) . '"'
|
||
. ' data-lng="' . $strLangue . '">' . fxInscrGestionEsc($strRenvoi) . '</button>';
|
||
fxInscrGestionPermInspectEcho('inscriptions_gestion.renvoi');
|
||
fxInscrGestionActionItemClose();
|
||
} elseif ($blnInspect) {
|
||
fxInscrGestionActionItemOpen('inscr-gestion-action-item--inspect-only');
|
||
fxInscrGestionPermInspectEchoHidden('inscriptions_gestion.renvoi', $strRenvoi);
|
||
fxInscrGestionActionItemClose();
|
||
}
|
||
echo '</div>';
|
||
if ($blnCanRefund) {
|
||
// Panneau de remboursement : vide au depart, rempli en AJAX a la 1ere ouverture (action=refund_form).
|
||
// data-loaded evite de recharger inutilement ; data-no_panier identifie la commande cote serveur.
|
||
// Place AVANT le panneau transaction pour qu'a l'ouverture le formulaire apparaisse au-dessus du detail.
|
||
echo '<div class="inscr-gestion-refund-panel" id="inscr-gestion-refund-panel" style="margin-top:10px;"'
|
||
. ' data-no_panier="' . fxInscrGestionEsc($arrRow['no_panier']) . '"'
|
||
. ' data-loaded="0" hidden></div>';
|
||
}
|
||
// Panneau de transaction : vide au depart, rempli en AJAX a la 1ere ouverture (action=transaction_resume).
|
||
// Sous les boutons, repliable, accessible a tout moment (independant du remboursement).
|
||
if ($blnCanTransaction) {
|
||
echo '<div class="inscr-gestion-transaction-panel" id="inscr-gestion-transaction-panel" style="margin-top:10px;"'
|
||
. ' data-no_panier="' . fxInscrGestionEsc($arrRow['no_panier']) . '"'
|
||
. ' data-loaded="0" hidden></div>';
|
||
}
|
||
fxInscrGestionRenderFicheSectionEnd();
|
||
}
|
||
|
||
if ($blnHasEditable || $blnInspect) {
|
||
// MSIN — champs modifiables fermes par defaut ; ouverts si scan QR ou kit « sections ouvertes ».
|
||
fxInscrGestionRenderFicheSectionStart(
|
||
'editable',
|
||
fxInscrGestionT('inscr_gestion_section_editable'),
|
||
'fa-pencil-square-o',
|
||
($blnForceSectionsOpen || $blnFromQr)
|
||
);
|
||
|
||
if ($blnCanBib) {
|
||
$strFullBibInputId = $strBibInputId . $intParId;
|
||
// Scanner OCR toujours offert sur la fiche de gestion : la saisie se fait
|
||
// souvent au cellulaire / a une table d'inscription. La lecture est 100%
|
||
// cote client (Tesseract.js) et ne touche aucun endpoint serveur protege.
|
||
$blnCanBibScan = true;
|
||
echo '<div class="inscr-gestion-field-block" data-inscr-field="bib">';
|
||
echo '<div class="inscr-gestion-field-block__label">' . fxInscrGestionEsc(fxInscrGestionT('promoteur_bib_number'));
|
||
fxInscrGestionRenderAuditBang($intParId, 'no_bib', $blnCanAudit, $arrAuditFieldsWithLogs);
|
||
echo '</div>';
|
||
if ($blnCanBibScan) {
|
||
fxInscrGestionRenderBibScanBlock($strFullBibInputId, $strLangue);
|
||
}
|
||
echo '<div class="inscr-gestion-bib-row">';
|
||
echo '<input class="form-control rounded-0" type="text" id="' . $strFullBibInputId . '" value="' . fxInscrGestionEsc($strBib) . '">';
|
||
// MSIN-4430 — OK avant camera (action principale a gauche du scan).
|
||
echo '<button class="btn btn-primary rounded-0 btn_bib" type="button" data-action="' . $strBibAction . '"'
|
||
. ' data-eve_id="' . rawurlencode(base64_encode((string)$arrRow['eve_id'])) . '"'
|
||
. ' data-epr_id="' . (int)$arrRow['epr_id'] . '"'
|
||
. ' data-par_id="' . (int)$intParId . '"'
|
||
. ' data-pec_id="' . (int)$arrRow['pec_id_original'] . '">OK</button>';
|
||
if ($blnCanBibScan) {
|
||
echo '<button class="btn btn-secondary rounded-0 inscr-gestion-bib-scan-toggle" type="button" aria-expanded="false"'
|
||
. ' title="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_bib_scan_btn')) . '"'
|
||
. ' aria-label="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_bib_scan_btn')) . '">';
|
||
echo '<i class="fa fa-camera" aria-hidden="true"></i>';
|
||
echo '</button>';
|
||
}
|
||
fxInscrGestionPermInspectEcho('inscriptions_gestion.bib_edit');
|
||
echo '</div>';
|
||
echo '</div>';
|
||
} elseif ($blnCanAudit && !empty($arrAuditFieldsWithLogs['no_bib'])) {
|
||
echo '<div class="inscr-gestion-field-block" data-inscr-field="bib">';
|
||
echo '<div class="inscr-gestion-field-block__label">' . fxInscrGestionEsc(fxInscrGestionT('promoteur_bib_number'));
|
||
fxInscrGestionRenderAuditBang($intParId, 'no_bib', $blnCanAudit, $arrAuditFieldsWithLogs);
|
||
echo '</div>';
|
||
echo '<div class="inscr-gestion-muted">' . fxInscrGestionEsc($strBib) . '</div>';
|
||
echo '</div>';
|
||
} elseif ($blnInspect) {
|
||
fxInscrGestionPermInspectEchoHidden('inscriptions_gestion.bib_edit', fxInscrGestionT('promoteur_bib_number'));
|
||
}
|
||
|
||
if ($blnCanStatut) {
|
||
echo '<div class="inscr-gestion-field-block inscr-gestion-field-block--statut" data-inscr-field="statut">';
|
||
echo '<div class="inscr-gestion-field-block__label">' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_statut_label'));
|
||
fxInscrGestionRenderAuditBang($intParId, 'par_statut_course', $blnCanAudit, $arrAuditFieldsWithLogs);
|
||
echo '</div>';
|
||
fxInscrGestionRenderStatutField($intParId, $arrRow['par_statut_course'] ?? '', $arrRow['eve_id'], $strLangue);
|
||
fxInscrGestionPermInspectEcho('inscriptions_gestion.statut_edit');
|
||
echo '</div>';
|
||
} elseif ($blnCanAudit && !empty($arrAuditFieldsWithLogs['par_statut_course'])) {
|
||
echo '<div class="inscr-gestion-field-block inscr-gestion-field-block--statut" data-inscr-field="statut">';
|
||
echo '<div class="inscr-gestion-field-block__label">' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_statut_label'));
|
||
fxInscrGestionRenderAuditBang($intParId, 'par_statut_course', $blnCanAudit, $arrAuditFieldsWithLogs);
|
||
echo '</div>';
|
||
$strStatutDisp = trim((string)($arrRow['par_statut_course'] ?? ''));
|
||
echo '<div class="inscr-gestion-muted">' . fxInscrGestionEsc($strStatutDisp !== '' ? $strStatutDisp : '—') . '</div>';
|
||
echo '</div>';
|
||
} elseif ($blnInspect) {
|
||
fxInscrGestionPermInspectEchoHidden('inscriptions_gestion.statut_edit', fxInscrGestionT('inscr_gestion_statut_label'));
|
||
}
|
||
|
||
$strRemisDate = trim((string)$arrRow['no_bib_remis_date']);
|
||
if ($strRemisDate === '0000-00-00 00:00:00') {
|
||
$strRemisDate = '';
|
||
}
|
||
$strRemisPar = trim((string)$arrRow['no_bib_remis_par']);
|
||
|
||
if ($blnCanRemis) {
|
||
echo '<div class="inscr-gestion-field-block" data-inscr-field="remis">';
|
||
echo '<div class="inscr-gestion-remis-row">';
|
||
echo '<div class="inscr-gestion-remis-row__left">';
|
||
echo '<div class="inscr-gestion-field-block__label inscr-gestion-field-block__label--inline">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('promoteur_bib_enregistre'));
|
||
fxInscrGestionRenderAuditBang($intParId, 'no_bib_remis', $blnCanAudit, $arrAuditFieldsWithLogs);
|
||
echo '</div>';
|
||
echo '<span id="' . (int)$intParId . '">';
|
||
echo '<a class="btn btn-sm rounded-0 btn_remis ' . $strRemisClass . '" href="#" data-action="remis"'
|
||
. ' data-table="' . rawurlencode(base64_encode('resultats_participants')) . '"'
|
||
. ' data-field="no_bib_remis" data-key="par_id" data-valeur="' . $intRemisVal . '"'
|
||
. ' data-id="' . (int)$intParId . '">' . $strRemisIcon . '</a>';
|
||
echo '</span>';
|
||
echo '</div>';
|
||
echo '<div class="inscr-gestion-remis-row__meta inscr-gestion-muted">';
|
||
echo '<span id="date_' . (int)$intParId . '">' . fxInscrGestionEsc($strRemisDate) . '</span>';
|
||
if ($strRemisDate !== '' && $strRemisPar !== '') {
|
||
echo '<span class="inscr-gestion-remis-row__sep"> — </span>';
|
||
}
|
||
echo '<span id="par_' . (int)$intParId . '">' . fxInscrGestionEsc($strRemisPar) . '</span>';
|
||
echo '</div></div></div>';
|
||
fxInscrGestionPermInspectEcho('inscriptions_gestion.bib_remis');
|
||
} elseif ($blnCanAudit && !empty($arrAuditFieldsWithLogs['no_bib_remis'])) {
|
||
echo '<div class="inscr-gestion-field-block" data-inscr-field="remis">';
|
||
echo '<div class="inscr-gestion-field-block__label">'
|
||
. fxInscrGestionEsc(fxInscrGestionT('promoteur_bib_enregistre'));
|
||
fxInscrGestionRenderAuditBang($intParId, 'no_bib_remis', $blnCanAudit, $arrAuditFieldsWithLogs);
|
||
echo '</div>';
|
||
echo '</div>';
|
||
} elseif ($blnInspect) {
|
||
fxInscrGestionPermInspectEchoHidden('inscriptions_gestion.bib_remis', fxInscrGestionT('promoteur_bib_enregistre'));
|
||
}
|
||
|
||
fxInscrGestionRenderFicheSectionEnd();
|
||
}
|
||
echo '</div>';
|
||
}
|
||
|
||
function fxInscrGestionRun($intEveId, $strLangue) {
|
||
$strBaseUrl = fxInscrGestionBaseUrl($strLangue);
|
||
$arrReq = fxInscrGestionParseRequest();
|
||
|
||
if ($arrReq['pec_id'] > 0) {
|
||
$arrRow = fxInscrGestionFetchRowByPec($arrReq['pec_id'], $intEveId);
|
||
if ($arrRow === null) {
|
||
echo '<div class="alert alert-danger">' . fxInscrGestionEsc(fxInscrGestionT('txt_recherche_no_result')) . '</div>';
|
||
echo '<a class="btn btn-primary rounded-0" href="' . fxInscrGestionEsc(fxInscrGestionResolveBackUrl($strBaseUrl, $intEveId, $arrReq)) . '">';
|
||
echo fxInscrGestionEsc(fxInscrGestionResolveBackLabel($arrReq, $strLangue)) . '</a>';
|
||
return;
|
||
}
|
||
fxInscrGestionRenderFiche($intEveId, $strLangue, $strBaseUrl, $arrReq, $arrRow);
|
||
return;
|
||
}
|
||
|
||
fxInscrGestionRenderList($intEveId, $strLangue, $strBaseUrl, $arrReq);
|
||
}
|