| Server IP : 188.114.96.3 / Your IP : 216.73.216.143 Web Server : Apache System : Linux sv058.home.net.pl 6.8.2 husak_dirty_pages_fix #1 SMP PREEMPT_DYNAMIC Tue Apr 2 22:06:51 CEST 2024 x86_64 User : ( 3194769) PHP Version : 8.5.12 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /polana/includes/ |
Upload File : |
<?php
declare(strict_types=1);
/** TKLM Polana Word exporter V3: DOCX generated from existing DB fields, no third-party API, no uploaded secrets. */
require_once __DIR__ . '/tklm_menu_core.php';
function tm_word_xml(string $s): string {
$s = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $s) ?? '';
return htmlspecialchars($s, ENT_QUOTES | ENT_XML1 | ENT_SUBSTITUTE, 'UTF-8');
}
function tm_word_para(string $text, string $style = 'Normal', string $align = ''): string {
$p = '<w:pPr><w:pStyle w:val="' . tm_word_xml($style) . '"/>';
if ($align !== '') $p .= '<w:jc w:val="' . tm_word_xml($align) . '"/>';
$p .= '</w:pPr>';
$lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $text));
$t = '';
foreach ($lines as $index => $line) {
if ($index) $t .= '<w:br/>';
$t .= '<w:t xml:space="preserve">' . tm_word_xml($line) . '</w:t>';
}
return '<w:p>' . $p . '<w:r>' . $t . '</w:r></w:p>';
}
function tm_word_cell(string $content, int $width): string {
return '<w:tc><w:tcPr><w:tcW w:w="' . $width . '" w:type="dxa"/><w:vAlign w:val="top"/></w:tcPr>' . $content . '</w:tc>';
}
function tm_word_price(array $item): string {
$p = trim((string)($item['price_text'] ?? ''));
if ($p !== '') return $p;
if (isset($item['price']) && $item['price'] !== '' && $item['price'] !== null) {
return number_format((float)$item['price'], 0, ',', ' ') . ' zł';
}
return '';
}
/** @return array{missing:array<string>, xml:string, count:int} */
function tm_word_document_parts(array $menu, array $cats, string $lang, bool $draft): array {
if (!in_array($lang, ['pl', 'en'], true)) throw new RuntimeException('Język musi być PL albo EN.');
$en = $lang === 'en';
$missing = []; $count = 0;
$body = tm_word_para('MOJA POLANA SMAKÓW', 'Brand', 'center');
$body .= tm_word_para($en ? 'RESTAURANT MENU' : 'KARTA RESTAURACJI', 'Subtitle', 'center');
if ($draft) $body .= tm_word_para($en ? 'DRAFT — CHECK TRANSLATIONS BEFORE PRINTING' : 'WERSJA ROBOCZA', 'Warning', 'center');
$body .= tm_word_para('Kraków • ' . date('d.m.Y'), 'Small', 'center');
foreach ($cats as $cat) {
if (empty($cat['active'])) continue;
$items = array_values(array_filter($cat['items'] ?? [], static fn(array $it):bool => !empty($it['active']) && !empty($it['available'])));
if (!$items) continue;
$cName = trim((string)($cat[$en ? 'name_en' : 'name'] ?? ''));
if ($en && $cName === '') {
$missing[] = 'Kategoria #' . (int)$cat['id'] . ': ' . (string)$cat['name'];
$cName = '[EN MISSING] ' . (string)$cat['name'];
}
$body .= tm_word_para($cName, 'Category', 'center');
foreach ($items as $item) {
$count++;
$name = trim((string)($item[$en ? 'name_en' : 'name'] ?? ''));
if ($en && $name === '') {
$missing[] = 'Danie #' . (int)$item['id'] . ': ' . (string)$item['name'];
$name = '[EN MISSING] ' . (string)$item['name'];
}
$description = trim((string)($item[$en ? 'description_en' : 'description'] ?? ''));
if ($en && $description === '' && trim((string)($item['description'] ?? '')) !== '') {
$missing[] = 'Opis dania #' . (int)$item['id'];
$description = '[EN MISSING] ' . (string)$item['description'];
}
$allergens = trim((string)($item[$en ? 'allergens_en' : 'allergens'] ?? ''));
if ($en && $allergens === '' && trim((string)($item['allergens'] ?? '')) !== '') {
$missing[] = 'Alergeny dania #' . (int)$item['id'];
$allergens = '[EN MISSING] ' . (string)$item['allergens'];
}
$left = tm_word_para($name, 'Dish');
if ($description !== '') $left .= tm_word_para($description, 'Description');
if ($allergens !== '') $left .= tm_word_para(($en ? 'Allergens: ' : 'Alergeny: ') . $allergens, 'Small');
$right = tm_word_para(tm_word_price($item), 'Price', 'right');
$body .= '<w:tbl><w:tblPr><w:tblW w:w="9300" w:type="dxa"/><w:tblInd w:w="0" w:type="dxa"/><w:tblCellMar><w:bottom w:w="125" w:type="dxa"/></w:tblCellMar></w:tblPr>';
$body .= '<w:tblGrid><w:gridCol w:w="7700"/><w:gridCol w:w="1600"/></w:tblGrid>';
$body .= '<w:tr><w:trPr><w:cantSplit/></w:trPr>' . tm_word_cell($left,7700) . tm_word_cell($right,1600) . '</w:tr></w:tbl>';
}
}
if ($en && $missing && !$draft) {
throw new RuntimeException('Karta ma ' . count($missing) . ' brakujących tłumaczeń EN. Wróć do panelu i przetłumacz je lub wybierz eksport ROBOCZY.');
}
if (!$count) throw new RuntimeException('Wybrana karta nie zawiera aktywnych i dostępnych pozycji.');
$body .= tm_word_para('Moja Polana Smaków • Kraków', 'Small', 'center');
$body .= '<w:sectPr><w:pgSz w:w="11906" w:h="16838"/><w:pgMar w:top="1080" w:right="1250" w:bottom="1080" w:left="1250" w:header="600" w:footer="600"/><w:cols w:space="720"/></w:sectPr>';
return ['missing'=>$missing,'count'=>$count,'xml'=>'<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body>'.$body.'</w:body></w:document>'];
}
function tm_word_zip(array $parts): string {
if (!class_exists('ZipArchive')) throw new RuntimeException('Na home.pl brakuje rozszerzenia PHP ZipArchive. Użyj istniejącego eksportu HTML → PDF lub aktywuj php-zip.');
$path = tempnam(sys_get_temp_dir(), 'polana_docx_');
if ($path === false) throw new RuntimeException('Nie można utworzyć pliku tymczasowego DOCX.');
$zip = new ZipArchive();
if ($zip->open($path, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
@unlink($path); throw new RuntimeException('Błąd tworzenia archiwum DOCX.');
}
$style = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">';
$defs = [
['Normal',20,'242424',false,100,0],
['Brand',33,'292d23',true,160,100],
['Subtitle',20,'6d715d',false,140,160],
['Category',26,'2a4228',true,360,180],
['Dish',21,'151515',true,25,25],
['Description',18,'666666',false,35,55],
['Price',21,'151515',true,25,20],
['Small',16,'6a6a6a',false,150,70],
['Warning',19,'b42318',true,210,110],
];
foreach ($defs as [$id,$size,$color,$bold,$before,$after]) {
$style .= '<w:style w:type="paragraph" w:styleId="'.$id.'"><w:name w:val="'.$id.'"/><w:pPr><w:spacing w:before="'.$before.'" w:after="'.$after.'"/></w:pPr><w:rPr><w:rFonts w:ascii="Calibri" w:hAnsi="Calibri"/><w:color w:val="'.$color.'"/><w:sz w:val="'.$size.'"/>'.($bold?'<w:b/>':'').'</w:rPr></w:style>';
}
$style .= '</w:styles>';
$types = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/><Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/></Types>';
$rels = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/></Relationships>';
$wordrels = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/></Relationships>';
try {
$zip->addFromString('[Content_Types].xml', $types);
$zip->addFromString('_rels/.rels', $rels);
$zip->addFromString('word/document.xml', $parts['xml']);
$zip->addFromString('word/styles.xml', $style);
$zip->addFromString('word/_rels/document.xml.rels', $wordrels);
} finally { $zip->close(); }
if (!is_file($path) || filesize($path) < 250) { @unlink($path); throw new RuntimeException('Niepełny dokument DOCX.'); }
return $path;
}