0) { $arrCandidates[] = $strDir . '/eve_' . $intEveId . '.jpg'; $arrCandidates[] = $strDir . '/eve_' . $intEveId . '.jpeg'; $arrCandidates[] = $strDir . '/eve_' . $intEveId . '.png'; } $arrCandidates[] = $strDir . '/default.jpg'; $arrCandidates[] = $strDir . '/default.jpeg'; $arrCandidates[] = $strDir . '/default.png'; foreach ($arrCandidates as $strPath) { if (is_file($strPath)) { return $strPath; } } return null; } /** * Type FPDF d'après le fichier réel. * * @return string JPG|PNG|GIF|'' */ function fxDossardPrintImageType($strPath) { $arrInfo = @getimagesize($strPath); if (!is_array($arrInfo) || empty($arrInfo[2])) { return ''; } if ((int)$arrInfo[2] === IMAGETYPE_JPEG) { return 'JPG'; } if ((int)$arrInfo[2] === IMAGETYPE_PNG) { return 'PNG'; } if ((int)$arrInfo[2] === IMAGETYPE_GIF) { return 'GIF'; } return ''; } /** Texte FPDF (core fonts = Windows-1252). */ function fxDossardPrintPdfText($str) { $str = (string)$str; if (function_exists('iconv')) { $strConverted = @iconv('UTF-8', 'Windows-1252//TRANSLIT', $str); if ($strConverted !== false) { return $strConverted; } } if (function_exists('mb_convert_encoding')) { return (string)mb_convert_encoding($str, 'Windows-1252', 'UTF-8'); } return $str; } /** Droit impression (hors kit total). */ function fxDossardPrintCan($intComId, $intEveId) { if (!function_exists('fxEveAccesHasPermission')) { require_once __DIR__ . '/inc_fx_eve_acces.php'; } return fxEveAccesHasPermission(intval($intComId), intval($intEveId), MSIN_DOSSARD_PRINT_PERM); } /** * @return array|null */ function fxDossardPrintLoadParticipant($intParId, $intEveId) { global $objDatabase; $intParId = intval($intParId); $intEveId = intval($intEveId); if ($intParId <= 0 || $intEveId <= 0) { return null; } $sql = 'SELECT p.par_id, p.par_prenom, p.par_nom, p.no_bib, p.eve_id, p.epr_id, p.pec_id, e.pec_id_original, e.pec_actif, e.is_cancelled FROM resultats_participants p INNER JOIN resultats_epreuves_commandees e ON e.pec_id_original = p.pec_id AND e.epr_id = p.epr_id WHERE p.par_id = ' . $intParId . ' AND p.eve_id = ' . $intEveId . ' LIMIT 1'; $row = $objDatabase->fxGetRow($sql); return ($row === null || $row === false) ? null : $row; } function fxDossardPrintQrUrl($intEveId) { if (!function_exists('fxKcEveResultatsQrUrl')) { require_once __DIR__ . '/inc_fx_eve_extra.php'; } return fxKcEveResultatsQrUrl(intval($intEveId)); } /** * @return string|false chemin PNG temp */ function fxDossardPrintBuildQrTempFile($intEveId) { require_once __DIR__ . '/../libs/phpqrcode-master/qrlib.php'; $strUrl = fxDossardPrintQrUrl($intEveId); if ($strUrl === '') { return false; } $strTmp = tempnam(sys_get_temp_dir(), 'ms1dossardqr_'); if ($strTmp === false) { return false; } $strPng = $strTmp . '.png'; @unlink($strTmp); QRcode::png($strUrl, $strPng, QR_ECLEVEL_M, 6, 1); if (!is_file($strPng)) { return false; } return $strPng; } /** * Fichier layout calibré (GUI drag & drop → %) — vérité pour le PDF. * * @param int $intEveId 0 = default * @return string */ function fxDossardPrintLayoutPath($intEveId = 0) { $strDir = fxDossardPrintTemplatesDir(); $intEveId = intval($intEveId); if ($intEveId > 0) { $strEve = $strDir . '/eve_' . $intEveId . '.layout.json'; if (is_file($strEve)) { return $strEve; } } return $strDir . '/default.layout.json'; } /** * Catalogue des champs exploitables dans une boîte (extensible). * * @return array */ function fxDossardPrintFieldCatalog() { return array( 'no_bib' => array('label' => 'N° dossard', 'sample' => '2347', 'font_max' => 220), 'par_prenom' => array('label' => 'Prénom', 'sample' => 'Cynthia', 'font_max' => 28), 'par_nom' => array('label' => 'Nom', 'sample' => 'Tremblay', 'font_max' => 28), 'par_prenom_nom' => array('label' => 'Prénom + nom', 'sample' => 'Cynthia Tremblay', 'font_max' => 22), // Réservés pour plus tard (valeur vide tant que non chargé en SQL) 'par_age' => array('label' => 'Âge', 'sample' => '42', 'font_max' => 24), 'par_sexe' => array('label' => 'Sexe', 'sample' => 'F', 'font_max' => 24), 'epr_nom' => array('label' => 'Épreuve', 'sample' => '10K', 'font_max' => 22), ); } /** * Layout défaut : 2 boîtes (n° + prénom) — modèle extensible N boîtes. * * @return array{version:int,page:string,boxes:array} */ function fxDossardPrintDefaultLayout() { return array( 'version' => 2, 'page' => '8.5x5.5', 'boxes' => array( array( 'id' => 'bib', 'field' => 'no_bib', 'align' => 'C', 'font_max' => 220, 'left' => 0.20, 'right' => 0.78, 'top' => 0.36, 'bottom' => 0.66, ), array( 'id' => 'prenom', 'field' => 'par_prenom', 'align' => 'L', 'font_max' => 28, 'left' => 0.32, 'right' => 0.68, 'top' => 0.72, 'bottom' => 0.84, ), ), ); } /** * Normalise JSON v1 (bib/name) ou v2 (boxes) → layout v2. * * @param array|null $arrData * @return array */ function fxDossardPrintNormalizeLayout($arrData) { $arrDefault = fxDossardPrintDefaultLayout(); if (!is_array($arrData)) { return $arrDefault; } // v1 legacy if (empty($arrData['boxes']) && !empty($arrData['bib']) && !empty($arrData['name'])) { $arrBoxes = $arrDefault['boxes']; foreach (array('left', 'right', 'top', 'bottom') as $strEdge) { if (isset($arrData['bib'][$strEdge])) { $arrBoxes[0][$strEdge] = floatval($arrData['bib'][$strEdge]); } if (isset($arrData['name'][$strEdge])) { $arrBoxes[1][$strEdge] = floatval($arrData['name'][$strEdge]); } } $arrDefault['boxes'] = $arrBoxes; return $arrDefault; } if (empty($arrData['boxes']) || !is_array($arrData['boxes'])) { return $arrDefault; } $arrCatalog = fxDossardPrintFieldCatalog(); $arrBoxes = array(); foreach ($arrData['boxes'] as $intIdx => $arrBox) { if (!is_array($arrBox)) { continue; } $strField = isset($arrBox['field']) ? (string)$arrBox['field'] : ''; if ($strField === '' || !isset($arrCatalog[$strField])) { continue; } foreach (array('left', 'right', 'top', 'bottom') as $strEdge) { if (!isset($arrBox[$strEdge])) { continue 2; } $flt = floatval($arrBox[$strEdge]); if ($flt < 0 || $flt > 1) { continue 2; } $arrBox[$strEdge] = $flt; } if ($arrBox['right'] <= $arrBox['left'] || $arrBox['bottom'] <= $arrBox['top']) { continue; } $strAlign = strtoupper((string)($arrBox['align'] ?? 'C')); if ($strAlign !== 'L' && $strAlign !== 'R') { $strAlign = 'C'; } $intFontMax = isset($arrBox['font_max']) ? max(8, intval($arrBox['font_max'])) : intval($arrCatalog[$strField]['font_max']); $strId = trim((string)($arrBox['id'] ?? '')); if ($strId === '') { $strId = 'box_' . ($intIdx + 1); } $arrBoxes[] = array( 'id' => $strId, 'field' => $strField, 'align' => $strAlign, 'font_max' => $intFontMax, 'left' => round($arrBox['left'], 5), 'right' => round($arrBox['right'], 5), 'top' => round($arrBox['top'], 5), 'bottom' => round($arrBox['bottom'], 5), ); } if (count($arrBoxes) < 1) { return $arrDefault; } return array( 'version' => 2, 'page' => '8.5x5.5', 'boxes' => $arrBoxes, 'updated_at' => isset($arrData['updated_at']) ? (string)$arrData['updated_at'] : null, ); } /** * Lit le layout calibré (JSON GUI) ou défaut. * * @param int $intEveId * @return array */ function fxDossardPrintLoadLayout($intEveId = 0) { $strPath = fxDossardPrintLayoutPath($intEveId); if (!is_file($strPath)) { return fxDossardPrintDefaultLayout(); } $strJson = @file_get_contents($strPath); $arrData = is_string($strJson) ? json_decode($strJson, true) : null; return fxDossardPrintNormalizeLayout($arrData); } /** * @deprecated alias GUI ancienne — préfère fxDossardPrintLoadLayout * @return array */ function fxDossardPrintLoadLayoutPct($intEveId = 0) { return fxDossardPrintLoadLayout($intEveId); } /** * Enregistre le layout calibré (liste de boîtes). * * @param array $arrLayout version + boxes[] * @param int $intEveId 0 = default.layout.json * @return array{ok:bool,error?:string,path?:string} */ function fxDossardPrintSaveLayout($arrLayout, $intEveId = 0) { $intEveId = intval($intEveId); $strDir = fxDossardPrintTemplatesDir(); if (!is_dir($strDir)) { return array('ok' => false, 'error' => 'Dossier templates absent.'); } $arrNorm = fxDossardPrintNormalizeLayout($arrLayout); if (empty($arrNorm['boxes'])) { return array('ok' => false, 'error' => 'Aucuneune boîte valide.'); } $strPath = ($intEveId > 0) ? $strDir . '/eve_' . $intEveId . '.layout.json' : $strDir . '/default.layout.json'; $arrOut = array( 'version' => 2, 'page' => '8.5x5.5', 'boxes' => $arrNorm['boxes'], 'updated_at' => date('c'), ); $strJson = json_encode($arrOut, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); if ($strJson === false || @file_put_contents($strPath, $strJson) === false) { return array('ok' => false, 'error' => 'Écriture impossible (droits fichier?).'); } return array('ok' => true, 'path' => $strPath); } /** * @deprecated — accepte encore {bib,name} ou {boxes} * @param array $arrPct * @param int $intEveId * @return array */ function fxDossardPrintSaveLayoutPct($arrPct, $intEveId = 0) { if (isset($arrPct['boxes'])) { return fxDossardPrintSaveLayout($arrPct, $intEveId); } return fxDossardPrintSaveLayout($arrPct, $intEveId); } /** * Valeur texte pour un champ boîte. * * @param array $arrRow * @param string $strField * @return string */ function fxDossardPrintResolveFieldValue($arrRow, $strField) { $fnUn = function ($str) { $str = (string)$str; return function_exists('fxUnescape') ? fxUnescape($str) : $str; }; switch ((string)$strField) { case 'no_bib': $str = trim((string)($arrRow['no_bib'] ?? '')); return ($str === '' || $str === '0') ? '-' : $str; case 'par_prenom': return trim($fnUn($arrRow['par_prenom'] ?? '')); case 'par_nom': return trim($fnUn($arrRow['par_nom'] ?? '')); case 'par_prenom_nom': return trim($fnUn($arrRow['par_prenom'] ?? '') . ' ' . $fnUn($arrRow['par_nom'] ?? '')); case 'par_age': return trim((string)($arrRow['par_age'] ?? $arrRow['age'] ?? '')); case 'par_sexe': return trim((string)($arrRow['par_sexe'] ?? '')); case 'epr_nom': return trim($fnUn($arrRow['epr_nom'] ?? $arrRow['epr_libelle'] ?? '')); default: return ''; } } /** * Layout mm pour le PDF — lit le JSON calibré (N boîtes). * * @param int $intEveId * @return array */ function fxDossardPrintLayout($intEveId = 0) { $arrSize = fxDossardPrintPageSizeMm(); $fltW = $arrSize[0]; $fltH = $arrSize[1]; $arrLayout = fxDossardPrintLoadLayout($intEveId); $arrBoxesMm = array(); foreach ($arrLayout['boxes'] as $arrBox) { $arrRect = fxDossardPrintBoxFromPct($fltW, $fltH, $arrBox); $arrBoxesMm[] = array_merge($arrBox, $arrRect); } $fltQr = 28.0; return array( 'page_w' => $fltW, 'page_h' => $fltH, 'boxes' => $arrBoxesMm, 'ascent_ratio' => 0.72, 'qr_size' => $fltQr, 'qr_x' => $fltW - $fltQr - 11.5, 'qr_y' => $fltH - $fltQr - 14.5, ); } /** * Rectangle mm depuis fractions 0–1 de la page. * * @param float $fltPageW * @param float $fltPageH * @param array $arrPct left|right|top|bottom * @return array{x:float,y:float,w:float,h:float} */ function fxDossardPrintBoxFromPct($fltPageW, $fltPageH, $arrPct) { $fltX = $fltPageW * floatval($arrPct['left']); $fltY = $fltPageH * floatval($arrPct['top']); $fltW = $fltPageW * (floatval($arrPct['right']) - floatval($arrPct['left'])); $fltH = $fltPageH * (floatval($arrPct['bottom']) - floatval($arrPct['top'])); return array( 'x' => $fltX, 'y' => $fltY, 'w' => $fltW, 'h' => $fltH, ); } /** * Plus grande police Helvetica Bold qui tient dans une boîte (largeur + hauteur). * * @param FPDF $pdf * @param string $strText * @param float $fltMaxW * @param float $fltMaxH * @param int $intFontMax * @param float $fltAscentRatio * @return int */ function fxDossardPrintFitBibFont($pdf, $strText, $fltMaxW, $fltMaxH, $intFontMax, $fltAscentRatio = 0.72) { $strPdf = fxDossardPrintPdfText($strText); $fltAscentRatio = ($fltAscentRatio > 0) ? $fltAscentRatio : 0.72; $fltPtPerMm = 72 / 25.4; $intCapByH = ($fltMaxH > 0) ? (int)floor($fltMaxH * $fltPtPerMm / $fltAscentRatio) : intval($intFontMax); $intHi = max(12, min(intval($intFontMax), $intCapByH)); $intLo = 12; $intBest = $intLo; while ($intLo <= $intHi) { $intMid = (int)floor(($intLo + $intHi) / 2); $pdf->SetFont('Helvetica', 'B', $intMid); if ($pdf->GetStringWidth($strPdf) <= $fltMaxW) { $intBest = $intMid; $intLo = $intMid + 1; } else { $intHi = $intMid - 1; } } return $intBest; } /** * Texte dans une boîte (Cell FPDF : align H + centre V de la cellule). * * @param FPDF $pdf * @param array $arrBox x,y,w,h * @param string $strText * @param int $intFont * @param string $strAlign C|L|R */ function fxDossardPrintDrawInBox($pdf, $arrBox, $strText, $intFont, $strAlign = 'C') { $strAlign = strtoupper((string)$strAlign); if ($strAlign !== 'L' && $strAlign !== 'R') { $strAlign = 'C'; } $pdf->SetFont('Helvetica', 'B', $intFont); $pdf->SetXY($arrBox['x'], $arrBox['y']); $pdf->Cell( $arrBox['w'], $arrBox['h'], fxDossardPrintPdfText($strText), 0, 0, $strAlign ); } /** @deprecated alias — préfère fxDossardPrintDrawInBox(..., 'C') */ function fxDossardPrintDrawCenteredInBox($pdf, $arrBox, $strText, $intFont) { fxDossardPrintDrawInBox($pdf, $arrBox, $strText, $intFont, 'C'); } /** * Génère et envoie le PDF (exit). * * @param array $arrRow */ function fxDossardPrintOutputPdf($arrRow) { $intEveId = intval($arrRow['eve_id'] ?? 0); $strTemplate = fxDossardPrintResolveTemplatePath($intEveId); if ($strTemplate === null) { http_response_code(500); header('Content-Type: text/plain; charset=utf-8'); echo 'Template dossard introuvable (assets/dossard_templates/default.jpg).'; exit; } $strFpdf = dirname(__DIR__) . '/fpdf182/fpdf.php'; if (!is_file($strFpdf)) { $strDocRoot = $_SERVER['DOCUMENT_ROOT'] ?? ''; $strFpdf = rtrim((string)$strDocRoot, '/\\') . '/fpdf182/fpdf.php'; } if (!is_file($strFpdf)) { http_response_code(500); header('Content-Type: text/plain; charset=utf-8'); echo 'FPDF introuvable (fpdf182/fpdf.php).'; exit; } require_once $strFpdf; $arrLayout = fxDossardPrintLayout($intEveId); $strQrFile = fxDossardPrintBuildQrTempFile($intEveId); $strTplType = fxDossardPrintImageType($strTemplate); $pdf = new FPDF('L', 'mm', array($arrLayout['page_w'], $arrLayout['page_h'])); $pdf->SetAutoPageBreak(false); $pdf->SetMargins(0, 0, 0); $pdf->AddPage(); $pdf->Image( $strTemplate, 0, 0, $arrLayout['page_w'], $arrLayout['page_h'], $strTplType ); $pdf->SetTextColor(0, 0, 0); // MSIN-4485 — N boîtes configurables (champ + align + %) $strBibForFile = '-'; foreach ($arrLayout['boxes'] as $arrBox) { $strText = fxDossardPrintResolveFieldValue($arrRow, $arrBox['field']); if ($arrBox['field'] === 'no_bib') { $strBibForFile = $strText; } if ($strText === '') { continue; } $intFont = fxDossardPrintFitBibFont( $pdf, $strText, $arrBox['w'] * 0.98, $arrBox['h'] * 0.92, intval($arrBox['font_max']), $arrLayout['ascent_ratio'] ); fxDossardPrintDrawInBox($pdf, $arrBox, $strText, $intFont, $arrBox['align']); } if ($strQrFile !== false) { $pdf->Image( $strQrFile, $arrLayout['qr_x'], $arrLayout['qr_y'], $arrLayout['qr_size'], $arrLayout['qr_size'], 'PNG' ); @unlink($strQrFile); } $strSafeBib = preg_replace('/[^a-zA-Z0-9_-]+/', '-', $strBibForFile); $strFilename = 'dossard-' . $strSafeBib . '-par' . intval($arrRow['par_id']) . '.pdf'; $pdf->Output('I', $strFilename); exit; }