Files
ms1inscription-v5/php/inc_fx_bib_production.php

842 lines
30 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* MSIN-4469 — Production Excel des dossards.
*
* Une commande active représente un instantané immuable des séquences. Le fichier
* peut être téléchargé de nouveau, mais une seconde génération est refusée tant
* que la commande active n'a pas été remplacée par un futur garde-fou.
*/
function fxBibProductionTemplatePath() {
return __DIR__ . DIRECTORY_SEPARATOR . 'templates'
. DIRECTORY_SEPARATOR . 'MSIN-4469-commande-dossards.xlsx';
}
function fxBibProductionStorageRoot() {
return dirname(__DIR__) . DIRECTORY_SEPARATOR . 'data'
. DIRECTORY_SEPARATOR . 'bib-production';
}
function fxBibProductionGetActiveOrder($intEveId) {
global $objDatabase;
$intEveId = (int)$intEveId;
if ($intEveId <= 0) {
return null;
}
return $objDatabase->fxGetRow(
"SELECT *
FROM inscriptions_bib_production_orders
WHERE eve_id = $intEveId
AND bpo_active_key = 1
LIMIT 1"
);
}
function fxBibProductionGetOrder($intOrderId, $intEveId = 0) {
global $objDatabase;
$intOrderId = (int)$intOrderId;
$intEveId = (int)$intEveId;
if ($intOrderId <= 0) {
return null;
}
$strWhereEve = $intEveId > 0 ? " AND eve_id = $intEveId" : '';
return $objDatabase->fxGetRow(
"SELECT *
FROM inscriptions_bib_production_orders
WHERE bpo_id = $intOrderId
$strWhereEve
LIMIT 1"
);
}
function fxBibProductionEventIsLocked($intEveId) {
return fxBibProductionGetActiveOrder($intEveId) !== null;
}
/**
* MSIN-4469 — Déverrouillage temporaire (dev) : même droit que la génération
* (promoteur avec dossards.manage, ou superadmin).
*/
function fxBibProductionDevelopmentUnlock($intEveId) {
global $objDatabase;
$intEveId = (int)$intEveId;
if ($intEveId <= 0 || !fxBibProductionUserCanManageEvent($intEveId)) {
return ['success' => false, 'message' => fxBibMsg('bib_v4_ajax_unauthorized')];
}
$tabOrder = fxBibProductionGetActiveOrder($intEveId);
if (!$tabOrder) {
return ['success' => true];
}
$strBy = fxBibGetCurrentPromoteurDisplayName();
$objDatabase->fxQuery(
"UPDATE inscriptions_bib_production_orders
SET bpo_status = 'development_unlocked',
bpo_active_key = NULL,
bpo_superseded_at = NOW(),
bpo_superseded_by = '" . $objDatabase->fxEscape($strBy) . "',
bpo_superseded_reason = 'MSIN-4469 development unlock'
WHERE bpo_id = " . (int)$tabOrder['bpo_id'] . "
AND bpo_active_key = 1"
);
return ['success' => true];
}
function fxBibProductionEpreuveIsLocked($intEprId) {
global $objDatabase;
$intEprId = (int)$intEprId;
if ($intEprId <= 0) {
return false;
}
$intEveId = (int)$objDatabase->fxGetVar(
"SELECT eve_id FROM inscriptions_epreuves WHERE epr_id = $intEprId LIMIT 1"
);
return fxBibProductionEventIsLocked($intEveId);
}
/**
* Vérifie l'accès réel à l'événement, en plus de la session et du jeton CSRF.
*/
function fxBibProductionUserCanManageEvent($intEveId) {
$intEveId = (int)$intEveId;
if ($intEveId <= 0 || !fxBibRequirePromoteurSession()) {
return false;
}
if (!empty($_SESSION['usa_id'])) {
return true;
}
$intComId = (int)($_SESSION['com_id'] ?? 0);
return $intComId > 0
&& function_exists('fxEveAccesHasPermission')
&& fxEveAccesHasPermission($intComId, $intEveId, 'dossards.manage');
}
/**
* Charge les épreuves actives et toutes leurs séquences, dans l'ordre d'affichage.
*/
function fxBibProductionCollectSnapshot($intEveId, $strLangue = 'fr') {
global $objDatabase;
$intEveId = (int)$intEveId;
$strLangue = in_array($strLangue, ['fr', 'en'], true) ? $strLangue : 'fr';
$tabEvent = $objDatabase->fxGetRow(
"SELECT eve_id, eve_nom_fr, eve_nom_en
FROM inscriptions_evenements
WHERE eve_id = $intEveId
LIMIT 1"
);
if (!$tabEvent) {
return ['success' => false, 'message' => fxBibMsg('bib_v5_production_event_invalid')];
}
$tabEpreuves = $objDatabase->fxGetResults(
"SELECT *
FROM inscriptions_epreuves
WHERE eve_id = $intEveId
AND epr_actif = 1
ORDER BY epr_tri, epr_id"
);
if (!is_array($tabEpreuves)) {
$tabEpreuves = [];
}
$tabRaces = [];
$intBibCount = 0;
$intRangeCount = 0;
foreach ($tabEpreuves as $tabEpreuve) {
$intEprId = (int)($tabEpreuve['epr_id'] ?? 0);
if ($intEprId <= 0) {
continue;
}
// MSIN-4469 — La commande porte sur toute la séquence à imprimer,
// même lorsquaucun coureur ni aucun dossard assigné ne sy trouve encore.
$tabRangesRaw = $objDatabase->fxGetResults(
"SELECT epr_bib_id, epr_bib_start, epr_bib_finish
FROM inscriptions_epreuves_bib
WHERE epr_id = $intEprId
ORDER BY epr_bib_start, epr_bib_id"
);
if (!is_array($tabRangesRaw)) {
continue;
}
$tabRanges = [];
foreach ($tabRangesRaw as $tabRange) {
$intStart = (int)($tabRange['epr_bib_start'] ?? 0);
$intFinish = (int)($tabRange['epr_bib_finish'] ?? 0);
if ($intStart <= 0 || $intFinish < $intStart) {
return ['success' => false, 'message' => fxBibMsg('bib_v5_production_range_invalid')];
}
$intQuantity = $intFinish - $intStart + 1;
$intBibCount += $intQuantity;
$intRangeCount++;
$tabRanges[] = [
'epr_bib_id' => (int)$tabRange['epr_bib_id'],
'start' => $intStart,
'finish' => $intFinish,
'quantity' => $intQuantity,
];
}
if (!empty($tabRanges)) {
$tabRaces[] = [
'epr_id' => $intEprId,
// Le libellé type/catégorie est le nom court retenu pour la V1.
'label' => fxBibEpreuveDisplayName($tabEpreuve, $strLangue),
'ranges' => $tabRanges,
];
}
}
if ($intRangeCount === 0) {
return ['success' => false, 'message' => fxBibMsg('bib_v5_production_no_ranges')];
}
return [
'success' => true,
'snapshot' => [
'event' => [
'eve_id' => $intEveId,
'name' => trim((string)($tabEvent['eve_nom_' . $strLangue] ?? '')),
],
'language' => $strLangue,
'bib_count' => $intBibCount,
'range_count' => $intRangeCount,
'races' => $tabRaces,
],
];
}
function fxBibProductionSafeFilePart($strValue) {
$strValue = trim((string)$strValue);
if (function_exists('iconv')) {
$strAscii = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $strValue);
if ($strAscii !== false) {
$strValue = $strAscii;
}
}
$strValue = strtolower($strValue);
$strValue = preg_replace('/[^a-z0-9]+/', '-', $strValue);
$strValue = trim((string)$strValue, '-');
return $strValue !== '' ? $strValue : 'evenement';
}
function fxBibProductionEnsureDirectory($strDirectory) {
if (is_dir($strDirectory)) {
return true;
}
return @mkdir($strDirectory, 0770, true) || is_dir($strDirectory);
}
function fxBibProductionXmlLoad($strXml) {
$objDom = new DOMDocument('1.0', 'UTF-8');
$objDom->preserveWhiteSpace = false;
$objDom->formatOutput = false;
if (!@$objDom->loadXML($strXml)) {
throw new Exception('MSIN-4469 invalid OpenXML part');
}
return $objDom;
}
/** @return DOMElement|null */
function fxBibProductionXmlFindRow(DOMXPath $objXpath, $intRow) {
$objRow = $objXpath->query('//m:sheetData/m:row[@r="' . (int)$intRow . '"]')->item(0);
return $objRow instanceof DOMElement ? $objRow : null;
}
function fxBibProductionXmlClearCell(DOMElement $objCell) {
while ($objCell->firstChild) {
$objCell->removeChild($objCell->firstChild);
}
$objCell->removeAttribute('t');
}
function fxBibProductionXmlFindCell(DOMElement $objRow, $strColumn) {
foreach ($objRow->childNodes as $objChild) {
if ($objChild instanceof DOMElement && $objChild->localName === 'c') {
$strRef = $objChild->getAttribute('r');
if (preg_replace('/[0-9]+/', '', $strRef) === $strColumn) {
return $objChild;
}
}
}
return null;
}
function fxBibProductionXmlEnsureCell(
DOMDocument $objDom,
DOMElement $objRow,
$strColumn,
$intRow,
$strStyle = ''
) {
$objCell = fxBibProductionXmlFindCell($objRow, $strColumn);
if (!$objCell) {
$objCell = $objDom->createElementNS(
'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
'c'
);
$objRow->appendChild($objCell);
}
$objCell->setAttribute('r', $strColumn . (int)$intRow);
if ($strStyle !== '') {
$objCell->setAttribute('s', $strStyle);
}
return $objCell;
}
function fxBibProductionXmlSetInlineString(
DOMDocument $objDom,
DOMElement $objRow,
$strColumn,
$intRow,
$strValue,
$strStyle = ''
) {
$strNs = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
$objCell = fxBibProductionXmlEnsureCell($objDom, $objRow, $strColumn, $intRow, $strStyle);
fxBibProductionXmlClearCell($objCell);
$objCell->setAttribute('t', 'inlineStr');
$objInline = $objDom->createElementNS($strNs, 'is');
$objText = $objDom->createElementNS($strNs, 't');
$objText->appendChild($objDom->createTextNode((string)$strValue));
$objInline->appendChild($objText);
$objCell->appendChild($objInline);
}
function fxBibProductionXmlSetNumber(
DOMDocument $objDom,
DOMElement $objRow,
$strColumn,
$intRow,
$mixValue,
$strStyle = ''
) {
$strNs = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
$objCell = fxBibProductionXmlEnsureCell($objDom, $objRow, $strColumn, $intRow, $strStyle);
fxBibProductionXmlClearCell($objCell);
$objValue = $objDom->createElementNS($strNs, 'v');
$objValue->appendChild($objDom->createTextNode((string)$mixValue));
$objCell->appendChild($objValue);
}
function fxBibProductionXmlSetFormula(
DOMDocument $objDom,
DOMElement $objRow,
$strColumn,
$intRow,
$strFormula,
$strStyle = ''
) {
$strNs = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
$objCell = fxBibProductionXmlEnsureCell($objDom, $objRow, $strColumn, $intRow, $strStyle);
fxBibProductionXmlClearCell($objCell);
$objFormula = $objDom->createElementNS($strNs, 'f');
$objFormula->appendChild($objDom->createTextNode($strFormula));
$objCell->appendChild($objFormula);
}
function fxBibProductionXmlCloneRow(DOMElement $objTemplateRow, $intTargetRow) {
$objRow = $objTemplateRow->cloneNode(true);
$objRow->setAttribute('r', (string)$intTargetRow);
foreach ($objRow->childNodes as $objChild) {
if ($objChild instanceof DOMElement && $objChild->localName === 'c') {
$strColumn = preg_replace('/[0-9]+/', '', $objChild->getAttribute('r'));
$objChild->setAttribute('r', $strColumn . $intTargetRow);
fxBibProductionXmlClearCell($objChild);
}
}
return $objRow;
}
function fxBibProductionXmlShiftReference($strReference, $intFromRow, $intOffset) {
return preg_replace_callback(
'/([A-Z]+)([0-9]+)/',
function ($tabMatch) use ($intFromRow, $intOffset) {
$intRow = (int)$tabMatch[2];
if ($intRow >= $intFromRow) {
$intRow += $intOffset;
}
return $tabMatch[1] . $intRow;
},
$strReference
);
}
function fxBibProductionBuildSupplierXml($strXml, array $tabSnapshot, array $tabLabels) {
$objDom = fxBibProductionXmlLoad($strXml);
$objXpath = new DOMXPath($objDom);
$objXpath->registerNamespace('m', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
$objSheetData = $objXpath->query('//m:sheetData')->item(0);
$objHeaderTemplate = fxBibProductionXmlFindRow($objXpath, 1);
$objDataTemplate = fxBibProductionXmlFindRow($objXpath, 2);
if (!$objSheetData || !$objHeaderTemplate || !$objDataTemplate) {
throw new Exception('MSIN-4469 invalid supplier template');
}
while ($objSheetData->firstChild) {
$objSheetData->removeChild($objSheetData->firstChild);
}
// MSIN-4469 — Convention imprimeur : cellules bleues = imprimées sur le dossard.
// Colonne A (numéro) = bleu ; colonne B (épreuve) = neutre (info, non imprimée).
$objHeader = fxBibProductionXmlCloneRow($objHeaderTemplate, 1);
$strHeaderStyleA = (string)(fxBibProductionXmlFindCell($objHeader, 'A')->getAttribute('s'));
$strHeaderStyleB = (string)(fxBibProductionXmlFindCell($objHeader, 'B')->getAttribute('s'));
fxBibProductionXmlSetInlineString($objDom, $objHeader, 'A', 1, $tabLabels['bib'], $strHeaderStyleA);
fxBibProductionXmlSetInlineString($objDom, $objHeader, 'B', 1, $tabLabels['race'], $strHeaderStyleB);
$objSheetData->appendChild($objHeader);
$strDataStyleA = (string)(fxBibProductionXmlFindCell($objDataTemplate, 'A')->getAttribute('s'));
$strDataStyleB = (string)(fxBibProductionXmlFindCell($objDataTemplate, 'B')->getAttribute('s'));
$intRow = 2;
foreach ($tabSnapshot['races'] as $intRaceIndex => $tabRace) {
if ($intRaceIndex > 0) {
$intRow++;
}
foreach ($tabRace['ranges'] as $tabRange) {
for ($intBib = (int)$tabRange['start']; $intBib <= (int)$tabRange['finish']; $intBib++) {
$objRow = fxBibProductionXmlCloneRow($objDataTemplate, $intRow);
fxBibProductionXmlSetNumber($objDom, $objRow, 'A', $intRow, $intBib, $strDataStyleA);
fxBibProductionXmlSetInlineString(
$objDom,
$objRow,
'B',
$intRow,
$tabRace['label'],
$strDataStyleB
);
$objSheetData->appendChild($objRow);
$intRow++;
}
}
}
$objDimension = $objXpath->query('//m:dimension')->item(0);
if ($objDimension instanceof DOMElement) {
$objDimension->setAttribute('ref', 'A1:B' . max(1, $intRow - 1));
}
return $objDom->saveXML();
}
function fxBibProductionBuildOrderXml($strXml, array $tabSnapshot, array $tabLabels) {
$objDom = fxBibProductionXmlLoad($strXml);
$objXpath = new DOMXPath($objDom);
$objXpath->registerNamespace('m', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
$objSheetData = $objXpath->query('//m:sheetData')->item(0);
$objBaseEvent = fxBibProductionXmlFindRow($objXpath, 4);
$objBaseSecond = fxBibProductionXmlFindRow($objXpath, 5);
$objBaseBlank = fxBibProductionXmlFindRow($objXpath, 6);
$objTotal = fxBibProductionXmlFindRow($objXpath, 31);
if (!$objSheetData || !$objBaseEvent || !$objBaseSecond || !$objTotal) {
throw new Exception('MSIN-4469 invalid order template');
}
if (!$objBaseBlank) {
$objBaseBlank = $objBaseSecond;
}
$tabOrderRows = [];
foreach ($tabSnapshot['races'] as $tabRace) {
foreach ($tabRace['ranges'] as $intRangeIndex => $tabRange) {
$tabOrderRows[] = [
// MSIN-4470 — Une ligne reste réservée à chaque séquence,
// mais le nom de l'épreuve n'est répété que sur la première.
'label' => $intRangeIndex === 0 ? $tabRace['label'] : '',
'start' => (int)$tabRange['start'],
'finish' => (int)$tabRange['finish'],
'quantity' => (int)$tabRange['quantity'],
];
}
}
$intSlotCount = max(9, count($tabOrderRows));
$intExtraRows = ($intSlotCount - 9) * 3;
$intTotalRow = 31 + $intExtraRows;
foreach ($objXpath->query('//m:sheetData/m:row') as $objRow) {
if (!($objRow instanceof DOMElement)) {
continue;
}
$intCurrentRow = (int)$objRow->getAttribute('r');
if ($intCurrentRow >= 31 && $intExtraRows > 0) {
$intShiftedRow = $intCurrentRow + $intExtraRows;
$objRow->setAttribute('r', (string)$intShiftedRow);
foreach ($objRow->childNodes as $objCell) {
if ($objCell instanceof DOMElement && $objCell->localName === 'c') {
$strColumn = preg_replace('/[0-9]+/', '', $objCell->getAttribute('r'));
$objCell->setAttribute('r', $strColumn . $intShiftedRow);
}
}
}
}
if ($intExtraRows > 0) {
foreach ($objXpath->query('//m:mergeCell') as $objMerge) {
if (!($objMerge instanceof DOMElement)) {
continue;
}
$objMerge->setAttribute(
'ref',
fxBibProductionXmlShiftReference($objMerge->getAttribute('ref'), 31, $intExtraRows)
);
}
}
$tabRemove = [];
foreach ($objXpath->query('//m:sheetData/m:row') as $objRow) {
if (!($objRow instanceof DOMElement)) {
continue;
}
$intCurrentRow = (int)$objRow->getAttribute('r');
if ($intCurrentRow >= 4 && $intCurrentRow < $intTotalRow) {
$tabRemove[] = $objRow;
}
}
foreach ($tabRemove as $objRow) {
$objSheetData->removeChild($objRow);
}
$objTotal = fxBibProductionXmlFindRow($objXpath, $intTotalRow);
if (!$objTotal) {
throw new Exception('MSIN-4469 total row missing');
}
for ($intSlot = 0; $intSlot < $intSlotCount; $intSlot++) {
$intRow = 4 + ($intSlot * 3);
$objEventRow = fxBibProductionXmlCloneRow($objBaseEvent, $intRow);
$objSecondRow = fxBibProductionXmlCloneRow($objBaseSecond, $intRow + 1);
$objBlankRow = fxBibProductionXmlCloneRow($objBaseBlank, $intRow + 2);
if (isset($tabOrderRows[$intSlot])) {
$tabRow = $tabOrderRows[$intSlot];
fxBibProductionXmlSetInlineString($objDom, $objEventRow, 'A', $intRow, $tabRow['label']);
fxBibProductionXmlSetNumber($objDom, $objEventRow, 'F', $intRow, $tabRow['quantity']);
fxBibProductionXmlSetNumber($objDom, $objEventRow, 'G', $intRow, $tabRow['start']);
fxBibProductionXmlSetNumber($objDom, $objEventRow, 'H', $intRow, $tabRow['finish']);
fxBibProductionXmlSetFormula($objDom, $objEventRow, 'I', $intRow, 'H' . $intRow . '-G' . $intRow . '+1');
fxBibProductionXmlSetFormula($objDom, $objEventRow, 'J', $intRow, 'I' . $intRow . '=F' . $intRow);
// MSIN-4469 — V1 : une puce par dossard, avec le même numéro.
fxBibProductionXmlSetNumber($objDom, $objEventRow, 'L', $intRow, $tabRow['quantity']);
fxBibProductionXmlSetNumber($objDom, $objEventRow, 'M', $intRow, $tabRow['start']);
fxBibProductionXmlSetNumber($objDom, $objEventRow, 'N', $intRow, $tabRow['finish']);
fxBibProductionXmlSetFormula($objDom, $objEventRow, 'O', $intRow, 'G' . $intRow . '=M' . $intRow);
fxBibProductionXmlSetFormula($objDom, $objEventRow, 'P', $intRow, 'H' . $intRow . '=N' . $intRow);
}
$objSheetData->insertBefore($objEventRow, $objTotal);
$objSheetData->insertBefore($objSecondRow, $objTotal);
$objSheetData->insertBefore($objBlankRow, $objTotal);
}
$objTitleRow = fxBibProductionXmlFindRow($objXpath, 1);
if ($objTitleRow) {
fxBibProductionXmlSetInlineString(
$objDom,
$objTitleRow,
'A',
1,
$tabSnapshot['event']['name']
);
}
fxBibProductionXmlSetInlineString($objDom, $objTotal, 'A', $intTotalRow, $tabLabels['total']);
fxBibProductionXmlSetFormula($objDom, $objTotal, 'F', $intTotalRow, 'SUM(F4:F' . ($intTotalRow - 1) . ')');
fxBibProductionXmlSetFormula($objDom, $objTotal, 'I', $intTotalRow, 'SUM(I4:I' . ($intTotalRow - 1) . ')');
fxBibProductionXmlSetFormula($objDom, $objTotal, 'L', $intTotalRow, 'SUM(L4:L' . ($intTotalRow - 1) . ')');
$objDimension = $objXpath->query('//m:dimension')->item(0);
if ($objDimension instanceof DOMElement) {
$objDimension->setAttribute('ref', 'A1:Q' . (48 + $intExtraRows));
}
return $objDom->saveXML();
}
function fxBibProductionBuildWorkbook(array $tabSnapshot, $strDestination, array $tabLabels) {
$strTemplate = fxBibProductionTemplatePath();
if (!is_file($strTemplate)) {
throw new Exception('MSIN-4469 template missing');
}
if (!class_exists('ZipArchive') || !class_exists('DOMDocument')) {
throw new Exception('MSIN-4469 ZIP/XML extensions missing');
}
if (!copy($strTemplate, $strDestination)) {
throw new Exception('MSIN-4469 template copy failed');
}
$objZip = new ZipArchive();
if ($objZip->open($strDestination) !== true) {
throw new Exception('MSIN-4469 workbook open failed');
}
try {
$strSupplierXml = $objZip->getFromName('xl/worksheets/sheet1.xml');
$strOrderXml = $objZip->getFromName('xl/worksheets/sheet2.xml');
if ($strSupplierXml === false || $strOrderXml === false) {
throw new Exception('MSIN-4469 worksheet missing');
}
$strSupplierXml = fxBibProductionBuildSupplierXml($strSupplierXml, $tabSnapshot, $tabLabels);
$strOrderXml = fxBibProductionBuildOrderXml($strOrderXml, $tabSnapshot, $tabLabels);
$objZip->deleteName('xl/worksheets/sheet1.xml');
$objZip->addFromString('xl/worksheets/sheet1.xml', $strSupplierXml);
$objZip->deleteName('xl/worksheets/sheet2.xml');
$objZip->addFromString('xl/worksheets/sheet2.xml', $strOrderXml);
$strWorkbookXml = $objZip->getFromName('xl/workbook.xml');
if ($strWorkbookXml !== false) {
$objWorkbookDom = fxBibProductionXmlLoad($strWorkbookXml);
$objWorkbookXpath = new DOMXPath($objWorkbookDom);
$objWorkbookXpath->registerNamespace('m', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
$objCalcPr = $objWorkbookXpath->query('//m:calcPr')->item(0);
if ($objCalcPr instanceof DOMElement) {
$objCalcPr->setAttribute('fullCalcOnLoad', '1');
$objCalcPr->setAttribute('forceFullCalc', '1');
}
$objZip->deleteName('xl/workbook.xml');
$objZip->addFromString('xl/workbook.xml', $objWorkbookDom->saveXML());
}
} catch (Exception $objException) {
$objZip->close();
@unlink($strDestination);
throw $objException;
}
$objZip->close();
}
/**
* Crée la commande officielle et verrouille immédiatement les séquences via
* bpo_active_key. Une requête concurrente retrouve la commande déjà active.
*/
function fxBibProductionCreateOfficialOrder($intEveId, $strLangue = 'fr') {
global $objDatabase;
$intEveId = (int)$intEveId;
$tabExisting = fxBibProductionGetActiveOrder($intEveId);
if ($tabExisting) {
return ['success' => true, 'order' => $tabExisting, 'existing' => true];
}
$tabCollected = fxBibProductionCollectSnapshot($intEveId, $strLangue);
if (empty($tabCollected['success'])) {
return $tabCollected;
}
$tabSnapshot = $tabCollected['snapshot'];
$strSnapshot = json_encode($tabSnapshot, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($strSnapshot === false) {
return ['success' => false, 'message' => fxBibMsg('bib_v5_production_generation_error')];
}
$strCreatedBy = fxBibGetCurrentPromoteurDisplayName();
$intComId = (int)($_SESSION['com_id'] ?? 0);
$intUsaId = (int)($_SESSION['usa_id'] ?? 0);
$strEventPart = fxBibProductionSafeFilePart($tabSnapshot['event']['name']);
$objDatabase->fxQuery('START TRANSACTION');
$objDatabase->fxGetVar(
"SELECT eve_id FROM inscriptions_evenements WHERE eve_id = $intEveId FOR UPDATE"
);
$tabExisting = fxBibProductionGetActiveOrder($intEveId);
if ($tabExisting) {
$objDatabase->fxQuery('COMMIT');
return ['success' => true, 'order' => $tabExisting, 'existing' => true];
}
$intVersion = (int)$objDatabase->fxGetVar(
"SELECT COALESCE(MAX(bpo_version), 0) + 1
FROM inscriptions_bib_production_orders
WHERE eve_id = $intEveId"
);
if ($intVersion <= 0) {
$intVersion = 1;
}
$strFileName = 'commande-dossards-' . $strEventPart . '-v' . $intVersion . '.xlsx';
$strInsert = "
INSERT INTO inscriptions_bib_production_orders (
eve_id,
bpo_com_id,
bpo_usa_id,
bpo_status,
bpo_active_key,
bpo_version,
bpo_file_name,
bpo_file_path,
bpo_sha256,
bpo_snapshot,
bpo_created_at,
bpo_created_by
) VALUES (
$intEveId,
$intComId,
$intUsaId,
'generating',
1,
$intVersion,
'" . $objDatabase->fxEscape($strFileName) . "',
'',
'',
'" . $objDatabase->fxEscape($strSnapshot) . "',
NOW(),
'" . $objDatabase->fxEscape($strCreatedBy) . "'
)
";
$qryInsert = $objDatabase->fxQuery($strInsert);
$intOrderId = $qryInsert ? (int)$objDatabase->fxGetLastId() : 0;
if ($intOrderId <= 0) {
$objDatabase->fxQuery('ROLLBACK');
$tabExisting = fxBibProductionGetActiveOrder($intEveId);
if ($tabExisting) {
return ['success' => true, 'order' => $tabExisting, 'existing' => true];
}
return ['success' => false, 'message' => fxBibMsg('bib_v5_production_generation_error')];
}
foreach ($tabSnapshot['races'] as $tabRace) {
foreach ($tabRace['ranges'] as $tabRange) {
$qryRange = $objDatabase->fxQuery(
"INSERT INTO inscriptions_bib_production_ranges (
bpo_id,
eve_id,
epr_id,
epr_bib_id,
bpr_epr_label,
bpr_start,
bpr_finish,
bpr_quantity
) VALUES (
$intOrderId,
$intEveId,
" . (int)$tabRace['epr_id'] . ",
" . (int)$tabRange['epr_bib_id'] . ",
'" . $objDatabase->fxEscape($tabRace['label']) . "',
" . (int)$tabRange['start'] . ",
" . (int)$tabRange['finish'] . ",
" . (int)$tabRange['quantity'] . "
)"
);
if ($qryRange === false) {
$objDatabase->fxQuery('ROLLBACK');
return ['success' => false, 'message' => fxBibMsg('bib_v5_production_generation_error')];
}
}
}
$objDatabase->fxQuery('COMMIT');
$strRelativeDir = 'eve-' . $intEveId . DIRECTORY_SEPARATOR . 'order-' . $intOrderId;
$strAbsoluteDir = fxBibProductionStorageRoot() . DIRECTORY_SEPARATOR . $strRelativeDir;
$strAbsoluteFile = $strAbsoluteDir . DIRECTORY_SEPARATOR . $strFileName;
try {
if (!fxBibProductionEnsureDirectory($strAbsoluteDir)) {
throw new Exception('MSIN-4469 storage unavailable');
}
$strStorageRoot = fxBibProductionStorageRoot();
if (fxBibProductionEnsureDirectory($strStorageRoot)) {
$strDenyFile = $strStorageRoot . DIRECTORY_SEPARATOR . '.htaccess';
if (!is_file($strDenyFile)) {
@file_put_contents($strDenyFile, "Deny from all\n");
}
}
fxBibProductionBuildWorkbook(
$tabSnapshot,
$strAbsoluteFile,
[
'bib' => fxBibTexte('bib_v5_production_col_bib', 0),
'race' => fxBibTexte('bib_v5_production_col_race', 0),
'total' => fxBibTexte('bib_v5_production_total', 0),
'title' => fxBibTexte('bib_v5_production_excel_title', 0),
]
);
if (!is_file($strAbsoluteFile)) {
throw new Exception('MSIN-4469 workbook not written');
}
$strRelativeFile = str_replace('\\', '/', $strRelativeDir . DIRECTORY_SEPARATOR . $strFileName);
$strSha256 = hash_file('sha256', $strAbsoluteFile);
$objDatabase->fxQuery(
"UPDATE inscriptions_bib_production_orders
SET bpo_status = 'ready',
bpo_file_path = '" . $objDatabase->fxEscape($strRelativeFile) . "',
bpo_sha256 = '" . $objDatabase->fxEscape($strSha256) . "'
WHERE bpo_id = $intOrderId
AND bpo_active_key = 1"
);
} catch (Exception $objException) {
if (is_file($strAbsoluteFile)) {
@unlink($strAbsoluteFile);
}
$objDatabase->fxQuery(
"UPDATE inscriptions_bib_production_orders
SET bpo_status = 'failed',
bpo_active_key = NULL,
bpo_error = '" . $objDatabase->fxEscape($objException->getMessage()) . "'
WHERE bpo_id = $intOrderId"
);
return ['success' => false, 'message' => fxBibMsg('bib_v5_production_generation_error')];
}
return [
'success' => true,
'order' => fxBibProductionGetOrder($intOrderId, $intEveId),
'existing' => false,
];
}
function fxBibProductionOrderAbsolutePath(array $tabOrder) {
$strRelative = str_replace('\\', '/', (string)($tabOrder['bpo_file_path'] ?? ''));
if ($strRelative === '' || strpos($strRelative, '..') !== false || strpos($strRelative, "\0") !== false) {
return '';
}
return fxBibProductionStorageRoot() . DIRECTORY_SEPARATOR
. str_replace('/', DIRECTORY_SEPARATOR, $strRelative);
}
function fxBibProductionStreamOrder(array $tabOrder) {
if (($tabOrder['bpo_status'] ?? '') !== 'ready') {
return false;
}
$strFile = fxBibProductionOrderAbsolutePath($tabOrder);
if ($strFile === '' || !is_file($strFile)) {
return false;
}
while (ob_get_level() > 0) {
ob_end_clean();
}
$strDownloadName = basename((string)$tabOrder['bpo_file_name']);
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment; filename="' . str_replace('"', '', $strDownloadName) . '"');
header('Content-Length: ' . filesize($strFile));
header('Cache-Control: private, no-store, max-age=0');
header('X-Content-Type-Options: nosniff');
readfile($strFile);
return true;
}