| 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);
/** Editable Word export without php-zip, php-dom or temporary files. */
function polana_word_xml(string $s): string
{
$s = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $s) ?? $s;
return htmlspecialchars($s, ENT_QUOTES | ENT_XML1 | ENT_SUBSTITUTE, 'UTF-8');
}
function polana_word_run(string $text, bool $bold = false, int $fontSize = 22): string
{
if ($text === '') return '';
return '<w:r><w:rPr><w:rFonts w:ascii="Aptos" w:hAnsi="Aptos"/><w:sz w:val="'. $fontSize .'"/>'
. ($bold ? '<w:b/>' : '')
. '</w:rPr><w:t xml:space="preserve">' . polana_word_xml($text) . '</w:t></w:r>';
}
function polana_word_paragraph(string $text, string $kind = 'item', string $price = ''): string
{
if ($kind === 'category') {
return '<w:p><w:pPr><w:pStyle w:val="Heading2"/><w:jc w:val="center"/><w:spacing w:before="420" w:after="180"/><w:keepNext/></w:pPr>'
. polana_word_run($text, true, 30) . '</w:p>';
}
if ($kind === 'hint') {
return '<w:p><w:pPr><w:jc w:val="center"/><w:spacing w:after="130"/></w:pPr>'
. polana_word_run($text, false, 18) . '</w:p>';
}
$props = '<w:pPr><w:spacing w:after="135"/><w:tabs><w:tab w:val="right" w:pos="9030"/></w:tabs></w:pPr>';
if ($price !== '') {
return '<w:p>' . $props . polana_word_run($text) . '<w:r><w:tab/></w:r>'
. polana_word_run($price, true, 20) . '</w:p>';
}
return '<w:p>' . $props . polana_word_run($text) . '</w:p>';
}
function polana_word_image(string $relationship, int $id, int $wEmu, int $hEmu): string
{
return '<w:p><w:pPr><w:jc w:val="center"/><w:spacing w:before="160" w:after="160"/></w:pPr><w:r><w:drawing>'
. '<wp:inline distT="0" distB="0" distL="0" distR="0"><wp:extent cx="'.$wEmu.'" cy="'.$hEmu.'"/>'
. '<wp:docPr id="'.$id.'" name="Polana image '.$id.'"/><wp:cNvGraphicFramePr/>'
. '<a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">'
. '<pic:pic><pic:nvPicPr><pic:cNvPr id="'.$id.'" name="polana.png"/><pic:cNvPicPr/></pic:nvPicPr>'
. '<pic:blipFill><a:blip r:embed="'.$relationship.'"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill>'
. '<pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="'.$wEmu.'" cy="'.$hEmu.'"/></a:xfrm>'
. '<a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic>'
. '</a:graphicData></a:graphic></wp:inline></w:drawing></w:r></w:p>';
}
function polana_word_zip(array $files): string
{
$local = '';
$central = '';
$offset = 0;
foreach ($files as $name => $data) {
$name = (string)$name;
$data = (string)$data;
$size = strlen($data);
$crc = crc32($data);
$entry = pack('VvvvvvVVVvv', 0x04034b50, 20, 0, 0, 0, 0, $crc, $size, $size, strlen($name), 0)
. $name . $data;
$local .= $entry;
$central .= pack('VvvvvvvVVVvvvvvVV', 0x02014b50, 20, 20, 0, 0, 0, 0,
$crc, $size, $size, strlen($name), 0, 0, 0, 0, 0, $offset)
. $name;
$offset += strlen($entry);
}
$count = count($files);
return $local . $central . pack('VvvvvVVv', 0x06054b50, 0, 0, $count, $count, strlen($central), strlen($local), 0);
}
/**
* @param array $categories Menu categories from menu_categories_with_items(..., true)
* @param string $lang pl or en; English falls back to PL for absent translations,
* with a visible warning in exported document.
*/
function polana_build_menu_docx(array $categories, string $lang, string $type, string $rootDir): string
{
$lang = $lang === 'en' ? 'en' : 'pl';
$type = in_array($type, ['food','soft','wine'], true) ? $type : 'food';
$labels = [
'food' => ['pl'=>'Dania', 'en'=>'Food'],
'soft' => ['pl'=>'Napoje bezalkoholowe', 'en'=>'Soft drinks'],
'wine' => ['pl'=>'Wina', 'en'=>'Wines'],
];
$assets = [];
$rels = [];
$body = '';
foreach (['logo.png' => 'logo', 'podpis-andrzej-polan.png' => 'signature'] as $file => $key) {
$path = rtrim($rootDir, '/') . '/assets/img/' . $file;
if (is_file($path) && is_readable($path) && filesize($path) < 3 * 1024 * 1024) {
$bytes = file_get_contents($path);
$dims = @getimagesize($path);
if (is_string($bytes) && $dims !== false && ($dims['mime'] ?? '') === 'image/png') {
$assets[$key] = ['bytes' => $bytes, 'width' => max(1, (int)$dims[0]), 'height' => max(1, (int)$dims[1])];
}
}
}
if (isset($assets['logo'])) {
$width = 1950000;
$height = (int)min(1050000, $width * $assets['logo']['height'] / $assets['logo']['width']);
$body .= polana_word_image('rId2', 1, $width, $height);
$rels[] = '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/logo.png"/>';
}
// Only the public-facing label appears; do not print internal name like "Menu 10/2026".
$body .= '<w:p><w:pPr><w:jc w:val="center"/><w:spacing w:after="160"/></w:pPr>'
. polana_word_run($labels[$type][$lang], true, 34) . '</w:p>';
$missing = false;
foreach ($categories as $cat) {
if (isset($cat['active']) && !(int)$cat['active']) continue;
$rows = [];
foreach (($cat['items'] ?? []) as $item) {
if ((isset($item['active']) && !(int)$item['active']) || (isset($item['available']) && !(int)$item['available'])) continue;
$rows[] = $item;
}
if (!$rows) continue;
$cn = trim((string)($cat['name'] ?? ''));
if ($lang === 'en') {
$translated = trim((string)($cat['name_en'] ?? ''));
if ($translated !== '') $cn = $translated;
else $missing = true;
}
$body .= polana_word_paragraph($cn, 'category');
foreach ($rows as $row) {
$name = trim((string)($row['name'] ?? ''));
if ($lang === 'en') {
$translated = trim((string)($row['name_en'] ?? ''));
if ($translated !== '') $name = $translated;
else $missing = true;
}
$price = trim((string)($row['price_text'] ?? ''));
if ($price === '' && isset($row['price']) && $row['price'] !== '' && $row['price'] !== null) {
$price = number_format((float)$row['price'], 0, ',', ' ') . ' zł';
}
if ($lang === 'en') {
$price = str_replace(['kieliszek', 'butelka', 'zł'], ['glass', 'bottle', 'PLN'], $price);
}
$body .= polana_word_paragraph($name, 'item', $price);
}
}
if (isset($assets['signature'])) {
$width = 2670000;
$height = (int)min(1500000, $width * $assets['signature']['height'] / $assets['signature']['width']);
$body .= polana_word_image('rId3', 2, $width, $height);
$rels[] = '<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/signature.png"/>';
} else {
$body .= '<w:p><w:pPr><w:jc w:val="center"/></w:pPr>' . polana_word_run('Andrzej Polan', false, 26) . '</w:p>';
}
if ($lang === 'en' && $missing) {
// Warning at the end rather than auto-generating potentially misleading translations.
$body .= polana_word_paragraph('MACHINE TRANSLATION / MISSING ENTRIES — REVIEW BEFORE PUBLICATION', 'hint');
}
$document = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
. '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"'
. ' xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"'
. ' xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"'
. ' xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"'
. ' xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">'
. '<w:body>' . $body
. '<w:sectPr><w:pgSz w:w="11906" w:h="16838"/><w:pgMar w:top="1100" w:right="1300" w:bottom="1100" w:left="1300" w:header="600" w:footer="600" w:gutter="0"/></w:sectPr></w:body></w:document>';
$styles = '<?xml version="1.0" encoding="UTF-8"?>'
. '<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">'
. '<w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/></w:style>'
. '<w:style w:type="paragraph" w:styleId="Heading2"><w:name w:val="heading 2"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/></w:style></w:styles>';
$files = [
'[Content_Types].xml' => '<?xml version="1.0" encoding="UTF-8"?><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"/>'
. '<Default Extension="png" ContentType="image/png"/>'
. '<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/.rels' => '<?xml version="1.0" encoding="UTF-8"?>'
. '<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>',
'word/document.xml' => $document,
'word/styles.xml' => $styles,
'word/_rels/document.xml.rels' => '<?xml version="1.0" encoding="UTF-8"?>'
. '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
. '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>'
. implode('', $rels) . '</Relationships>',
];
foreach ($assets as $key => $asset) {
$files['word/media/' . ($key === 'logo' ? 'logo.png' : 'signature.png')] = $asset['bytes'];
}
return polana_word_zip($files);
}