['message' => 'ZipArchive extension not available.']]); exit; } $docx = buildMinimalDocx($text); header('Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document'); header('Content-Disposition: attachment; filename="redacted.docx"'); header('Content-Length: ' . strlen($docx)); header('Cache-Control: no-store'); echo $docx; exit; function buildMinimalDocx(string $text): string { $tmp = tempnam(sys_get_temp_dir(), 'dbn_docx_'); @unlink($tmp); $tmp .= '.docx'; $zip = new ZipArchive(); $zip->open($tmp, ZipArchive::CREATE | ZipArchive::OVERWRITE); $zip->addFromString('[Content_Types].xml', contentTypesXml()); $zip->addFromString('_rels/.rels', relsXml()); $zip->addFromString('word/document.xml', documentXml($text)); $zip->addFromString('word/_rels/document.xml.rels', wordRelsXml()); $zip->addFromString('docProps/app.xml', appXml()); $zip->addFromString('docProps/core.xml', coreXml()); $zip->close(); $bytes = file_get_contents($tmp); @unlink($tmp); return $bytes; } function documentXml(string $text): string { $lines = explode("\n", str_replace("\r\n", "\n", str_replace("\r", "\n", $text))); $paras = []; foreach ($lines as $line) { $safe = htmlspecialchars($line, ENT_XML1 | ENT_COMPAT, 'UTF-8'); if ($safe === '') { $paras[] = ''; } else { $paras[] = '' . '' . $safe . ''; } } return '' . '' . '' . implode('', $paras) . '' . '' . ''; } function contentTypesXml(): string { return '' . '' . '' . '' . '' . '' . '' . ''; } function relsXml(): string { return '' . '' . '' . '' . '' . ''; } function wordRelsXml(): string { return '' . ''; } function appXml(): string { return '' . '' . 'DoBetterNorge Redact' . ''; } function coreXml(): string { $date = date('Y-m-d\TH:i:s\Z'); return '' . '' . 'DoBetterNorge' . '' . $date . '' . ''; }