| 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 2026 — Odczyt wersji językowych i eksport Word PL/EN/DE/ES.
* Brak zmian w bazie, brak sieci, brak kluczy API, brak ZipArchive/tempnam.
*/
require_once __DIR__ . '/functions.php';
function tklm_v5_rows(int $menuId): array {
if ($menuId < 1) throw new RuntimeException('Nieprawidłowe ID menu.');
$q=db()->prepare('SELECT * FROM menus WHERE id=? LIMIT 1'); $q->execute([$menuId]);
$menu=$q->fetch(PDO::FETCH_ASSOC);
if (!$menu) throw new RuntimeException('Karta menu nie istnieje.');
$q=db()->prepare('SELECT * FROM menu_categories WHERE menu_id=? ORDER BY sort_order,id');
$q->execute([$menuId]); $cats=$q->fetchAll(PDO::FETCH_ASSOC);
$q=db()->prepare('SELECT i.* FROM menu_items i INNER JOIN menu_categories c ON c.id=i.category_id WHERE c.menu_id=? ORDER BY c.sort_order,c.id,i.sort_order,i.id');
$q->execute([$menuId]); $by=[];
foreach ($q->fetchAll(PDO::FETCH_ASSOC) as $it) $by[(int)$it['category_id']][]=$it;
foreach ($cats as &$cat) $cat['items']=$by[(int)$cat['id']]??[];
unset($cat);
return [$menu,$cats];
}
function tklm_v7_type_rank(string $type): int {
return match($type) {
'food' => 1,
'soft' => 2,
'wine' => 3,
default => 99,
};
}
function tklm_v7_type_label(string $type, string $lang): string {
$map=[
'pl'=>['food'=>'Dania','soft'=>'Napoje bezalkoholowe','wine'=>'Wina'],
'en'=>['food'=>'Food','soft'=>'Soft drinks','wine'=>'Wines'],
'de'=>['food'=>'Speisen','soft'=>'Alkoholfreie Getränke','wine'=>'Weine'],
'es'=>['food'=>'Platos','soft'=>'Bebidas sin alcohol','wine'=>'Vinos'],
];
return $map[$lang][$type] ?? ($map['pl'][$type] ?? ucfirst($type));
}
/**
* Pobiera komplet powiazanych kart (np. Dania + Napoje bezalkoholowe + Wina)
* o tej samej nazwie i tym samym okresie obowiazywania. Kazda karta stanie sie
* osobna strona w Word/PDF.
* @return array<int,array{menu:array,categories:array}>
*/
function tklm_v7_bundle_rows(int $menuId): array {
if ($menuId < 1) throw new RuntimeException('Nieprawidłowe ID menu.');
[$sourceMenu,] = tklm_v5_rows($menuId);
$q = db()->prepare(
'SELECT * FROM menus
WHERE name = ?
AND ((valid_from IS NULL AND ? IS NULL) OR valid_from = ?)
AND ((valid_to IS NULL AND ? IS NULL) OR valid_to = ?)
ORDER BY id'
);
$q->execute([
(string)$sourceMenu['name'],
$sourceMenu['valid_from'] ?? null, $sourceMenu['valid_from'] ?? null,
$sourceMenu['valid_to'] ?? null, $sourceMenu['valid_to'] ?? null,
]);
$menus = $q->fetchAll(PDO::FETCH_ASSOC);
if (!$menus) $menus = [$sourceMenu];
usort($menus, static function(array $a, array $b): int {
$ra = tklm_v7_type_rank((string)($a['menu_type'] ?? ''));
$rb = tklm_v7_type_rank((string)($b['menu_type'] ?? ''));
return $ra <=> $rb ?: ((int)$a['id'] <=> (int)$b['id']);
});
// Jedna aktualna karta na rodzaj: bez powielania poprzednich wersji.
// Wybieramy jawnie wskazana karte dla jej typu, a najnowsza opublikowana
// karte dla pozostalych rodzajow w tym samym zestawie nazwa/data.
$selected=[];
foreach ($menus as $candidate) {
$type=(string)($candidate['menu_type']??'food');
if (!in_array($type,['food','soft','wine'],true)) continue;
if ((string)($candidate['status']??'') !== 'published' && (int)$candidate['id'] !== $menuId) continue;
if (!isset($selected[$type]) || (int)$candidate['id'] > (int)$selected[$type]['id']) $selected[$type]=$candidate;
}
$sourceType=(string)($sourceMenu['menu_type']??'food');
$selected[$sourceType]=$sourceMenu;
uksort($selected,static fn(string $a,string $b):int=>tklm_v7_type_rank($a)<=>tklm_v7_type_rank($b));
$out=[];
foreach ($selected as $menu) {
[$fullMenu,$cats]=tklm_v5_rows((int)$menu['id']);
$visible=false;
foreach ($cats as $cat) {
if (empty($cat['active'])) continue;
foreach ($cat['items'] ?? [] as $it) {
if (!empty($it['active']) && !empty($it['available'])) { $visible=true; break 2; }
}
}
if (!$visible) continue;
$out[]=['menu'=>$fullMenu,'categories'=>$cats];
}
if (!$out) throw new RuntimeException('Brak aktywnych pozycji w powiązanych kartach.');
return $out;
}
function tklm_v5_status(int $menuId, string $lang): array {
if (!in_array($lang,['pl','en','de','es'],true)) throw new RuntimeException('Nieznany język.');
$bundle=tklm_v7_bundle_rows($menuId);
$present=0; $required=0; $visible=0; $missing=[];
foreach($bundle as $pack) {
$menu=$pack['menu'];
$cats=$pack['categories'];
foreach($cats as $cat) {
if (!(int)$cat['active']) continue;
$items=array_values(array_filter($cat['items'],static fn(array $it):bool=>(int)$it['active']===1 && (int)$it['available']===1));
if (!$items) continue;
if ($lang !== 'pl' && trim((string)$cat['name'])!=='') {
$required++;
if (trim((string)($cat['name_'.$lang]??''))!=='') $present++;
else $missing[]='Karta #'.(int)$menu['id'].' / kategoria #'.(int)$cat['id'];
}
foreach($items as $it) {
$visible++;
if ($lang==='pl') continue;
foreach(['name','description','allergens'] as $field) {
if (trim((string)($it[$field]??''))==='') continue;
$required++;
if (trim((string)($it[$field.'_'.$lang]??''))!=='') $present++;
else $missing[]='Karta #'.(int)$menu['id'].' / pozycja #'.(int)$it['id'].': '.$field;
}
}
}
}
return ['visible'=>$visible,'required'=>$required,'present'=>$present,'missing'=>$missing,
'available'=>$visible>0 && ($lang==='pl'||$present>0),
'ready'=>$visible>0 && ($lang==='pl'||($required>0&&$present===$required))];
}
function tklm_v5_word_buttons(int $menuId): string {
if ($menuId < 1) return '';
static $cache=[];
if(isset($cache[$menuId])) return $cache[$menuId];
$buttons='';
try {
foreach(['en','de','es'] as $lang) {
$s=tklm_v5_status($menuId,$lang);
if (!$s['available']) continue;
$draft=!$s['ready'];
$href='/admin/tklm-word-export-v5.php?menu_id='.$menuId.'&lang='.$lang.($draft?'&draft=1':'');
$label='Word '.strtoupper($lang).($draft?' (roboczy)':'').' ↓';
$title=$draft ? 'Brakujących pól: '.count($s['missing']).'. Sprawdź przed drukiem.' : 'Gotowa karta '.strtoupper($lang).' do druku';
$buttons.=' <a class="btn btn-small btn-light" style="display:inline-block;white-space:nowrap;margin:2px" href="'.$href.'" title="'.htmlspecialchars($title,ENT_QUOTES,'UTF-8').'">'.htmlspecialchars($label,ENT_QUOTES,'UTF-8').'</a>';
$pdf='/admin/tklm-pdf-export-v6.php?menu_id='.$menuId.'&lang='.$lang.($draft?'&draft=1':'').'&download=1';
$pdfLabel='PDF '.strtoupper($lang).($draft?' (roboczy)':'').' ↓';
$buttons.=' <a class="btn btn-small btn-light" style="display:inline-block;white-space:nowrap;margin:2px" target="_blank" rel="noopener" href="'.$pdf.'" title="Zapisz PDF A4 dla '.strtoupper($lang).'">'.htmlspecialchars($pdfLabel,ENT_QUOTES,'UTF-8').'</a>';
}
} catch(Throwable $e) { error_log('[TKLM Word V5] '.$e->getMessage()); }
return $cache[$menuId]=$buttons;
}
function tklm_v5_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 tklm_v5_word_para(string $text, string $style = 'Normal', string $align = ''): string {
$p = '<w:pPr><w:pStyle w:val="' . tklm_v5_word_xml($style) . '"/>';
if ($align !== '') $p .= '<w:jc w:val="' . tklm_v5_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">' . tklm_v5_word_xml($line) . '</w:t>';
}
return '<w:p>' . $p . '<w:r>' . $t . '</w:r></w:p>';
}
function tklm_v5_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 tklm_v5_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 '';
}
/**
* Jedno logo dla PL/EN/DE/ES. Ten sam plik jest wyswietlany w publicznym menu.
* Brak zasobu to blad eksportu - nie wysylamy Worda bez logo.
*/
function tklm_v6_word_logo_bytes(): string {
$path = dirname(__DIR__).'/assets/img/logo.png';
if (!is_file($path) || !is_readable($path)) throw new RuntimeException('Brak logo: /assets/img/logo.png.');
$bytes = file_get_contents($path);
if ($bytes === false || strlen($bytes) < 100 || substr($bytes,0,8) !== "\x89PNG\r\n\x1a\n") {
throw new RuntimeException('Logo PNG jest uszkodzone lub niedostepne.');
}
return $bytes;
}
function tklm_v6_word_logo_paragraph(): string {
// Wymiary grafiki zachowuja proporcje oryginalnego PNG (1200x1105).
$meta=getimagesizefromstring(tklm_v6_word_logo_bytes());
if (!is_array($meta) || (int)$meta[0]<1 || (int)$meta[1]<1) throw new RuntimeException('Nie mozna odczytac wymiarow logo.');
$cx=1480000;
$cy=(int)round($cx * (int)$meta[1]/(int)$meta[0]);
return '<w:p><w:pPr><w:jc w:val="center"/><w:spacing w:after="100"/></w:pPr><w:r><w:drawing>'
.'<wp:inline distT="0" distB="0" distL="0" distR="0"><wp:extent cx="'.$cx.'" cy="'.$cy.'"/>'
.'<wp:docPr id="1" name="Logo Polana" descr="Logo Moja Polana Smakow"/>'
.'<a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">'
.'<pic:pic><pic:nvPicPr><pic:cNvPr id="0" name="logo.png"/><pic:cNvPicPr/></pic:nvPicPr>'
.'<pic:blipFill><a:blip r:embed="rIdLogo"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill>'
.'<pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="'.$cx.'" cy="'.$cy.'"/></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>';
}
/** @return array{missing:array<string>, xml:string, count:int} */
function tklm_v5_word_document_parts(array $menu, array $cats, string $lang, bool $draft, ?array $bundleOverride=null): array {
if (!in_array($lang,['pl','en','de','es'],true)) throw new RuntimeException('Język musi być PL, EN, DE albo ES.');
$allerg=['pl'=>'Alergeny: ','en'=>'Allergens: ','de'=>'Allergene: ','es'=>'Alérgenos: '];
$missing=[]; $count=0;
$bundle=$bundleOverride ?? tklm_v7_bundle_rows((int)$menu['id']);
$body='';
$pageNo=0;
foreach ($bundle as $pack) {
$pageNo++;
$curMenu=$pack['menu'];
$curCats=$pack['categories'];
if ($pageNo > 1) {
$body .= '<w:p><w:r><w:br w:type="page"/></w:r></w:p>';
}
$body.=tklm_v6_word_logo_paragraph();
// Wersja 12.2: po logo od razu wlasciwe kategorie i pozycje menu.
// Nie drukujemy ozdobnych naglowkow KARTA RESTAURACJI / RESTAURANT MENU,
// rodzaju karty (Food / Soft drinks / Wines) ani miasta i daty.
// Podzial na osobne strony i graficzny podpis w stopce pozostaja bez zmian.
foreach($curCats 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;
$sourceCat=trim((string)($cat['name']??''));
$catName=trim((string)($cat[$lang==='pl'?'name':'name_'.$lang]??''));
if ($lang!=='pl' && $sourceCat!=='' && $catName==='') {
$missing[]='Karta #'.(int)$curMenu['id'].' / kategoria #'.(int)$cat['id'];
$catName='['.strtoupper($lang).' MISSING] '.$sourceCat;
}
$body.=tklm_v5_word_para($catName,'Category','center');
foreach($items as $it) {
$count++;
$content=[];
foreach(['name','description','allergens'] as $field) {
$source=trim((string)($it[$field]??''));
$key=$lang==='pl'?$field:$field.'_'.$lang;
$out=trim((string)($it[$key]??''));
if ($lang!=='pl' && $source!=='' && $out==='') {
$missing[]='Karta #'.(int)$curMenu['id'].' / pozycja #'.(int)$it['id'].': '.$field;
$out='['.strtoupper($lang).' MISSING] '.$source;
}
$content[$field]=$out;
}
$left=tklm_v5_word_para($content['name'],'Dish');
if ($content['description']!=='') $left.=tklm_v5_word_para($content['description'],'Description');
if ($content['allergens']!=='') $left.=tklm_v5_word_para($allerg[$lang].$content['allergens'],'Small');
$right=tklm_v5_word_para(tklm_v5_word_price($it),'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>'.tklm_v5_word_cell($left,7700).tklm_v5_word_cell($right,1600).'</w:tr></w:tbl>';
}
}
// Oryginalny podpis graficzny jest w stopce dokumentu, na kazdej stronie.
}
if (!$count) throw new RuntimeException('Karta nie zawiera aktywnych i dostępnych pozycji.');
if ($lang!=='pl' && $missing && !$draft) throw new RuntimeException('Brak '.count($missing).' pól '.strtoupper($lang).'. Uzupełnij tłumaczenia lub wybierz wersję roboczą.');
$body.='<w:sectPr><w:footerReference w:type="default" r:id="rIdFooter"/><w:pgSz w:w="11906" w:h="16838"/><w:pgMar w:top="1080" w:right="1250" w:bottom="1320" w:left="1250" w:header="600" w:footer="480"/><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" 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:body></w:document>'];
}
function tklm_v8_signature_bytes(): string {
$p=dirname(__DIR__).'/assets/img/tklm_andrzej_polan_signature.png';
if(!is_file($p) || !is_readable($p)) throw new RuntimeException('Brak podpisu w assets/img/tklm_andrzej_polan_signature.png.');
$raw=file_get_contents($p);
if($raw===false || substr($raw,0,8)!=="\x89PNG\r\n\x1a\n") throw new RuntimeException('Nieprawidłowa grafika podpisu.');
return $raw;
}
function tklm_v8_footer_xml(): string {
// Oryginalny podpis graficzny z polskiej karty, proporcje ok. 2.3:1.
$cx=1750000; $cy=1002000;
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
.'<w:ftr 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:p><w:pPr><w:jc w:val="center"/></w:pPr><w:r><w:drawing>'
.'<wp:inline distT="0" distB="0" distL="0" distR="0"><wp:extent cx="'.$cx.'" cy="'.$cy.'"/>'
.'<wp:docPr id="7" name="Podpis Andrzej Polan"/>'
.'<a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">'
.'<pic:pic><pic:nvPicPr><pic:cNvPr id="8" name="podpis.png"/><pic:cNvPicPr/></pic:nvPicPr>'
.'<pic:blipFill><a:blip r:embed="rIdSignature"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill>'
.'<pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="'.$cx.'" cy="'.$cy.'"/></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></w:ftr>';
}
function tklm_v5_word_zip(array $parts): string {
$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"/><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"/><Override PartName="/word/footer1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+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"/><Relationship Id="rIdLogo" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/logo.png"/><Relationship Id="rIdFooter" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" Target="footer1.xml"/></Relationships>';
$files = [
'[Content_Types].xml' => $types,
'_rels/.rels' => $rels,
'word/document.xml' => $parts['xml'],
'word/styles.xml' => $style,
'word/_rels/document.xml.rels' => $wordrels,
'word/media/logo.png' => tklm_v6_word_logo_bytes(),
'word/footer1.xml' => tklm_v8_footer_xml(),
'word/_rels/footer1.xml.rels' => '<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rIdSignature" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/signature.png"/></Relationships>',
'word/media/signature.png' => tklm_v8_signature_bytes(),
];
$zip=''; $directory=''; $entries=0;
$t=getdate();
$dosDate=((max(1980,(int)$t['year'])-1980)<<9)|((int)$t['mon']<<5)|(int)$t['mday'];
$dosTime=((int)$t['hours']<<11)|((int)$t['minutes']<<5)|((int)$t['seconds']>>1);
foreach ($files as $name => $content) {
$name=(string)$name;
$content=(string)$content;
$size=strlen($content); $nlen=strlen($name);
$crc=crc32($content);
$offset=strlen($zip);
// Lokalny naglowek ZIP (PK 03 04): store, bez descriptorow.
$zip .= pack('VvvvvvVVVvv',0x04034b50,20,0,0,$dosTime,$dosDate,$crc,$size,$size,$nlen,0).$name.$content;
// Central directory (PK 01 02).
$directory .= pack('VvvvvvvVVVvvvvvVV',0x02014b50,20,20,0,0,$dosTime,$dosDate,$crc,$size,$size,$nlen,0,0,0,0,0,$offset).$name;
$entries++;
}
$dirOffset=strlen($zip);
$zip.=$directory;
$zip.=pack('VvvvvVVv',0x06054b50,0,0,$entries,$entries,strlen($directory),$dirOffset,0);
if(strlen($zip)<250) throw new RuntimeException('Blad generowania DOCX.');
return $zip;
}