Merge pull request #10171 from seamuslee001/CRM-20427
[civicrm-core.git] / CRM / Utils / PDF / Document.php
CommitLineData
0aeb5a1e
CW
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
0f03f337 6 | Copyright CiviCRM LLC (c) 2004-2017 |
0aeb5a1e
CW
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28/**
29 *
30 * @package CRM
0f03f337 31 * @copyright CiviCRM LLC (c) 2004-2017
0aeb5a1e 32 */
ad7fdc34 33
34require_once 'TbsZip/tbszip.php';
35
8246bca4 36/**
37 * Class CRM_Utils_PDF_Document.
38 */
0aeb5a1e
CW
39class CRM_Utils_PDF_Document {
40
9eae09f1
CW
41 public static $ooxmlMap = array(
42 'docx' => array(
43 'dataFile' => 'word/document.xml',
44 'startTag' => '<w:body>',
2bd6b6cf 45 'pageBreak' => '<w:p><w:pPr><w:pStyle w:val="Normal"/><w:rPr></w:rPr></w:pPr><w:r><w:rPr></w:rPr></w:r><w:r><w:br w:type="page"/></w:r></w:p>',
9eae09f1
CW
46 'endTag' => '</w:body></w:document>',
47 ),
48 'odt' => array(
49 'dataFile' => 'content.xml',
50 'startTag' => '<office:body>',
51 'pageBreak' => '<text:p text:style-name="Standard"></text:p>',
52 'endTag' => '</office:body></office:document-content>',
53 ),
54 );
55
0aeb5a1e 56 /**
8246bca4 57 * Convert html to a Doc file.
58 *
0aeb5a1e 59 * @param array $pages
4f308883 60 * List of HTML snippets.
0aeb5a1e 61 * @param string $fileName
3ed92c14
TO
62 * The logical filename to return to client.
63 * Ex: "HelloWorld.odt".
0aeb5a1e
CW
64 * @param array|int $format
65 */
66 public static function html2doc($pages, $fileName, $format = array()) {
67 if (is_array($format)) {
68 // PDF Page Format parameters passed in - merge with defaults
69 $format += CRM_Core_BAO_PdfFormat::getDefaultValues();
70 }
71 else {
72 // PDF Page Format ID passed in
73 $format = CRM_Core_BAO_PdfFormat::getById($format);
74 }
75 $paperSize = CRM_Core_BAO_PaperSize::getByName($format['paper_size']);
76
77 $metric = CRM_Core_BAO_PdfFormat::getValue('metric', $format);
78 $pageStyle = array(
79 'orientation' => CRM_Core_BAO_PdfFormat::getValue('orientation', $format),
80 'pageSizeW' => self::toTwip($paperSize['width'], $paperSize['metric']),
81 'pageSizeH' => self::toTwip($paperSize['height'], $paperSize['metric']),
82 'marginTop' => self::toTwip(CRM_Core_BAO_PdfFormat::getValue('margin_top', $format), $metric),
83 'marginRight' => self::toTwip(CRM_Core_BAO_PdfFormat::getValue('margin_right', $format), $metric),
84 'marginBottom' => self::toTwip(CRM_Core_BAO_PdfFormat::getValue('margin_bottom', $format), $metric),
85 'marginLeft' => self::toTwip(CRM_Core_BAO_PdfFormat::getValue('margin_left', $format), $metric),
86 );
87
88 $ext = pathinfo($fileName, PATHINFO_EXTENSION);
89
90 $phpWord = new \PhpOffice\PhpWord\PhpWord();
91
92 $phpWord->getDocInfo()
93 ->setCreator(CRM_Core_DAO::getFieldValue('CRM_Contact_BAO_Contact', CRM_Core_Session::getLoggedInContactID(), 'display_name'));
94
95 foreach ((array) $pages as $page => $html) {
96 $section = $phpWord->addSection($pageStyle + array('breakType' => 'nextPage'));
97 \PhpOffice\PhpWord\Shared\Html::addHtml($section, $html);
98 }
ad7fdc34 99
100 self::printDoc($phpWord, $ext, $fileName);
101 }
102
103 /**
104 * @param object|string $phpWord
105 * @param string $ext
3ed92c14
TO
106 * File extension/type.
107 * Ex: docx, odt, html.
ad7fdc34 108 * @param string $fileName
3ed92c14
TO
109 * The logical filename to return to client.
110 * Ex: "HelloWorld.odt".
111 * Alternatively, a full path of a file to display. This seems sketchy.
112 * Ex: "/var/lib/data/HelloWorld.odt".
ad7fdc34 113 */
114 public static function printDoc($phpWord, $ext, $fileName) {
0aeb5a1e
CW
115 $formats = array(
116 'docx' => 'Word2007',
117 'odt' => 'ODText',
118 'html' => 'HTML',
119 // todo
120 'pdf' => 'PDF',
121 );
2c8a6e63 122
59d861cb 123 if (realpath($fileName)) {
124 $phpWord = \PhpOffice\PhpWord\IOFactory::load($fileName, $formats[$ext]);
ad7fdc34 125 }
126
f0ba77f4 127 \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(TRUE); //CRM-20015
0aeb5a1e
CW
128 $objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, $formats[$ext]);
129
0aeb5a1e
CW
130 CRM_Utils_System::setHttpHeader('Content-Type', "application/$ext");
131 CRM_Utils_System::setHttpHeader('Content-Disposition', 'attachment; filename="' . $fileName . '"');
132 $objWriter->save("php://output");
133 }
134
135 /**
136 * @param $value
137 * @param $metric
138 * @return int
139 */
140 public static function toTwip($value, $metric) {
141 $point = CRM_Utils_PDF_Utils::convertMetric($value, $metric, 'pt');
142 return \PhpOffice\PhpWord\Shared\Converter::pointToTwip($point);
143 }
144
04a76231 145 /**
146 * @param array $path docx/odt file path
147 * @param string $type File type
04a76231 148 *
00eb08cf 149 * @return array
150 * Return extracted content of document in HTML and document type
04a76231 151 */
00eb08cf 152 public static function docReader($path, $type) {
153 $type = array_search($type, CRM_Core_SelectValues::documentApplicationType());
04a76231 154 $fileType = ($type == 'docx') ? 'Word2007' : 'ODText';
00eb08cf 155
77a80c4e 156 $phpWord = \PhpOffice\PhpWord\IOFactory::load($path, $fileType);
9cbcd63f 157 $phpWordHTML = new \PhpOffice\PhpWord\Writer\HTML($phpWord);
04a76231 158
9cbcd63f 159 // return the html content for tokenreplacment and eventually used for document download
00eb08cf 160 return array($phpWordHTML->getWriterPart('Body')->write(), $type);
04a76231 161 }
162
163 /**
9eae09f1
CW
164 * Extract content of docx/odt file
165 *
ad7fdc34 166 * @param string $filePath Document file path
167 * @param string $docType File type of document
04a76231 168 *
9eae09f1
CW
169 * @return array
170 * [string, clsTbsZip]
04a76231 171 */
9eae09f1 172 public static function unzipDoc($filePath, $docType) {
1410eae7 173 $dataFile = self::$ooxmlMap[$docType]['dataFile'];
2c8a6e63 174
ad7fdc34 175 $zip = new clsTbsZip();
176 $zip->Open($filePath);
177 $content = $zip->FileRead($dataFile);
178
9eae09f1 179 return array($content, $zip);
ad7fdc34 180 }
181
182 /**
183 * Modify contents of docx/odt file(s) and later merged into one final document
184 *
9eae09f1 185 * @param array $contents
4f308883
TO
186 * Content of formatted/token-replaced document.
187 * List of HTML snippets.
63c8a9ba 188 * @param string $fileName
3ed92c14
TO
189 * The logical filename to return to client.
190 * Ex: "HelloWorld.odt".
9eae09f1
CW
191 * @param string $docType
192 * Document type e.g. odt/docx
193 * @param clsTbsZip $zip
194 * Zip archive
2bd6b6cf 195 * @param bool $returnFinalContent
196 * Return the content of file document as a string used in unit test
197 *
198 * @return string
ad7fdc34 199 */
63c8a9ba 200 public static function printDocuments($contents, $fileName, $docType, $zip, $returnFinalContent = FALSE) {
1410eae7 201 $dataMap = self::$ooxmlMap[$docType];
007590da 202
9eae09f1 203 $finalContent = $zip->FileRead($dataMap['dataFile']);
90a73810 204
ad7fdc34 205 // token-replaced document contents of each contact will be merged into final document
206 foreach ($contents as $key => $content) {
207 if ($key == 0) {
208 $finalContent = $content;
04a76231 209 continue;
210 }
ad7fdc34 211
212 // 1. fetch the start position of document body
213 // 2. later fetch only the body part starting from position $start
214 // 3. replace closing body tag with pageBreak
215 // 4. append the $content to the finalContent
216 $start = strpos($content, $dataMap['startTag']);
217 $content = substr($content, $start);
218 $content = str_replace($dataMap['startTag'], $dataMap['pageBreak'], $content);
219 $finalContent = str_replace($dataMap['endTag'], $content, $finalContent);
04a76231 220 }
221
2bd6b6cf 222 if ($returnFinalContent) {
223 return $finalContent;
224 }
225
007590da 226 // Replace the loaded document file content located at $filePath with $finaContent
ad7fdc34 227 $zip->FileReplace($dataMap['dataFile'], $finalContent, TBSZIP_STRING);
228
007590da 229 $zip->Flush(TBSZIP_DOWNLOAD, $fileName);
90a73810 230 }
04a76231 231
0aeb5a1e 232}