Merge pull request #9629 from colemanw/CRM-19770
[civicrm-core.git] / CRM / Utils / PDF / Document.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2017 |
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
31 * @copyright CiviCRM LLC (c) 2004-2017
32 */
33
34 require_once 'TbsZip/tbszip.php';
35
36 class CRM_Utils_PDF_Document {
37
38 public static $ooxmlMap = array(
39 'docx' => array(
40 'dataFile' => 'word/document.xml',
41 'startTag' => '<w:body>',
42 '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>',
43 'endTag' => '</w:body></w:document>',
44 ),
45 'odt' => array(
46 'dataFile' => 'content.xml',
47 'startTag' => '<office:body>',
48 'pageBreak' => '<text:p text:style-name="Standard"></text:p>',
49 'endTag' => '</office:body></office:document-content>',
50 ),
51 );
52
53 /**
54 * @param array $pages
55 * @param string $fileName
56 * @param array|int $format
57 */
58 public static function html2doc($pages, $fileName, $format = array()) {
59 if (is_array($format)) {
60 // PDF Page Format parameters passed in - merge with defaults
61 $format += CRM_Core_BAO_PdfFormat::getDefaultValues();
62 }
63 else {
64 // PDF Page Format ID passed in
65 $format = CRM_Core_BAO_PdfFormat::getById($format);
66 }
67 $paperSize = CRM_Core_BAO_PaperSize::getByName($format['paper_size']);
68
69 $metric = CRM_Core_BAO_PdfFormat::getValue('metric', $format);
70 $pageStyle = array(
71 'orientation' => CRM_Core_BAO_PdfFormat::getValue('orientation', $format),
72 'pageSizeW' => self::toTwip($paperSize['width'], $paperSize['metric']),
73 'pageSizeH' => self::toTwip($paperSize['height'], $paperSize['metric']),
74 'marginTop' => self::toTwip(CRM_Core_BAO_PdfFormat::getValue('margin_top', $format), $metric),
75 'marginRight' => self::toTwip(CRM_Core_BAO_PdfFormat::getValue('margin_right', $format), $metric),
76 'marginBottom' => self::toTwip(CRM_Core_BAO_PdfFormat::getValue('margin_bottom', $format), $metric),
77 'marginLeft' => self::toTwip(CRM_Core_BAO_PdfFormat::getValue('margin_left', $format), $metric),
78 );
79
80 $ext = pathinfo($fileName, PATHINFO_EXTENSION);
81
82 $phpWord = new \PhpOffice\PhpWord\PhpWord();
83
84 $phpWord->getDocInfo()
85 ->setCreator(CRM_Core_DAO::getFieldValue('CRM_Contact_BAO_Contact', CRM_Core_Session::getLoggedInContactID(), 'display_name'));
86
87 foreach ((array) $pages as $page => $html) {
88 $section = $phpWord->addSection($pageStyle + array('breakType' => 'nextPage'));
89 \PhpOffice\PhpWord\Shared\Html::addHtml($section, $html);
90 }
91
92 self::printDoc($phpWord, $ext, $fileName);
93 }
94
95 /**
96 * @param object|string $phpWord
97 * @param string $ext
98 * @param string $fileName
99 */
100 public static function printDoc($phpWord, $ext, $fileName) {
101 $formats = array(
102 'docx' => 'Word2007',
103 'odt' => 'ODText',
104 'html' => 'HTML',
105 // todo
106 'pdf' => 'PDF',
107 );
108
109 if (realpath($fileName)) {
110 $phpWord = \PhpOffice\PhpWord\IOFactory::load($fileName, $formats[$ext]);
111 }
112
113 $objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, $formats[$ext]);
114
115 CRM_Utils_System::setHttpHeader('Content-Type', "application/$ext");
116 CRM_Utils_System::setHttpHeader('Content-Disposition', 'attachment; filename="' . $fileName . '"');
117 $objWriter->save("php://output");
118 }
119
120 /**
121 * @param $value
122 * @param $metric
123 * @return int
124 */
125 public static function toTwip($value, $metric) {
126 $point = CRM_Utils_PDF_Utils::convertMetric($value, $metric, 'pt');
127 return \PhpOffice\PhpWord\Shared\Converter::pointToTwip($point);
128 }
129
130 /**
131 * @param array $path docx/odt file path
132 * @param string $type File type
133 *
134 * @return array
135 * Return extracted content of document in HTML and document type
136 */
137 public static function docReader($path, $type) {
138 $type = array_search($type, CRM_Core_SelectValues::documentApplicationType());
139 $fileType = ($type == 'docx') ? 'Word2007' : 'ODText';
140
141 $phpWord = \PhpOffice\PhpWord\IOFactory::load($path, $fileType);
142 $phpWordHTML = new \PhpOffice\PhpWord\Writer\HTML($phpWord);
143
144 // return the html content for tokenreplacment and eventually used for document download
145 return array($phpWordHTML->getWriterPart('Body')->write(), $type);
146 }
147
148 /**
149 * Extract content of docx/odt file
150 *
151 * @param string $filePath Document file path
152 * @param string $docType File type of document
153 *
154 * @return array
155 * [string, clsTbsZip]
156 */
157 public static function unzipDoc($filePath, $docType) {
158 $dataFile = self::$ooxmlMap[$docType]['dataFile'];
159
160 $zip = new clsTbsZip();
161 $zip->Open($filePath);
162 $content = $zip->FileRead($dataFile);
163
164 return array($content, $zip);
165 }
166
167 /**
168 * Modify contents of docx/odt file(s) and later merged into one final document
169 *
170 * @param string $filePath
171 * Document file path
172 * @param array $contents
173 * Content of formatted/token-replaced document
174 * @param string $docType
175 * Document type e.g. odt/docx
176 * @param clsTbsZip $zip
177 * Zip archive
178 * @param bool $returnFinalContent
179 * Return the content of file document as a string used in unit test
180 *
181 * @return string
182 */
183 public static function printDocuments($filePath, $contents, $docType, $zip, $returnFinalContent = FALSE) {
184 $dataMap = self::$ooxmlMap[$docType];
185
186 $finalContent = $zip->FileRead($dataMap['dataFile']);
187
188 // token-replaced document contents of each contact will be merged into final document
189 foreach ($contents as $key => $content) {
190 if ($key == 0) {
191 $finalContent = $content;
192 continue;
193 }
194
195 // 1. fetch the start position of document body
196 // 2. later fetch only the body part starting from position $start
197 // 3. replace closing body tag with pageBreak
198 // 4. append the $content to the finalContent
199 $start = strpos($content, $dataMap['startTag']);
200 $content = substr($content, $start);
201 $content = str_replace($dataMap['startTag'], $dataMap['pageBreak'], $content);
202 $finalContent = str_replace($dataMap['endTag'], $content, $finalContent);
203 }
204
205 if ($returnFinalContent) {
206 return $finalContent;
207 }
208
209 // Replace the loaded document file content located at $filePath with $finaContent
210 $zip->FileReplace($dataMap['dataFile'], $finalContent, TBSZIP_STRING);
211
212 $fileName = pathinfo($filePath, PATHINFO_FILENAME) . '.' . $docType;
213 $zip->Flush(TBSZIP_DOWNLOAD, $fileName);
214 }
215
216 }