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 / MSIN-4484 — kit total via HasPermission (respecte perm_grants_all = 0). 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 ''; echo fxEveAccesPermInspectBadge($strPermKey, 'inscr_gestion'); echo ' ' . fxInscrGestionEsc($strLabel) . ''; echo ''; } /** 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 $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 ' '; } function fxInscrGestionActionItemOpen($strExtraClass = '') { $strExtra = trim((string)$strExtraClass); echo ''; } function fxInscrGestionActionItemClose() { echo ''; } 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 / MSIN-4484 — kit total via HasAnyPermission (respecte perm_grants_all = 0). 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, 'par_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 // MSIN-4419 — hint post-remboursement + bouton annuler. ? 'Votre transaction a été remboursée. Si vous voulez aussi annuler le participant cliquez sur annuler' : 'Your transaction has been refunded. If you also want to cancel the participant, click cancel', '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', // MSIN-4485 — impression dossard PDF 'inscr_gestion_btn_dossard_print' => $blnFr ? 'Imprimer le dossard' : 'Print bib', 'inscr_gestion_btn_dossard_print_title' => $blnFr ? 'Générer le PDF du dossard' : 'Generate bib PDF', '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' : 'Bib', '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 ? 'Équipe : %s' : 'Team: %s', 'inscr_gestion_list_teammate' => $blnFr ? 'Coéquipier' : 'Teammate', 'inscr_gestion_list_team_no_label' => $blnFr ? 'No d\'équipe' : 'Team no.', 'inscr_gestion_list_team_name_label' => $blnFr ? 'Nom d\'équipe' : 'Team name', ); } 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 '
'; echo '
'; } /** 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 ''; echo '' . fxInscrGestionEsc($strLabel); echo ''; } /** * 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 '
'; echo ''; echo '' . fxInscrGestionEsc($strLabel); echo ''; echo '
'; } function fxInscrGestionRenderPageHead($strTitle, $strSub = '') { echo '
'; echo '

' . fxInscrGestionEsc($strTitle) . '

'; if (trim((string)$strSub) !== '') { echo '

' . fxInscrGestionEsc($strSub) . '

'; } echo '
'; } function fxInscrGestionRenderSectionTitle($strLabel, $strIconClass) { echo '

'; echo ''; echo '' . fxInscrGestionEsc($strLabel) . ''; echo '

'; } function fxInscrGestionRenderFicheSectionStart($strModifier, $strLabel, $strIconClass, $blnOpen = true) { echo '
'; echo ''; echo ''; echo '' . fxInscrGestionEsc($strLabel) . ''; echo ''; echo '
'; } function fxInscrGestionRenderFicheSectionEnd() { echo '
'; } function fxInscrGestionRenderPanelSummary($strLabel, $strIconClass) { echo ''; echo '' . fxInscrGestionEsc($strLabel) . ''; } 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-4459 — Participant cible de la fiche (coéquipier vs capitaine). 'par_id' => isset($_GET['par_id']) ? intval($_GET['par_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, 'par_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-4459 — « Sans dossard » = bib individuel vide (p.no_bib), jamais no_equipe. if ($arrReq['chk_bib']) { $strWhere .= " AND TRIM(IFNULL(p.no_bib, '')) = ''"; } 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 — 1 ligne = 1 participant (coéquipiers dans les memes colonnes). // Equipes regroupées (pec / no_equipe, capitaine d'abord) ; solos par nom. // Exclut rol_id 3/4 (benevole / autre) comme le promoteur. $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' . ' AND p.rol_id NOT IN (3, 4)' . ' ORDER BY' . ' CASE WHEN IFNULL(e.pec_equipe, 0) = 1' . " THEN CONCAT('T', LPAD(CAST(e.pec_id_original AS CHAR), 11, '0'))" . " ELSE CONCAT('P', p.par_nom, '\t', p.par_prenom)" . ' END,' . ' CASE WHEN p.rol_id = 1 THEN 0 ELSE 1 END,' . ' p.par_nom, p.par_prenom, p.par_id'; return $objDatabase->fxGetResults($sql); } function fxInscrGestionFetchRowByPec($intPecId, $intEveId, $intParId = 0) { global $objDatabase; // MSIN-4459 — par_id precis = bon coéquipier ; sinon preferer le capitaine (rol_id=1). $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, e.pec_nom_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'; if (intval($intParId) > 0) { $sql .= ' AND p.par_id = ' . intval($intParId); } $sql .= ' ORDER BY CASE WHEN p.rol_id = 1 THEN 0 ELSE 1 END, p.par_id' . ' 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 cible). * MSIN-4459 — Individuel = p.no_bib (exclure par_id) ; legacy equipe = e.no_equipe (exclure pec). * * @param bool $blnTeam true = comparer e.no_equipe (legacy), false = p.no_bib * @return array liste de lignes ['par_nom','par_prenom','epr_id'] */ function fxInscrGestionFindBibDuplicates($intEveId, $strNoBib, $intExceptPec, $blnTeam, $intCurrentEprId = 0, $intExceptParId = 0) { global $objDatabase; $strNoBib = trim((string)$strNoBib); $intCurrentEprId = (int)$intCurrentEprId; $intExceptParId = (int)$intExceptParId; if (intval($intEveId) <= 0 || $strNoBib === '') { return array(); } if ($blnTeam) { $strBibCol = 'e.no_equipe'; $strExcept = ' AND e.pec_id_original <> ' . intval($intExceptPec); $strGroup = ' GROUP BY e.pec_id_original'; } else { $strBibCol = 'p.no_bib'; if ($intExceptParId > 0) { $strExcept = ' AND p.par_id <> ' . $intExceptParId; } else { $strExcept = ' AND e.pec_id_original <> ' . intval($intExceptPec); } $strGroup = ' GROUP BY p.par_id'; } $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) . "'" . $strExcept . $strGroup . ' 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) { // MSIN-4459 — Dossard = toujours le bib individuel (p.no_bib), jamais e.no_equipe. return fxShowBibNumber( isset($arrRow['no_bib']) ? $arrRow['no_bib'] : '', isset($arrRow['epr_id']) ? $arrRow['epr_id'] : 0, 'participant' ); } /** * MSIN-4459 — Pastille / edition inline du dossard individuel (liste). */ function fxInscrGestionRenderListBibControl($arrCtx) { $strBib = isset($arrCtx['bib']) ? (string)$arrCtx['bib'] : ''; $intParId = (int)($arrCtx['par_id'] ?? 0); $intEveId = (int)($arrCtx['eve_id'] ?? 0); $intEprId = (int)($arrCtx['epr_id'] ?? 0); $intPecId = (int)($arrCtx['pec_id'] ?? 0); $blnCanBib = !empty($arrCtx['can_bib']); $strBibOkLabel = (string)($arrCtx['ok_label'] ?? 'OK'); $strColLabel = (string)($arrCtx['col_label'] ?? ''); if ($blnCanBib) { $strBibInputId = '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 '
'; echo ''; echo '
'; } elseif (trim($strBib) !== '') { echo '' . fxInscrGestionEsc($strBib) . ''; } else { echo ''; } } /** 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 '
'; } function fxInscrGestionRenderKvListClose() { fxInscrGestionKvFicheCardMode(false); echo '
'; } 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 '
'; echo '
' . fxInscrGestionEsc($strLabel) . '
'; echo '
'; if ($blnHtml) { echo $strValue; } else { echo fxInscrGestionEsc($strValue); } echo '
'; } function fxInscrGestionFetchFicheProfile($arrRow, $strLangue) { global $objDatabase; $intParId = (int)$arrRow['par_id']; $intPecId = (int)$arrRow['pec_id_original']; $intPecPk = isset($arrRow['pec_id']) ? (int)$arrRow['pec_id'] : 0; $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']; // MSIN-4482 — hors tx : accepter pec_id PK ou pec_id_original (réponses import). // Pas de IFNULL(que_actif) / pas de requête te_nom ici : une SQL en erreur = plantage // (fxGetResults → mysqli_num_rows(false)). $strPecClause = 'rq.pec_id = ' . $intPecId; if ($intPecPk > 0 && $intPecPk !== $intPecId) { $strPecClause = '(rq.pec_id = ' . $intPecId . ' OR rq.pec_id = ' . $intPecPk . ')'; } $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 ' . $strPecClause . ' 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 '
'; echo '' . fxInscrGestionEsc($strText) . ''; echo '
'; } 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 '
' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_statut_label')) . '
'; return; } $strCurrent = fxInscrGestionResolveParStatutCourse($strCurrent); $strSelectId = 'inscr_statut_' . (int)$intParId; echo ''; } /** * 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 ''; echo '
'; echo '' . '' . fxInscrGestionEsc($strSummary) . '' . ''; echo '
'; 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 ''; } echo '
'; } /** 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) || ($strKey === 'par_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, 'par_id' => null)); unset($tab['pg'], $tab['pec_id'], $tab['par_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 '
' . fxGetPagination( fxInscrGestionPaginationUrl($strBaseUrl, $intEveId, $arrReq), $intTotal, $intPerPage, $intPage ) . '
'; } 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 '
'; fxInscrGestionRenderPageHead($strTitle); echo '
'; 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 ''; echo '' . fxInscrGestionEsc(fxUnescape($tabEv['eve_nom_' . $strLangue])) . ''; echo ''; echo ''; } echo '
'; } 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 '
'; echo ''; fxInscrGestionRenderPanelSummary(fxInscrGestionT('inscr_gestion_qr_title'), 'fa-qrcode'); echo ''; echo '
'; echo '

' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_qr_hint')) . '

'; echo ''; echo '
'; echo '
'; echo '
'; } /** * 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 '
'; echo '

' . fxInscrGestionEsc(fxInscrGestionScanLabel('inscr_gestion_bib_scan_hint')) . '

'; echo '
'; echo '
'; echo '
'; echo ''; echo '' . fxInscrGestionEsc(fxInscrGestionScanLabel('inscr_gestion_bib_scan_tap')) . ''; echo '
'; echo '
'; } /** * 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) { $intOnePec = (int)$tabRows[1]['pec_id_original']; // MSIN-4459 — Preferer le capitaine si equipe (meme logique que la fiche sans par_id). $arrOne = fxInscrGestionFetchRowByPec($intOnePec, $intEveId, 0); $intOnePar = ($arrOne !== null) ? (int)$arrOne['par_id'] : (int)$tabRows[1]['par_id']; $strFicheUrl = fxInscrGestionBuildUrl($strBaseUrl, $intEveId, $arrReq, array( 'pec_id' => $intOnePec, 'par_id' => $intOnePar, 'pg' => 1, )); echo '
'; echo ''; echo ''; echo '
'; return; } } $strBackUrl = fxInscrGestionIsEntry() ? $strBaseUrl : fxInscrGestionListBackUrl($intEveId, $strLangue); echo '
'; 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 ''; echo '
'; // Tant qu'aucune recherche n'a été lancée, on n'affiche aucune inscription : seulement une invite. if (!$blnSearchActive) { echo '
' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_search_prompt')) . '
'; echo '
'; 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 '
'; echo '
'; $strCountKey = ($intNbTotal === 1) ? 'inscr_gestion_list_count_one' : 'inscr_gestion_list_count_many'; echo '' . fxInscrGestionEsc(sprintf(fxInscrGestionT($strCountKey), $intNbTotal)) . ''; // MSIN-4401 — Rappel des filtres / criteres non vides a cote du compteur. $tabFilters = fxInscrGestionActiveFilterLabels($arrReq, $strLangue); if (count($tabFilters) > 0) { echo ''; foreach ($tabFilters as $strFilt) { echo '' . fxInscrGestionEsc($strFilt) . ''; } echo ''; } echo '
'; if ($intNbTotal > $intNbItemsParPage) { $intNbPages = (int)ceil($intNbTotal / $intNbItemsParPage); echo '' . fxInscrGestionEsc(sprintf(fxInscrGestionT('inscr_gestion_list_page'), $intPage, $intNbPages)) . ''; } echo '
'; 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 '
'; // 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 ''; $strRemisLabel = fxInscrGestionT('promoteur_bib_enregistre'); $strBibOkLabel = fxInscrGestionT('inscr_gestion_bib_ok'); for ($j = $intStart; $j <= $intEnd; $j++) { $row = $tabRows[$j]; $blnEquipe = (intval($row['pec_equipe']) === 1); $intPecId = (int)$row['pec_id_original']; $intParId = (int)$row['par_id']; $strTeamNo = $blnEquipe ? trim(fxUnescape(isset($row['no_equipe']) ? $row['no_equipe'] : '')) : ''; // MSIN-4459 — Fiche cible ce participant (capitaine ou coéquipier). $strFicheUrl = fxInscrGestionBuildUrl($strBaseUrl, $intEveId, $arrReq, array( 'pec_id' => $intPecId, 'par_id' => $intParId, 'pg' => $intPage, )); $strFicheHref = fxInscrGestionEsc($strFicheUrl); $strBib = fxInscrGestionBibDisplay($row); $strName = fxUnescape($row['par_nom']) . ', ' . fxUnescape($row['par_prenom']); // No d'équipe en parenthèses (lecture seule), jamais dans la colonne dossard. if ($blnEquipe && $strTeamNo !== '') { $strName .= ' (' . $strTeamNo . ')'; } $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'; // MSIN-4459 — Groupe visuel leger : debut d'equipe + indent coequipiers (1 ligne de nom). if ($blnEquipe) { $strCardClass .= ' inscr-gestion-list-item--team'; if ((int)$row['rol_id'] === 1) { $strCardClass .= ' inscr-gestion-list-item--team-lead'; } else { $strCardClass .= ' inscr-gestion-list-item--teammate'; } if (!isset($intPrevTeamPec) || $intPecId !== $intPrevTeamPec) { $strCardClass .= ' inscr-gestion-list-item--team-start'; } $intPrevTeamPec = $intPecId; } else { $intPrevTeamPec = 0; } $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 = ' ' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_statut_' . $strCause)) . ''; } $blnRemis = (intval($row['no_bib_remis']) === 1); // MSIN-4459 — Meme densite que la liste solo : 1 ligne de nom (+ no equipe entre parentheses). echo '
'; echo ''; echo '' . fxInscrGestionEsc($strName) . $strCancelBadge . ''; // Nom d'équipe une fois par groupe (ligne de tete), inline mute — pas une 2e ligne. if ($blnEquipe && strpos($strCardClass, 'inscr-gestion-list-item--team-start') !== false) { $strTeamName = trim(fxUnescape(isset($row['pec_nom_equipe']) ? $row['pec_nom_equipe'] : '')); if ($strTeamName !== '') { echo '' . fxInscrGestionEsc($strTeamName) . ''; } } echo ''; echo '' . fxInscrGestionEsc($strRace) . ''; echo '' . fxInscrGestionEsc($row['no_commande']) . ''; echo ''; // MSIN-4459 — Colonne dossard = toujours no_bib individuel (pas no_equipe). fxInscrGestionRenderListBibControl(array( 'bib' => $strBib, 'par_id' => $intParId, 'eve_id' => (int)$row['eve_id'], 'epr_id' => (int)$row['epr_id'], 'pec_id' => (int)$row['pec_id_original'], 'can_bib' => $blnCanBib, 'ok_label' => $strBibOkLabel, 'col_label' => fxInscrGestionT('inscr_gestion_col_dossard'), )); echo ''; // 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 ''; echo '' . fxInscrGestionEsc($strStatutLabel) . ''; echo ''; echo ''; if ($blnCanRemis) { $intRemisVal = $blnRemis ? 0 : 1; $strRemisMod = $blnRemis ? 'yes' : 'no'; echo ''; } elseif ($blnRemis) { echo ''; echo ''; echo '' . fxInscrGestionEsc($strRemisLabel) . ''; echo ''; } else { echo ''; } echo ''; echo '' . ''; echo '
'; } echo '
'; echo fxInscrGestionRenderPagination($strBaseUrl, $intEveId, $arrReq, $intNbTotal, $intNbItemsParPage, $intPage); } else { echo '
' . fxInscrGestionEsc(fxInscrGestionT('txt_recherche_no_result')) . '
'; } echo '
'; } 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); // MSIN-4459 — Edition fiche = bib individuel ; no_equipe en lecture seule. $strBibAction = 'no_bib'; $strBibInputId = 'txt_no_bib_p'; if ($arrRow['no_bib_remis'] == 1) { $strRemisClass = 'btn-success'; $strRemisIcon = ''; $intRemisVal = 0; } else { $strRemisClass = 'btn-primary'; $strRemisIcon = ''; $intRemisVal = 1; } echo '
'; fxInscrGestionRenderNavBack($strBackUrl, fxInscrGestionResolveBackLabel($arrReq, $strLangue)); echo '
'; echo '

' . fxInscrGestionEsc(fxUnescape($arrRow['par_nom']) . ', ' . fxUnescape($arrRow['par_prenom'])) . '

'; // 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 ' ' . ' ' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_btn_chronotrack')) . ''; } } } // MSIN-4485 — Imprimer le dossard (droit hors kit total) if (!function_exists('fxDossardPrintCan')) { require_once __DIR__ . '/inc_fx_dossard_print.php'; } if (fxDossardPrintCan($intComId, $intRowEveId)) { $strPrintUrl = rtrim((string)$vDomaine, '/') . '/dossard_print.php' . '?par_id=' . intval($arrRow['par_id']) . '&eve_id=' . intval($intRowEveId); echo ' ' . ' ' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_btn_dossard_print')) . ''; } echo '
'; $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 ($blnEquipe) { $strTeamNoFiche = trim(fxUnescape(isset($arrRow['no_equipe']) ? $arrRow['no_equipe'] : '')); if ($strTeamNoFiche !== '') { fxInscrGestionRenderKvRow( fxInscrGestionT('inscr_gestion_list_team_no_label'), $strTeamNoFiche ); } $strTeamNameFiche = trim(fxUnescape(isset($arrRow['pec_nom_equipe']) ? $arrRow['pec_nom_equipe'] : '')); if ($strTeamNameFiche !== '') { fxInscrGestionRenderKvRow( fxInscrGestionT('inscr_gestion_list_team_name_label'), $strTeamNameFiche ); } } if ($blnCancelled) { // MSIN-4405 — Detail de l'annulation (transferee / annulee) derive en lecture seule. $strCause = fxGetAnnulationCause($arrRow['pec_id_original'], $arrRow['pec_actif']); $strCancelVal = '' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_statut_' . $strCause)) . ''; 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 '
'; // MSIN-4401 — Chaque action = wrap [controle][cadenas a droite] (evite badge orphelin a gauche). if ($blnCanInvoice) { fxInscrGestionActionItemOpen(); echo '' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_show_invoice')) . ''; echo '' . fxInscrGestionEsc($arrRow['no_commande']) . ''; 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 ''; 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 ''; 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 ''; 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 ''; 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 ''; 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 '' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_audit_field_is_cancelled')); fxInscrGestionRenderAuditBang($intParId, 'is_cancelled', $blnCanAudit, $arrAuditFieldsWithLogs); echo ''; fxInscrGestionActionItemClose(); } if ($blnInspect) { fxInscrGestionPermInspectEcho('inscriptions_gestion.audit_view'); } if ($blnCanRenvoi) { fxInscrGestionActionItemOpen(); echo ''; fxInscrGestionPermInspectEcho('inscriptions_gestion.renvoi'); fxInscrGestionActionItemClose(); } elseif ($blnInspect) { fxInscrGestionActionItemOpen('inscr-gestion-action-item--inspect-only'); fxInscrGestionPermInspectEchoHidden('inscriptions_gestion.renvoi', $strRenvoi); fxInscrGestionActionItemClose(); } echo '
'; 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 ''; } // 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 ''; } 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 '
'; echo '
' . fxInscrGestionEsc(fxInscrGestionT('promoteur_bib_number')); fxInscrGestionRenderAuditBang($intParId, 'no_bib', $blnCanAudit, $arrAuditFieldsWithLogs); echo '
'; if ($blnCanBibScan) { fxInscrGestionRenderBibScanBlock($strFullBibInputId, $strLangue); } echo '
'; echo ''; // MSIN-4430 — OK avant camera (action principale a gauche du scan). echo ''; if ($blnCanBibScan) { echo ''; } fxInscrGestionPermInspectEcho('inscriptions_gestion.bib_edit'); echo '
'; echo '
'; } elseif ($blnCanAudit && !empty($arrAuditFieldsWithLogs['no_bib'])) { echo '
'; echo '
' . fxInscrGestionEsc(fxInscrGestionT('promoteur_bib_number')); fxInscrGestionRenderAuditBang($intParId, 'no_bib', $blnCanAudit, $arrAuditFieldsWithLogs); echo '
'; echo '
' . fxInscrGestionEsc($strBib) . '
'; echo '
'; } elseif ($blnInspect) { fxInscrGestionPermInspectEchoHidden('inscriptions_gestion.bib_edit', fxInscrGestionT('promoteur_bib_number')); } if ($blnCanStatut) { echo '
'; echo '
' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_statut_label')); fxInscrGestionRenderAuditBang($intParId, 'par_statut_course', $blnCanAudit, $arrAuditFieldsWithLogs); echo '
'; fxInscrGestionRenderStatutField($intParId, $arrRow['par_statut_course'] ?? '', $arrRow['eve_id'], $strLangue); fxInscrGestionPermInspectEcho('inscriptions_gestion.statut_edit'); echo '
'; } elseif ($blnCanAudit && !empty($arrAuditFieldsWithLogs['par_statut_course'])) { echo '
'; echo '
' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_statut_label')); fxInscrGestionRenderAuditBang($intParId, 'par_statut_course', $blnCanAudit, $arrAuditFieldsWithLogs); echo '
'; $strStatutDisp = trim((string)($arrRow['par_statut_course'] ?? '')); echo '
' . fxInscrGestionEsc($strStatutDisp !== '' ? $strStatutDisp : '—') . '
'; echo '
'; } 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 '
'; echo '
'; echo '
'; echo '
' . fxInscrGestionEsc(fxInscrGestionT('promoteur_bib_enregistre')); fxInscrGestionRenderAuditBang($intParId, 'no_bib_remis', $blnCanAudit, $arrAuditFieldsWithLogs); echo '
'; echo ''; echo '' . $strRemisIcon . ''; echo ''; echo '
'; echo '
'; echo '' . fxInscrGestionEsc($strRemisDate) . ''; if ($strRemisDate !== '' && $strRemisPar !== '') { echo ''; } echo '' . fxInscrGestionEsc($strRemisPar) . ''; echo '
'; fxInscrGestionPermInspectEcho('inscriptions_gestion.bib_remis'); } elseif ($blnCanAudit && !empty($arrAuditFieldsWithLogs['no_bib_remis'])) { echo '
'; echo '
' . fxInscrGestionEsc(fxInscrGestionT('promoteur_bib_enregistre')); fxInscrGestionRenderAuditBang($intParId, 'no_bib_remis', $blnCanAudit, $arrAuditFieldsWithLogs); echo '
'; echo '
'; } elseif ($blnInspect) { fxInscrGestionPermInspectEchoHidden('inscriptions_gestion.bib_remis', fxInscrGestionT('promoteur_bib_enregistre')); } fxInscrGestionRenderFicheSectionEnd(); } echo '
'; } function fxInscrGestionRun($intEveId, $strLangue) { $strBaseUrl = fxInscrGestionBaseUrl($strLangue); $arrReq = fxInscrGestionParseRequest(); if ($arrReq['pec_id'] > 0) { $arrRow = fxInscrGestionFetchRowByPec($arrReq['pec_id'], $intEveId, isset($arrReq['par_id']) ? (int)$arrReq['par_id'] : 0); if ($arrRow === null) { echo '
' . fxInscrGestionEsc(fxInscrGestionT('txt_recherche_no_result')) . '
'; echo ''; echo fxInscrGestionEsc(fxInscrGestionResolveBackLabel($arrReq, $strLangue)) . ''; return; } fxInscrGestionRenderFiche($intEveId, $strLangue, $strBaseUrl, $arrReq, $arrRow); return; } fxInscrGestionRenderList($intEveId, $strLangue, $strBaseUrl, $arrReq); }