402 lines
11 KiB
PHP
402 lines
11 KiB
PHP
<?php
|
||
/**
|
||
* MSIN-4485 — Impression dossard PDF (1 participant).
|
||
*
|
||
* Format FINAL : demi-feuille Lettre coupée sur le 11"
|
||
* → papier 8.5" large × 5.5" haut (paysage)
|
||
* → PDF page = exactement cette taille
|
||
* → fond pleine page + n° / nom / QR alignés
|
||
*
|
||
* Impression : papier personnalisé 8.5 × 5.5, échelle 100 %, sans « ajuster à la page ».
|
||
*/
|
||
|
||
define('MSIN_DOSSARD_PRINT_PERM', 'inscriptions_gestion.dossard_print');
|
||
|
||
/**
|
||
* Taille page = feuille coupée (pouces → mm).
|
||
* Lettre 8.5×11 coupée en 2 sur le 11" → 8.5 × 5.5.
|
||
*
|
||
* @return array{0:float,1:float} [largeur, hauteur]
|
||
*/
|
||
function fxDossardPrintPageSizeMm()
|
||
{
|
||
return array(8.5 * 25.4, 5.5 * 25.4);
|
||
}
|
||
|
||
/** Dossier des fonds JPG/PNG (même ratio 8.5:5.5 recommandé). */
|
||
function fxDossardPrintTemplatesDir()
|
||
{
|
||
return dirname(__DIR__) . '/assets/dossard_templates';
|
||
}
|
||
|
||
/**
|
||
* Fond : eve_{id}.jpg|png sinon default.jpg|png.
|
||
*
|
||
* @return string|null
|
||
*/
|
||
function fxDossardPrintResolveTemplatePath($intEveId)
|
||
{
|
||
$strDir = fxDossardPrintTemplatesDir();
|
||
$intEveId = intval($intEveId);
|
||
$arrCandidates = array();
|
||
if ($intEveId > 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;
|
||
}
|
||
|
||
/**
|
||
* Layout calibré pour le fond comic default (8.5" × 5.5").
|
||
* Deux boîtes indépendantes en % de page (références user annotées).
|
||
*
|
||
* @return array<string,mixed>
|
||
*/
|
||
function fxDossardPrintLayout()
|
||
{
|
||
$arrSize = fxDossardPrintPageSizeMm();
|
||
$fltW = $arrSize[0];
|
||
$fltH = $arrSize[1];
|
||
|
||
// --- Boîte NUMÉRO (grande) — % page, d'après annotation rouge ---
|
||
$arrBibPct = array(
|
||
'left' => 0.20,
|
||
'right' => 0.78,
|
||
'top' => 0.36,
|
||
'bottom' => 0.66,
|
||
);
|
||
// --- Boîte PRÉNOM (petite, plus bas, centrée) — PAS collée au n° ---
|
||
$arrNamePct = array(
|
||
'left' => 0.32,
|
||
'right' => 0.68,
|
||
'top' => 0.72,
|
||
'bottom' => 0.84,
|
||
);
|
||
|
||
$arrBib = fxDossardPrintBoxFromPct($fltW, $fltH, $arrBibPct);
|
||
$arrName = fxDossardPrintBoxFromPct($fltW, $fltH, $arrNamePct);
|
||
|
||
$fltQr = 28.0;
|
||
|
||
return array(
|
||
'page_w' => $fltW,
|
||
'page_h' => $fltH,
|
||
'bib' => $arrBib,
|
||
'name' => $arrName,
|
||
'ascent_ratio' => 0.72,
|
||
'bib_font_max' => 220,
|
||
'name_font_max' => 28,
|
||
'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<string,float> $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 centré dans une boîte (Cell = centre H + V FPDF).
|
||
*
|
||
* @param FPDF $pdf
|
||
* @param array $arrBox x,y,w,h
|
||
* @param string $strText
|
||
* @param int $intFont
|
||
*/
|
||
function fxDossardPrintDrawCenteredInBox($pdf, $arrBox, $strText, $intFont)
|
||
{
|
||
$pdf->SetFont('Helvetica', 'B', $intFont);
|
||
$pdf->SetXY($arrBox['x'], $arrBox['y']);
|
||
$pdf->Cell(
|
||
$arrBox['w'],
|
||
$arrBox['h'],
|
||
fxDossardPrintPdfText($strText),
|
||
0,
|
||
0,
|
||
'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();
|
||
$strBib = trim((string)($arrRow['no_bib'] ?? ''));
|
||
if ($strBib === '' || $strBib === '0') {
|
||
$strBib = '-';
|
||
}
|
||
// MSIN-4485 — prénom seul, centré dans sa boîte (%).
|
||
$strName = trim(function_exists('fxUnescape') ? fxUnescape($arrRow['par_prenom'] ?? '') : (string)($arrRow['par_prenom'] ?? ''));
|
||
|
||
$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);
|
||
|
||
// 1) Numéro → grande boîte, centré, police max
|
||
$arrBibBox = $arrLayout['bib'];
|
||
$intBibFont = fxDossardPrintFitBibFont(
|
||
$pdf,
|
||
$strBib,
|
||
$arrBibBox['w'] * 0.98,
|
||
$arrBibBox['h'] * 0.92,
|
||
$arrLayout['bib_font_max'],
|
||
$arrLayout['ascent_ratio']
|
||
);
|
||
fxDossardPrintDrawCenteredInBox($pdf, $arrBibBox, $strBib, $intBibFont);
|
||
|
||
// 2) Prénom → petite boîte, centré, police max
|
||
if ($strName !== '') {
|
||
$arrNameBox = $arrLayout['name'];
|
||
$intNameFont = fxDossardPrintFitBibFont(
|
||
$pdf,
|
||
$strName,
|
||
$arrNameBox['w'] * 0.95,
|
||
$arrNameBox['h'] * 0.90,
|
||
$arrLayout['name_font_max'],
|
||
$arrLayout['ascent_ratio']
|
||
);
|
||
fxDossardPrintDrawCenteredInBox($pdf, $arrNameBox, $strName, $intNameFont);
|
||
}
|
||
|
||
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_-]+/', '-', $strBib);
|
||
$strFilename = 'dossard-' . $strSafeBib . '-par' . intval($arrRow['par_id']) . '.pdf';
|
||
|
||
$pdf->Output('I', $strFilename);
|
||
exit;
|
||
}
|