Implement PDF distribution panel for bib management (MSIN-4433)

This commit introduces a new feature for generating and managing PDF distributions of bibs. It adds a new AJAX action to handle requests for the distribution panel, along with corresponding JavaScript functions to refresh and initialize the panel. CSS styles are updated to enhance the layout of the distribution panel, and new PHP functions are added to support the generation and metadata handling for the PDF prints. The version code is incremented to 4.72.759 to reflect these changes.
This commit is contained in:
2026-07-08 12:00:58 -04:00
parent 76533287dd
commit 21b28a0d06
7 changed files with 1136 additions and 1 deletions

View File

@ -43,6 +43,7 @@ $arrBibAjaxActions = [
'batch_global_analysis',
'batch_global_simulate',
'global_batch_panel',
'dist_print_panel',
];
if ($action !== '' && in_array($action, $arrBibAjaxActions, true)) {
@ -1153,6 +1154,29 @@ if ($action == 'qty_summary') {
exit;
}
// MSIN-4433 — Panneau impression PDF distribution dossards
if ($action == 'dist_print_panel') {
$int_eve_id = (int)($_POST['eve_id'] ?? $_GET['eve_id'] ?? 0);
if ($int_eve_id <= 0) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_epr_invalid')
]);
exit;
}
$tabPanel = fxBibCollectDistPrintPanelData($int_eve_id, $strLangue);
echo json_encode([
'success' => true,
'html' => renderBibDistPrintPanel($int_eve_id, $strLangue, $tabPanel),
'meta' => $tabPanel['meta'] ?? [],
]);
exit;
}
// =====================
// ACTION INVALIDE
// =====================

46
bib_dist_print.php Normal file
View File

@ -0,0 +1,46 @@
<?php
// MSIN-4433 — Génération PDF distribution des dossards (POST + session promoteur).
session_start();
$_SERVER['PHP_SELF'] = '/compte.php';
$_SERVER['SCRIPT_NAME'] = '/compte.php';
require_once('php/inc_fonctions.php');
require_once('php/inc_fx_promoteur.php');
$strLangue = $_SESSION['lang'] ?? 'fr';
$reqLang = $_REQUEST['lang'] ?? $_REQUEST['lng'] ?? '';
if (!empty($reqLang) && in_array($reqLang, ['fr', 'en'], true)) {
$strLangue = $reqLang;
}
$vPage = 'compte.php';
$vtexte_page = obtenirTextepage($vPage, $strLangue, 2);
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
exit;
}
$int_eve_id = (int)($_POST['eve_id'] ?? 0);
$_REQUEST['eve_id'] = $int_eve_id;
$_POST['eve_id'] = $int_eve_id;
fxBibRequireDistPrintAccess();
$tabSelections = fxBibParseDistPrintSelectionsFromPost($_POST);
$tabSave = fxBibSaveDistPrintSelections($int_eve_id, $tabSelections, $strLangue);
if (!$tabSave['success']) {
http_response_code(400);
header('Content-Type: text/plain; charset=utf-8');
echo $tabSave['message'] ?? 'Invalid selection';
exit;
}
$strPrintedBy = fxBibGetCurrentPromoteurDisplayName();
$intComId = (int)($_SESSION['com_id'] ?? 0);
fxBibSaveDistPrintMeta($int_eve_id, $intComId, $strPrintedBy);
fxBibOutputDistPrintPdf($int_eve_id, $strLangue, $strPrintedBy);
exit;

View File

@ -2687,6 +2687,48 @@ a.ms1-trad-link.btn-aide-trad{
background:#fff;
}
.bib-dist-print-panel{
margin-bottom:0;
}
.bib-dist-print-epr{
border-top:1px solid #e9ecef;
padding-top:12px;
margin-top:12px;
}
.bib-dist-print-epr:first-of-type{
border-top:0;
padding-top:0;
margin-top:0;
}
.bib-dist-print-epr-title{
font-size:14px;
font-weight:600;
margin:0 0 8px;
}
.bib-dist-print-question-list{
display:flex;
flex-wrap:wrap;
gap:8px 16px;
}
.bib-dist-print-question-label{
display:inline-flex;
align-items:flex-start;
gap:6px;
margin:0;
font-size:13px;
cursor:pointer;
}
.bib-dist-print-actions{
border-top:1px solid #e9ecef;
padding-top:12px;
}
.bib-anomaly-block{
padding:10px 12px;
border-top:1px solid #e9ecef;

View File

@ -956,6 +956,12 @@
&& block.getAttribute('data-bib-qty-summary-deferred') === '1') {
refreshBibQtySummaryPanel();
}
// MSIN-4433 — Impression PDF : chargement à la première ouverture.
if (!collapsed && block.id === 'bib-dist-print-panel'
&& block.getAttribute('data-bib-dist-print-deferred') === '1') {
refreshBibDistPrintPanel();
}
});
// ============================================================
@ -1115,6 +1121,8 @@
panelId = 'bib-anomalies-panel';
} else if (focus === 'qty_summary') {
panelId = 'bib-qty-summary-panel';
} else if (focus === 'dist_print') {
panelId = 'bib-dist-print-panel';
} else if (focus === 'event_config') {
panelId = 'bib-event-config-panel';
}
@ -1134,6 +1142,10 @@
refreshBibQtySummaryPanel();
}
if (focus === 'dist_print' && panel.getAttribute('data-bib-dist-print-deferred') === '1') {
refreshBibDistPrintPanel();
}
panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
@ -1166,6 +1178,96 @@
});
}
/** MSIN-4433 — Rafraîchit le panneau impression PDF. */
function refreshBibDistPrintPanel() {
let mount = document.getElementById('bib-dist-print-mount');
if (!mount || !moduleBib) {
return Promise.resolve();
}
let panel = document.getElementById('bib-dist-print-panel');
let blnDeferred = panel && panel.getAttribute('data-bib-dist-print-deferred') === '1';
let blnLoaded = panel && panel.getAttribute('data-bib-dist-print-loaded') === '1';
if (!blnDeferred && !blnLoaded) {
return Promise.resolve();
}
let eveId = moduleBib.dataset.eveId || '';
if (!eveId) {
return Promise.resolve();
}
let blnWasExpanded = panel && !panel.classList.contains('is-collapsed');
return bibFetch('/ajax_bib_range.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'action=dist_print_panel&eve_id=' + encodeURIComponent(eveId) + bibAjaxLangSuffix()
}, { loader: false })
.then(function (res) { return res.json(); })
.then(function (data) {
if (!data.success || !data.html) {
return;
}
mount.innerHTML = data.html;
initModuleBibTippy(mount);
initBibDistPrintPanel(mount);
if (blnWasExpanded) {
let newPanel = document.getElementById('bib-dist-print-panel');
if (newPanel) {
bibExpandBlock(newPanel);
}
}
});
}
/** MSIN-4433 — Limite questions + rafraîchissement après génération PDF. */
function initBibDistPrintPanel(root) {
root = root || document;
let panel = root.querySelector ? root.querySelector('#bib-dist-print-panel') : null;
if (!panel) {
panel = document.getElementById('bib-dist-print-panel');
}
if (!panel || panel.getAttribute('data-bib-dist-print-init') === '1') {
return;
}
panel.setAttribute('data-bib-dist-print-init', '1');
let intMax = parseInt(panel.getAttribute('data-max-questions') || '5', 10);
if (!intMax || intMax < 1) {
intMax = 5;
}
panel.addEventListener('change', function (ev) {
let cb = ev.target;
if (!cb || !cb.classList || !cb.classList.contains('bib-dist-print-question-cb')) {
return;
}
if (!cb.checked) {
return;
}
let section = cb.closest('.bib-dist-print-epr');
if (!section) {
return;
}
let tabChecked = section.querySelectorAll('.bib-dist-print-question-cb:checked');
if (tabChecked.length > intMax) {
cb.checked = false;
}
});
let form = panel.querySelector('.bib-dist-print-form');
if (form) {
form.addEventListener('submit', function () {
window.setTimeout(function () {
refreshBibDistPrintPanel();
}, 1500);
});
}
}
/** MSIN-4424 — Rafraîchit le sommaire des quantités (si déjà chargé ou ouverture). */
function refreshBibQtySummaryPanel() {
let mount = document.getElementById('bib-qty-summary-mount');

View File

@ -3343,6 +3343,19 @@ function fxBibStaticFallback($clef) {
'bib_v4_qty_summary_by_epr' => ['fr' => 'Détail par épreuve', 'en' => 'Breakdown by race'],
'bib_v4_qty_summary_col_epr' => ['fr' => 'Épreuve', 'en' => 'Race'],
'bib_v4_ajax_global_sim_summary' => ['fr' => '%d épreuve(s) · %d dispo / %d à assigner → %d assignables (%d manque)', 'en' => '%d race(s) · %d avail. / %d to assign → %d assignable (%d short)'],
'bib_v4_dist_print_title' => ['fr' => 'Impression PDF — distribution des dossards', 'en' => 'PDF print — bib distribution'],
'bib_v4_dist_print_questions' => ['fr' => 'Colonnes optionnelles (questions)', 'en' => 'Optional columns (questions)'],
'bib_v4_dist_print_generate' => ['fr' => 'Générer le PDF', 'en' => 'Generate PDF'],
'bib_v4_dist_print_last_none' => ['fr' => 'Aucune impression enregistrée pour cet événement.', 'en' => 'No print recorded for this event yet.'],
'bib_v4_dist_print_max_questions' => ['fr' => 'Maximum %d questions par épreuve pour le format lettre.', 'en' => 'Maximum %d questions per race for letter format.'],
'bib_v4_dist_print_no_questions' => ['fr' => 'Aucune question éligible pour cette épreuve.', 'en' => 'No eligible question for this race.'],
'bib_v4_dist_print_col_bib' => ['fr' => 'Dossard', 'en' => 'Bib'],
'bib_v4_dist_print_col_nom' => ['fr' => 'Nom', 'en' => 'Last name'],
'bib_v4_dist_print_col_prenom' => ['fr' => 'Prénom', 'en' => 'First name'],
'bib_v4_dist_print_col_sexe' => ['fr' => 'Sexe', 'en' => 'Gender'],
'bib_v4_dist_print_empty_epr' => ['fr' => 'Aucun dossard assigné pour cette épreuve.', 'en' => 'No bib assigned for this race.'],
'bib_v4_dist_print_last' => ['fr' => 'Dernière impression : %s — %s', 'en' => 'Last print: %s — %s'],
'bib_v4_dist_print_footer' => ['fr' => 'Imprimé le %s — %s', 'en' => 'Printed on %s — %s'],
];
return $tab[$clef][$lng] ?? '';
@ -4609,6 +4622,10 @@ function fxShowBibTool4($str_code, $int_eve_id, $strLangue){
<?php echo renderBibAnomaliesPanelShell((int)$int_eve_id, $strLangue); ?>
</div>
<div id="bib-dist-print-mount">
<?php echo renderBibDistPrintPanelShell((int)$int_eve_id, $strLangue); ?>
</div>
<?php echo renderBibGlobalBatchPanelShell((int)$int_eve_id, $tabEpreuves, $strLangue); ?>
<?php for($i = 1; $i <= count($tabEpreuves); $i++){ ?>
@ -7723,4 +7740,744 @@ function fxResolveTeamBib(
}
return null;
}
// =============================================================================
// MSIN-4433 — Impression PDF distribution des dossards
// =============================================================================
/** MSIN-4433 — Limite de colonnes questions (format lettre 8½ × 11). */
function fxBibDistPrintMaxQuestions() {
return 5;
}
/** MSIN-4433 — Nom affiché du compte connecté (promoteur ou superadmin). */
function fxBibGetCurrentPromoteurDisplayName() {
if (!empty($_SESSION['usa_id']) && !empty($_SESSION['usa_info']) && is_array($_SESSION['usa_info'])) {
$str = trim((string)($_SESSION['usa_info']['com_prenom'] ?? '') . ' ' . (string)($_SESSION['usa_info']['com_nom'] ?? ''));
if ($str !== '') {
return $str;
}
}
if (!empty($_SESSION['com_info']) && is_array($_SESSION['com_info'])) {
$str = trim((string)($_SESSION['com_info']['com_prenom'] ?? '') . ' ' . (string)($_SESSION['com_info']['com_nom'] ?? ''));
if ($str !== '') {
return $str;
}
}
return trim((string)($_SESSION['com_prenom'] ?? '') . ' ' . (string)($_SESSION['com_nom'] ?? ''));
}
/** MSIN-4433 — Accès génération PDF (session, permissions, CSRF). */
function fxBibRequireDistPrintAccess() {
if (!fxBibRequirePromoteurSession()) {
http_response_code(403);
header('Content-Type: text/plain; charset=utf-8');
echo fxBibMsg('bib_v4_ajax_session_invalid');
exit;
}
if (empty($_SESSION['usa_id'])) {
require_once __DIR__ . '/inc_fx_eve_acces.php';
$intComId = (int)($_SESSION['com_id'] ?? 0);
$intEveId = (int)($_REQUEST['eve_id'] ?? $_POST['eve_id'] ?? 0);
if ($intComId <= 0 || $intEveId <= 0
|| !fxEveAccesHasAnyPermission($intComId, $intEveId, array('dossards.manage', 'epreuves.edit_qte'))) {
http_response_code(403);
header('Content-Type: text/plain; charset=utf-8');
echo fxBibMsg('bib_v4_ajax_unauthorized');
exit;
}
}
$strToken = $_POST['csrf_token'] ?? $_REQUEST['csrf_token'] ?? '';
if (!fxBibValidateCsrf($strToken)) {
http_response_code(403);
header('Content-Type: text/plain; charset=utf-8');
echo fxBibMsg('bib_v4_ajax_unauthorized');
exit;
}
}
/** MSIN-4433 — Parse liste d'IDs questions depuis chaîne CSV. */
function fxBibParseDistPrintQueIds($strCsv) {
if ($strCsv === null || $strCsv === '') {
return [];
}
$out = [];
foreach (explode(',', (string)$strCsv) as $part) {
$intId = (int)trim($part);
if ($intId > 0 && !in_array($intId, $out, true)) {
$out[] = $intId;
}
}
return $out;
}
/** MSIN-4433 — Sérialise les IDs questions pour stockage. */
function fxBibDistPrintQueIdsToCsv(array $tabQueIds) {
$out = [];
foreach ($tabQueIds as $mixId) {
$intId = (int)$mixId;
if ($intId > 0 && !in_array($intId, $out, true)) {
$out[] = $intId;
}
}
return implode(',', $out);
}
/** MSIN-4433 — Libellé court sexe pour le PDF. */
function fxBibDistPrintSexeLabel($strSexe, $strLangue = 'fr') {
$strSexe = strtolower(trim((string)$strSexe));
if ($strLangue === 'en') {
$tab = ['f' => 'F', 'h' => 'M', 'n' => 'X', 'a' => 'O'];
} else {
$tab = ['f' => 'F', 'h' => 'H', 'n' => 'X', 'a' => 'A'];
}
return $tab[$strSexe] ?? strtoupper($strSexe);
}
/** MSIN-4433 — Date/heure affichée (pied de page, panneau). */
function fxBibFormatDistPrintDateTime($strWhen = '', $strLangue = 'fr') {
$ts = ($strWhen !== '') ? strtotime($strWhen) : time();
if ($ts === false) {
$ts = time();
}
if ($strLangue === 'en') {
return date('F j, Y, g:i a', $ts);
}
$tabMonths = [
1 => 'janvier', 2 => 'février', 3 => 'mars', 4 => 'avril',
5 => 'mai', 6 => 'juin', 7 => 'juillet', 8 => 'août',
9 => 'septembre', 10 => 'octobre', 11 => 'novembre', 12 => 'décembre',
];
$intDay = (int)date('j', $ts);
$intMonth = (int)date('n', $ts);
$intYear = (int)date('Y', $ts);
$strTime = date('H:i', $ts);
return $intDay . ' ' . ($tabMonths[$intMonth] ?? '') . ' ' . $intYear . ', ' . $strTime;
}
/** MSIN-4433 — Métadonnées dernière impression (événement). */
function fxBibGetDistPrintMeta($int_eve_id) {
global $objDatabase;
$int_eve_id = (int)$int_eve_id;
if ($int_eve_id <= 0) {
return ['at' => '', 'by' => '', 'com_id' => 0];
}
$row = $objDatabase->fxGetRow("
SELECT eve_bib_dist_print_at, eve_bib_dist_print_by, eve_bib_dist_print_com_id
FROM inscriptions_evenements
WHERE eve_id = $int_eve_id
LIMIT 1
");
if (!is_array($row)) {
return ['at' => '', 'by' => '', 'com_id' => 0];
}
return [
'at' => trim((string)($row['eve_bib_dist_print_at'] ?? '')),
'by' => trim((string)($row['eve_bib_dist_print_by'] ?? '')),
'com_id' => (int)($row['eve_bib_dist_print_com_id'] ?? 0),
];
}
/** MSIN-4433 — Persiste date / auteur de l'impression. */
function fxBibSaveDistPrintMeta($int_eve_id, $int_com_id, $str_by) {
global $objDatabase;
$int_eve_id = (int)$int_eve_id;
$int_com_id = (int)$int_com_id;
$str_by = trim((string)$str_by);
if ($int_eve_id <= 0) {
return;
}
$sql = "
UPDATE inscriptions_evenements
SET eve_bib_dist_print_at = '" . $objDatabase->fxEscape(fxGetDateTime()) . "',
eve_bib_dist_print_by = '" . $objDatabase->fxEscape($str_by) . "',
eve_bib_dist_print_com_id = $int_com_id
WHERE eve_id = $int_eve_id
LIMIT 1
";
$objDatabase->fxQuery($sql);
}
/**
* MSIN-4433 — Données panneau : épreuves, questions éligibles, sélection mémorisée.
* @return array{meta: array, epreuves: array<int, array<string, mixed>>}
*/
function fxBibCollectDistPrintPanelData($int_eve_id, $strLangue = 'fr') {
global $objDatabase;
$int_eve_id = (int)$int_eve_id;
$tabOut = ['meta' => fxBibGetDistPrintMeta($int_eve_id), 'epreuves' => []];
if ($int_eve_id <= 0) {
return $tabOut;
}
$sqlEpreuves = "
SELECT *
FROM inscriptions_epreuves
WHERE eve_id = $int_eve_id
AND epr_actif = 1
ORDER BY epr_tri, epr_id
";
$tabEpreuves = $objDatabase->fxGetResults($sqlEpreuves);
if (!is_array($tabEpreuves)) {
return $tabOut;
}
foreach ($tabEpreuves as $epr) {
$epr_id = (int)($epr['epr_id'] ?? 0);
if ($epr_id <= 0) {
continue;
}
$tabQuestionOpts = fxBibGetQuestionSortOptions($epr_id, $int_eve_id, $strLangue);
$tabSelected = fxBibParseDistPrintQueIds($epr['ba_bib_dist_que_ids'] ?? '');
$tabValidIds = [];
foreach ($tabQuestionOpts as $opt) {
if (preg_match('/^que:(\d+)$/', (string)($opt['key'] ?? ''), $m)) {
$tabValidIds[] = (int)$m[1];
}
}
$tabSelected = array_values(array_intersect($tabSelected, $tabValidIds));
$tabOut['epreuves'][] = [
'epr_id' => $epr_id,
'epr_label' => fxBibAnomalyEprLabel($epr, $strLangue),
'questions' => $tabQuestionOpts,
'selected' => $tabSelected,
'avec_bib' => (int)(fxInfosBibEpreuve($epr_id)['avec_bib'] ?? 0),
];
}
return $tabOut;
}
/** MSIN-4433 — Parse sélections POST dist_print[epr_id][] = que_id. */
function fxBibParseDistPrintSelectionsFromPost(array $post) {
$tabRaw = $post['dist_print'] ?? [];
if (!is_array($tabRaw)) {
return [];
}
$out = [];
foreach ($tabRaw as $mixEprId => $mixQueIds) {
$epr_id = (int)$mixEprId;
if ($epr_id <= 0) {
continue;
}
if (!is_array($mixQueIds)) {
$mixQueIds = [$mixQueIds];
}
$tabIds = [];
foreach ($mixQueIds as $mixQueId) {
$intQueId = (int)$mixQueId;
if ($intQueId > 0 && !in_array($intQueId, $tabIds, true)) {
$tabIds[] = $intQueId;
}
}
$out[$epr_id] = $tabIds;
}
return $out;
}
/**
* MSIN-4433 — Sauvegarde les questions cochées par épreuve (à la génération PDF).
* @return array{success: bool, message?: string}
*/
function fxBibSaveDistPrintSelections($int_eve_id, array $tabSelections, $strLangue = 'fr') {
global $objDatabase;
$int_eve_id = (int)$int_eve_id;
if ($int_eve_id <= 0) {
return ['success' => false, 'message' => fxBibMsg('bib_v4_ajax_epr_invalid')];
}
$intMax = fxBibDistPrintMaxQuestions();
$tabPanel = fxBibCollectDistPrintPanelData($int_eve_id, $strLangue);
foreach ($tabPanel['epreuves'] as $tabEpr) {
$epr_id = (int)($tabEpr['epr_id'] ?? 0);
if ($epr_id <= 0) {
continue;
}
$tabValidIds = [];
foreach ($tabEpr['questions'] as $opt) {
if (preg_match('/^que:(\d+)$/', (string)($opt['key'] ?? ''), $m)) {
$tabValidIds[] = (int)$m[1];
}
}
$tabChosen = $tabSelections[$epr_id] ?? [];
if (!is_array($tabChosen)) {
$tabChosen = [];
}
$tabFiltered = [];
foreach ($tabChosen as $intQueId) {
$intQueId = (int)$intQueId;
if ($intQueId > 0 && in_array($intQueId, $tabValidIds, true)
&& !in_array($intQueId, $tabFiltered, true)) {
$tabFiltered[] = $intQueId;
}
}
if (count($tabFiltered) > $intMax) {
return [
'success' => false,
'message' => fxBibMsg('bib_v4_dist_print_max_questions', $intMax),
];
}
$strCsv = fxBibDistPrintQueIdsToCsv($tabFiltered);
$sql = "
UPDATE inscriptions_epreuves
SET ba_bib_dist_que_ids = '" . $objDatabase->fxEscape($strCsv) . "'
WHERE epr_id = $epr_id
AND eve_id = $int_eve_id
LIMIT 1
";
$objDatabase->fxQuery($sql);
}
return ['success' => true];
}
/**
* MSIN-4433 — Participants avec dossard (une ligne par no_bib), tri batch.
* @return array<int, array<string, mixed>>
*/
function fxBibGetParticipantsForDistPrint($epr_id, $sort1, $sort2 = '', $strLangue = 'fr') {
global $objDatabase;
$epr_id = (int)$epr_id;
if ($epr_id <= 0) {
return [];
}
$strSort1 = fxBibNormalizeSortKey($sort1);
$strSort2 = fxBibNormalizeSortKey($sort2);
if ($strSort1 === '') {
return [];
}
if ($strSort1 === $strSort2) {
$strSort2 = '';
}
$sqlBibNumP = fxBibSqlNoBibUnsigned('p.no_bib');
$orderBy = fxBibBuildBatchOrderBy($epr_id, $strSort1, $strSort2, $strLangue);
$sql = "
SELECT
p.par_id,
p.pec_id,
p.no_bib,
p.par_nom,
p.par_prenom,
p.par_sexe
FROM resultats_participants p
LEFT JOIN resultats_epreuves_commandees c
ON c.pec_id_original = p.pec_id
AND c.epr_id = p.epr_id
WHERE p.epr_id = $epr_id
AND p.is_cancelled = 0
AND $sqlBibNumP > 0
AND p.rol_id NOT IN (3, 4)
ORDER BY $orderBy
";
$rows = $objDatabase->fxGetResults($sql);
if (!is_array($rows)) {
return [];
}
$tabSeen = [];
$tabOut = [];
foreach ($rows as $row) {
$strBibKey = (string)(int)($row['no_bib'] ?? 0);
if ($strBibKey === '0' || isset($tabSeen[$strBibKey])) {
continue;
}
$tabSeen[$strBibKey] = true;
$tabOut[] = $row;
}
return $tabOut;
}
/**
* MSIN-4433 — Réponses questions pour une liste de participants.
* @return array<string, array<int, string>> clé "par_id:pec_id" => [que_id => réponse]
*/
function fxBibGetDistPrintQuestionAnswers($epr_id, array $tabQueIds, array $tabParticipants, $strLangue = 'fr') {
global $objDatabase;
$epr_id = (int)$epr_id;
$tabQueIds = array_values(array_filter(array_map('intval', $tabQueIds)));
if ($epr_id <= 0 || empty($tabQueIds) || empty($tabParticipants)) {
return [];
}
$tabParIds = [];
foreach ($tabParticipants as $row) {
$intParId = (int)($row['par_id'] ?? 0);
if ($intParId > 0) {
$tabParIds[] = $intParId;
}
}
$tabParIds = array_values(array_unique($tabParIds));
if (empty($tabParIds)) {
return [];
}
$strCol = ($strLangue === 'en') ? 'que_choix_en' : 'que_choix_fr';
$strQueIds = implode(',', $tabQueIds);
$strParIds = implode(',', $tabParIds);
$sql = "
SELECT par_id, pec_id, que_id, TRIM($strCol) AS ans
FROM resultats_questions
WHERE que_actif = 1
AND que_id IN ($strQueIds)
AND par_id IN ($strParIds)
";
$rows = $objDatabase->fxGetResults($sql);
if (!is_array($rows)) {
return [];
}
$out = [];
foreach ($rows as $row) {
$strKey = (int)($row['par_id'] ?? 0) . ':' . (int)($row['pec_id'] ?? 0);
$intQueId = (int)($row['que_id'] ?? 0);
$out[$strKey][$intQueId] = trim((string)($row['ans'] ?? ''));
}
return $out;
}
/** MSIN-4433 — Tronque texte pour cellule PDF. */
function fxBibDistPrintTruncate($str, $intMax = 28) {
$str = trim((string)$str);
if ($str === '') {
return '';
}
if (function_exists('mb_strlen') && function_exists('mb_substr')) {
if (mb_strlen($str) <= $intMax) {
return $str;
}
return mb_substr($str, 0, max(1, $intMax - 1)) . '…';
}
if (strlen($str) <= $intMax) {
return $str;
}
return substr($str, 0, max(1, $intMax - 1)) . '…';
}
/** MSIN-4433 — Génère et envoie le PDF (format lettre). */
function fxBibOutputDistPrintPdf($int_eve_id, $strLangue = 'fr', $strPrintedBy = '') {
global $objDatabase;
$int_eve_id = (int)$int_eve_id;
if ($int_eve_id <= 0) {
http_response_code(400);
exit;
}
$tabEvent = $objDatabase->fxGetRow("SELECT * FROM inscriptions_evenements WHERE eve_id = $int_eve_id LIMIT 1");
if (!is_array($tabEvent)) {
http_response_code(404);
exit;
}
$strEventTitle = trim((string)($tabEvent['eve_nom_' . $strLangue] ?? ''));
if ($strEventTitle === '') {
$strEventTitle = trim((string)($tabEvent['eve_nom_fr'] ?? ''));
}
$strPrintedAt = fxBibFormatDistPrintDateTime('', $strLangue);
if (trim((string)$strPrintedBy) === '') {
$strPrintedBy = fxBibGetCurrentPromoteurDisplayName();
}
$strFooter = sprintf(
fxBibTexte('bib_v4_dist_print_footer', 0),
$strPrintedAt,
$strPrintedBy
);
$tabPanel = fxBibCollectDistPrintPanelData($int_eve_id, $strLangue);
$strDocRoot = $_SERVER['DOCUMENT_ROOT'] ?? dirname(__DIR__);
require_once($strDocRoot . '/fpdf182/fpdf.php');
if (!class_exists('BibDistPrintPdf', false)) {
class BibDistPrintPdf extends FPDF {
public $footerText = '';
function Footer() {
$this->SetY(-12);
$this->SetFont('Arial', 'I', 7);
$this->Cell(0, 4, utf8_decode($this->footerText), 0, 0, 'L');
$this->Cell(0, 4, utf8_decode('p. ' . $this->PageNo()), 0, 0, 'R');
}
}
}
$pdf = new BibDistPrintPdf('P', 'mm', 'letter');
$pdf->footerText = $strFooter;
$pdf->SetMargins(10, 12, 10);
$pdf->SetAutoPageBreak(true, 14);
$pdf->AddPage();
$pdf->SetFont('Arial', 'B', 14);
$pdf->Cell(0, 8, utf8_decode(fxBibDistPrintTruncate($strEventTitle, 80)), 0, 1, 'C');
$pdf->SetFont('Arial', '', 10);
$pdf->Cell(0, 6, utf8_decode(fxBibTexte('bib_v4_dist_print_title', 0)), 0, 1, 'C');
$pdf->Ln(4);
$fltPageW = $pdf->GetPageWidth() - 20;
$fltFixedW = 18 + 42 + 42 + 12;
foreach ($tabPanel['epreuves'] as $tabEpr) {
$epr_id = (int)($tabEpr['epr_id'] ?? 0);
if ($epr_id <= 0) {
continue;
}
$tabEprRow = $objDatabase->fxGetRow("SELECT * FROM inscriptions_epreuves WHERE epr_id = $epr_id LIMIT 1");
if (!is_array($tabEprRow)) {
continue;
}
list($strSort1, $strSort2) = fxBibGetEpreuveDefaultSortKeys($tabEprRow);
$tabSelectedQue = $tabEpr['selected'] ?? [];
$tabQueCols = [];
foreach ($tabEpr['questions'] as $opt) {
if (preg_match('/^que:(\d+)$/', (string)($opt['key'] ?? ''), $m)) {
$intQueId = (int)$m[1];
if (in_array($intQueId, $tabSelectedQue, true)) {
$tabQueCols[] = [
'que_id' => $intQueId,
'label' => (string)($opt['label'] ?? ('Q' . $intQueId)),
];
}
}
}
$intNbQ = count($tabQueCols);
$fltQTotal = max(0, $fltPageW - $fltFixedW);
$fltQEach = ($intNbQ > 0) ? ($fltQTotal / $intNbQ) : 0;
$pdf->SetFont('Arial', 'B', 11);
$pdf->Cell(0, 7, utf8_decode(fxBibDistPrintTruncate($tabEpr['epr_label'] ?? '', 90)), 0, 1, 'L');
$pdf->Ln(1);
$tabParticipants = fxBibGetParticipantsForDistPrint($epr_id, $strSort1, $strSort2, $strLangue);
if (empty($tabParticipants)) {
$pdf->SetFont('Arial', 'I', 9);
$pdf->Cell(0, 5, utf8_decode(fxBibTexte('bib_v4_dist_print_empty_epr', 0)), 0, 1, 'L');
$pdf->Ln(3);
continue;
}
$tabAnswers = fxBibGetDistPrintQuestionAnswers($epr_id, $tabSelectedQue, $tabParticipants, $strLangue);
$pdf->SetFont('Arial', 'B', 8);
$pdf->SetFillColor(230, 230, 230);
$pdf->Cell(18, 6, utf8_decode(fxBibTexte('bib_v4_dist_print_col_bib', 0)), 1, 0, 'C', true);
$pdf->Cell(42, 6, utf8_decode(fxBibTexte('bib_v4_dist_print_col_nom', 0)), 1, 0, 'L', true);
$pdf->Cell(42, 6, utf8_decode(fxBibTexte('bib_v4_dist_print_col_prenom', 0)), 1, 0, 'L', true);
$pdf->Cell(12, 6, utf8_decode(fxBibTexte('bib_v4_dist_print_col_sexe', 0)), 1, 0, 'C', true);
foreach ($tabQueCols as $i => $col) {
$blnLast = ($i === $intNbQ - 1);
$pdf->Cell($fltQEach, 6, utf8_decode(fxBibDistPrintTruncate($col['label'], 22)), 1, $blnLast ? 1 : 0, 'L', true);
}
if ($intNbQ === 0) {
$pdf->Ln();
}
$pdf->SetFont('Arial', '', 8);
foreach ($tabParticipants as $row) {
$strKey = (int)($row['par_id'] ?? 0) . ':' . (int)($row['pec_id'] ?? 0);
$pdf->Cell(18, 5, utf8_decode((string)(int)($row['no_bib'] ?? 0)), 1, 0, 'C');
$pdf->Cell(42, 5, utf8_decode(fxBibDistPrintTruncate($row['par_nom'] ?? '', 24)), 1, 0, 'L');
$pdf->Cell(42, 5, utf8_decode(fxBibDistPrintTruncate($row['par_prenom'] ?? '', 24)), 1, 0, 'L');
$pdf->Cell(12, 5, utf8_decode(fxBibDistPrintSexeLabel($row['par_sexe'] ?? '', $strLangue)), 1, 0, 'C');
foreach ($tabQueCols as $i => $col) {
$intQueId = (int)$col['que_id'];
$strAns = $tabAnswers[$strKey][$intQueId] ?? '';
$blnLast = ($i === $intNbQ - 1);
$pdf->Cell($fltQEach, 5, utf8_decode(fxBibDistPrintTruncate($strAns, 24)), 1, $blnLast ? 1 : 0, 'L');
}
if ($intNbQ === 0) {
$pdf->Ln();
}
}
$pdf->Ln(5);
}
$strFilename = 'distribution-dossards-' . $int_eve_id . '.pdf';
$pdf->Output('I', $strFilename);
}
/** MSIN-4433 — Coquille panneau impression (chargement AJAX). */
function renderBibDistPrintPanelShell($int_eve_id, $strLangue = 'fr') {
global $vDomaine;
$int_eve_id = (int)$int_eve_id;
$strWait = fxBibTexte('bib_v4_js_loader_wait', 0);
if ($strWait === '' || $strWait === 'bib_v4_js_loader_wait') {
$strWait = ($strLangue === 'en') ? 'Loading...' : 'Chargement...';
}
$strRunnerSrc = $vDomaine . '/images/ms1-runner-loader.gif?v=' . _VERSION_CODE;
ob_start();
?>
<div id="bib-dist-print-panel"
class="bib-dist-print-panel bib-anomalies-panel epr-block is-collapsed"
data-eve-id="<?php echo $int_eve_id; ?>"
data-bib-dist-print-deferred="1">
<div class="epr-header bib-anomalies-header">
<span class="epr-header-label bib-anomalies-header-label">
<?php echo fxBibEsc(fxBibTexte('bib_v4_dist_print_title', 0)); ?>
<?php echo fxBibAideButton('bib_v4_dist_print_title'); ?>
</span>
<?php echo fxBibBlockCollapseToggle($strLangue, true); ?>
</div>
<div class="epr-content bib-dist-print-content">
<div class="ms1-loader ms1-loader--inline" role="status" aria-live="polite" aria-busy="true">
<img class="ms1-loader__runner"
src="<?php echo fxBibEsc($strRunnerSrc); ?>"
alt=""
role="presentation"
decoding="async">
<span class="ms1-loader__label"><?php echo fxBibEsc($strWait); ?></span>
</div>
</div>
</div>
<?php
return ob_get_clean();
}
/** MSIN-4433 — Panneau impression PDF (formulaire + choix questions). */
function renderBibDistPrintPanel($int_eve_id, $strLangue = 'fr', $tabPanel = null) {
global $vDomaine;
$int_eve_id = (int)$int_eve_id;
if ($tabPanel === null) {
$tabPanel = fxBibCollectDistPrintPanelData($int_eve_id, $strLangue);
}
$tabMeta = $tabPanel['meta'] ?? [];
$tabEpreuves = $tabPanel['epreuves'] ?? [];
$intMaxQ = fxBibDistPrintMaxQuestions();
$strPrintUrl = $vDomaine . '/bib_dist_print.php';
ob_start();
?>
<div id="bib-dist-print-panel"
class="bib-dist-print-panel bib-anomalies-panel epr-block is-collapsed"
data-eve-id="<?php echo $int_eve_id; ?>"
data-bib-dist-print-loaded="1"
data-max-questions="<?php echo (int)$intMaxQ; ?>">
<div class="epr-header bib-anomalies-header">
<span class="epr-header-label bib-anomalies-header-label">
<?php echo fxBibEsc(fxBibTexte('bib_v4_dist_print_title', 0)); ?>
<?php echo fxBibAideButton('bib_v4_dist_print_title'); ?>
</span>
<?php echo fxBibBlockCollapseToggle($strLangue, true); ?>
</div>
<div class="epr-content bib-dist-print-content">
<p class="bib-dist-print-last text-muted small mb-3">
<?php
if (!empty($tabMeta['at'])) {
echo fxBibEsc(sprintf(
fxBibTexte('bib_v4_dist_print_last', 0),
fxBibFormatDistPrintDateTime($tabMeta['at'], $strLangue),
$tabMeta['by'] !== '' ? $tabMeta['by'] : '—'
));
} else {
fxBibTexteTrad('bib_v4_dist_print_last_none', 1);
}
?>
</p>
<p class="bib-dist-print-hint text-muted small mb-3">
<?php echo fxBibEsc(fxBibMsg('bib_v4_dist_print_max_questions', $intMaxQ)); ?>
</p>
<form class="bib-dist-print-form"
method="post"
action="<?php echo fxBibEsc($strPrintUrl); ?>"
target="_blank"
data-print-url="<?php echo fxBibEsc($strPrintUrl); ?>">
<input type="hidden" name="eve_id" value="<?php echo $int_eve_id; ?>">
<input type="hidden" name="csrf_token" value="<?php echo fxBibEsc(fxBibCsrfToken()); ?>">
<input type="hidden" name="lang" value="<?php echo fxBibEsc($strLangue); ?>">
<?php foreach ($tabEpreuves as $tabEpr) {
$epr_id = (int)($tabEpr['epr_id'] ?? 0);
$tabQuestions = $tabEpr['questions'] ?? [];
$tabSelected = $tabEpr['selected'] ?? [];
?>
<section class="bib-dist-print-epr" data-epr-id="<?php echo $epr_id; ?>">
<h3 class="bib-dist-print-epr-title">
<?php echo fxBibEsc($tabEpr['epr_label'] ?? ''); ?>
<span class="text-muted small">(<?php echo (int)($tabEpr['avec_bib'] ?? 0); ?>)</span>
</h3>
<?php if (empty($tabQuestions)) { ?>
<p class="text-muted small bib-dist-print-no-questions">
<?php fxBibTexteTrad('bib_v4_dist_print_no_questions', 1); ?>
</p>
<?php } else { ?>
<fieldset class="bib-dist-print-questions">
<legend class="sr-only"><?php echo fxBibEsc(fxBibTexte('bib_v4_dist_print_questions', 0)); ?></legend>
<span class="bib-dist-print-questions-label small text-muted d-block mb-1">
<?php fxBibTexteTrad('bib_v4_dist_print_questions', 1); ?>
</span>
<ul class="bib-dist-print-question-list list-unstyled mb-0">
<?php foreach ($tabQuestions as $opt) {
if (!preg_match('/^que:(\d+)$/', (string)($opt['key'] ?? ''), $m)) {
continue;
}
$intQueId = (int)$m[1];
$blnChecked = in_array($intQueId, $tabSelected, true);
?>
<li class="bib-dist-print-question-item">
<label class="bib-dist-print-question-label">
<input type="checkbox"
class="bib-dist-print-question-cb"
name="dist_print[<?php echo $epr_id; ?>][]"
value="<?php echo $intQueId; ?>"
<?php echo $blnChecked ? 'checked' : ''; ?>>
<span><?php echo fxBibEsc($opt['label'] ?? ''); ?></span>
</label>
</li>
<?php } ?>
</ul>
</fieldset>
<?php } ?>
</section>
<?php } ?>
<div class="bib-dist-print-actions mt-3">
<button type="submit" class="btn btn-primary btn-sm bib-dist-print-submit">
<?php fxBibTexteTrad('bib_v4_dist_print_generate', 1); ?>
</button>
</div>
</form>
</div>
</div>
<?php
return ob_get_clean();
}

View File

@ -7,7 +7,7 @@
* Constantes *
*
**************/
define('_VERSION_CODE', '4.72.758');
define('_VERSION_CODE', '4.72.759');
define('_DATE_CODE', '2026-07-08');
//MSIN-4290
define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe');

View File

@ -0,0 +1,164 @@
-- MSIN-4433 — Impression PDF distribution des dossards
-- Prérequis : MSIN-4436-bib-sort-levels.sql (questions tri batch)
-- Idempotent. Exécuter avant déploiement PHP/JS.
SET @db := DATABASE();
-- Questions affichées sur le rapport (IDs séparés par virgule, par épreuve)
SET @col_dist_que := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'inscriptions_epreuves' AND COLUMN_NAME = 'ba_bib_dist_que_ids'
);
SET @sql_dist_que := IF(
@col_dist_que = 0,
'ALTER TABLE inscriptions_epreuves ADD COLUMN ba_bib_dist_que_ids VARCHAR(128) NOT NULL DEFAULT '''' COMMENT ''MSIN-4433 — que_id cochés pour PDF distribution'' AFTER ba_bib_sort_2',
'SELECT ''ba_bib_dist_que_ids already exists'' AS note'
);
PREPARE stmt_dist_que FROM @sql_dist_que;
EXECUTE stmt_dist_que;
DEALLOCATE PREPARE stmt_dist_que;
-- Dernière impression (événement)
SET @col_print_at := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'inscriptions_evenements' AND COLUMN_NAME = 'eve_bib_dist_print_at'
);
SET @sql_print_at := IF(
@col_print_at = 0,
'ALTER TABLE inscriptions_evenements ADD COLUMN eve_bib_dist_print_at DATETIME NULL DEFAULT NULL COMMENT ''MSIN-4433 — dernière impression PDF distribution'' AFTER eve_bib_allow_inter_epr',
'SELECT ''eve_bib_dist_print_at already exists'' AS note'
);
PREPARE stmt_print_at FROM @sql_print_at;
EXECUTE stmt_print_at;
DEALLOCATE PREPARE stmt_print_at;
SET @col_print_by := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'inscriptions_evenements' AND COLUMN_NAME = 'eve_bib_dist_print_by'
);
SET @sql_print_by := IF(
@col_print_by = 0,
'ALTER TABLE inscriptions_evenements ADD COLUMN eve_bib_dist_print_by VARCHAR(128) NOT NULL DEFAULT '''' COMMENT ''MSIN-4433 — nom affiché dernière impression'' AFTER eve_bib_dist_print_at',
'SELECT ''eve_bib_dist_print_by already exists'' AS note'
);
PREPARE stmt_print_by FROM @sql_print_by;
EXECUTE stmt_print_by;
DEALLOCATE PREPARE stmt_print_by;
SET @col_print_com := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'inscriptions_evenements' AND COLUMN_NAME = 'eve_bib_dist_print_com_id'
);
SET @sql_print_com := IF(
@col_print_com = 0,
'ALTER TABLE inscriptions_evenements ADD COLUMN eve_bib_dist_print_com_id INT NOT NULL DEFAULT 0 COMMENT ''MSIN-4433 — com_id dernière impression'' AFTER eve_bib_dist_print_by',
'SELECT ''eve_bib_dist_print_com_id already exists'' AS note'
);
PREPARE stmt_print_com FROM @sql_print_com;
EXECUTE stmt_print_com;
DEALLOCATE PREPARE stmt_print_com;
-- Traductions (compte.php)
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_title', 'fr', 'Impression PDF — distribution des dossards', 'Liste imprimable pour le comptoir : une section par épreuve, une ligne par dossard assigné. Les colonnes de questions sont mémorisées à chaque génération.', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_title' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_title', 'en', 'PDF print — bib distribution', 'Printable list for the desk: one section per race, one row per assigned bib. Question columns are saved on each generation.', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_title' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_questions', 'fr', 'Colonnes optionnelles (questions)', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_questions' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_questions', 'en', 'Optional columns (questions)', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_questions' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_generate', 'fr', 'Générer le PDF', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_generate' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_generate', 'en', 'Generate PDF', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_generate' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_last', 'fr', 'Dernière impression : %s — %s', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_last' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_last', 'en', 'Last print: %s — %s', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_last' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_last_none', 'fr', 'Aucune impression enregistrée pour cet événement.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_last_none' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_last_none', 'en', 'No print recorded for this event yet.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_last_none' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_col_bib', 'fr', 'Dossard', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_col_bib' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_col_bib', 'en', 'Bib', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_col_bib' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_col_nom', 'fr', 'Nom', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_col_nom' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_col_nom', 'en', 'Last name', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_col_nom' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_col_prenom', 'fr', 'Prénom', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_col_prenom' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_col_prenom', 'en', 'First name', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_col_prenom' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_col_sexe', 'fr', 'Sexe', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_col_sexe' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_col_sexe', 'en', 'Gender', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_col_sexe' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_max_questions', 'fr', 'Maximum %d questions par épreuve pour le format lettre.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_max_questions' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_max_questions', 'en', 'Maximum %d questions per race for letter format.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_max_questions' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_empty_epr', 'fr', 'Aucun dossard assigné pour cette épreuve.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_empty_epr' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_empty_epr', 'en', 'No bib assigned for this race.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_empty_epr' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_no_questions', 'fr', 'Aucune question éligible pour cette épreuve.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_no_questions' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_no_questions', 'en', 'No eligible question for this race.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_no_questions' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_footer', 'fr', 'Imprimé le %s — %s', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_footer' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_dist_print_footer', 'en', 'Printed on %s — %s', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_footer' AND info_langue = 'en' AND info_prg = 'compte.php');