[ 'dataFile' => 'word/document.xml', 'startTag' => '', 'pageBreak' => '', 'endTag' => '', ], 'odt' => [ 'dataFile' => 'content.xml', 'startTag' => '', 'pageBreak' => '', 'endTag' => '', ], ]; /** * Convert html to a Doc file. * * @param array $pages * List of HTML snippets. * @param string $fileName * The logical filename to return to client. * Ex: "HelloWorld.odt". * @param array|int $format */ public static function html2doc($pages, $fileName, $format = []) { if (is_array($format)) { // PDF Page Format parameters passed in - merge with defaults $format += CRM_Core_BAO_PdfFormat::getDefaultValues(); } else { // PDF Page Format ID passed in $format = CRM_Core_BAO_PdfFormat::getById($format); } $paperSize = CRM_Core_BAO_PaperSize::getByName($format['paper_size']); $metric = CRM_Core_BAO_PdfFormat::getValue('metric', $format); $pageStyle = [ 'orientation' => CRM_Core_BAO_PdfFormat::getValue('orientation', $format), 'pageSizeW' => self::toTwip($paperSize['width'], $paperSize['metric']), 'pageSizeH' => self::toTwip($paperSize['height'], $paperSize['metric']), 'marginTop' => self::toTwip(CRM_Core_BAO_PdfFormat::getValue('margin_top', $format), $metric), 'marginRight' => self::toTwip(CRM_Core_BAO_PdfFormat::getValue('margin_right', $format), $metric), 'marginBottom' => self::toTwip(CRM_Core_BAO_PdfFormat::getValue('margin_bottom', $format), $metric), 'marginLeft' => self::toTwip(CRM_Core_BAO_PdfFormat::getValue('margin_left', $format), $metric), ]; if (CIVICRM_UF === 'UnitTests' && headers_sent()) { // Streaming content will 'die' in unit tests unless ob_start() // has been called. throw new CRM_Core_Exception_PrematureExitException('_html2doc called', [ 'html' => $pages, 'fileName' => $fileName, 'pageStyle' => $pageStyle, ]); } $ext = pathinfo($fileName, PATHINFO_EXTENSION); $phpWord = new \PhpOffice\PhpWord\PhpWord(); $phpWord->getDocInfo() ->setCreator(CRM_Core_DAO::getFieldValue('CRM_Contact_BAO_Contact', CRM_Core_Session::getLoggedInContactID(), 'display_name')); foreach ((array) $pages as $page => $html) { $section = $phpWord->addSection($pageStyle + ['breakType' => 'nextPage']); \PhpOffice\PhpWord\Shared\Html::addHtml($section, $html); } self::printDoc($phpWord, $ext, $fileName); } /** * @param object|string $phpWord * @param string $ext * File extension/type. * Ex: docx, odt, html. * @param string $fileName * The logical filename to return to client. * Ex: "HelloWorld.odt". * Alternatively, a full path of a file to display. This seems sketchy. * Ex: "/var/lib/data/HelloWorld.odt". */ public static function printDoc($phpWord, $ext, $fileName) { $formats = [ 'docx' => 'Word2007', 'odt' => 'ODText', 'html' => 'HTML', // todo 'pdf' => 'PDF', ]; if (realpath($fileName)) { $phpWord = \PhpOffice\PhpWord\IOFactory::load($fileName, $formats[$ext]); } //CRM-20015 \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(TRUE); $objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, $formats[$ext]); CRM_Utils_System::setHttpHeader('Content-Type', "application/$ext"); CRM_Utils_System::setHttpHeader('Content-Disposition', 'attachment; filename="' . $fileName . '"'); $objWriter->save("php://output"); } /** * @param int $value * @param string $metric * @return float */ public static function toTwip($value, $metric) { $point = CRM_Utils_PDF_Utils::convertMetric($value, $metric, 'pt'); return \PhpOffice\PhpWord\Shared\Converter::pointToTwip($point); } /** * @param array $path docx/odt file path * @param string $type File type * * @return array * Return extracted content of document in HTML and document type */ public static function docReader($path, $type) { $type = array_search($type, CRM_Core_SelectValues::documentApplicationType()); $fileType = ($type == 'docx') ? 'Word2007' : 'ODText'; $phpWord = \PhpOffice\PhpWord\IOFactory::load($path, $fileType); $phpWordHTML = new \PhpOffice\PhpWord\Writer\HTML($phpWord); // return the html content for tokenreplacment and eventually used for document download return [$phpWordHTML->getWriterPart('Body')->write(), $type]; } /** * Extract content of docx/odt file * * @param string $filePath Document file path * @param string $docType File type of document * * @return array * [string, clsTbsZip] */ public static function unzipDoc($filePath, $docType) { $dataFile = self::$ooxmlMap[$docType]['dataFile']; $zip = new clsTbsZip(); $zip->Open($filePath); $content = $zip->FileRead($dataFile); return [$content, $zip]; } /** * Modify contents of docx/odt file(s) and later merged into one final document * * @param array $contents * Content of formatted/token-replaced document. * List of HTML snippets. * @param string $fileName * The logical filename to return to client. * Ex: "HelloWorld.odt". * @param string $docType * Document type e.g. odt/docx * @param clsTbsZip $zip * Zip archive * @param bool $returnFinalContent * Return the content of file document as a string used in unit test * * @return string */ public static function printDocuments($contents, $fileName, $docType, $zip, $returnFinalContent = FALSE) { $dataMap = self::$ooxmlMap[$docType]; $finalContent = $zip->FileRead($dataMap['dataFile']); // token-replaced document contents of each contact will be merged into final document foreach ($contents as $key => $content) { if ($key == 0) { $finalContent = $content; continue; } // 1. fetch the start position of document body // 2. later fetch only the body part starting from position $start // 3. replace closing body tag with pageBreak // 4. append the $content to the finalContent $start = strpos($content, $dataMap['startTag']); $content = substr($content, $start); $content = str_replace($dataMap['startTag'], $dataMap['pageBreak'], $content); $finalContent = str_replace($dataMap['endTag'], $content, $finalContent); } if ($returnFinalContent) { return $finalContent; } // Replace the loaded document file content located at $filePath with $finaContent $zip->FileReplace($dataMap['dataFile'], $finalContent, TBSZIP_STRING); $zip->Flush(TBSZIP_DOWNLOAD, $fileName); } }