Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
127 / 127 |
|
100.00% |
10 / 10 |
CRAP | |
100.00% |
1 / 1 |
Word2007 | |
100.00% |
127 / 127 |
|
100.00% |
10 / 10 |
34 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
30 / 30 |
|
100.00% |
1 / 1 |
3 | |||
save | |
100.00% |
29 / 29 |
|
100.00% |
1 / 1 |
6 | |||
getContentTypes | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRelationships | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
addHeaderFooterMedia | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
5 | |||
addHeaderFooterContent | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
addNotes | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
4 | |||
addComments | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
addChart | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
3 | |||
registerContentTypes | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
6 |
1 | <?php |
2 | /** |
3 | * This file is part of PHPWord - A pure PHP library for reading and writing |
4 | * word processing documents. |
5 | * |
6 | * PHPWord is free software distributed under the terms of the GNU Lesser |
7 | * General Public License version 3 as published by the Free Software Foundation. |
8 | * |
9 | * For the full copyright and license information, please read the LICENSE |
10 | * file that was distributed with this source code. For the full list of |
11 | * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. |
12 | * |
13 | * @see https://github.com/PHPOffice/PHPWord |
14 | * |
15 | * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 |
16 | */ |
17 | |
18 | namespace PhpOffice\PhpWord\Writer; |
19 | |
20 | use PhpOffice\PhpWord\Element\Section; |
21 | use PhpOffice\PhpWord\Media; |
22 | use PhpOffice\PhpWord\PhpWord; |
23 | use PhpOffice\PhpWord\Shared\ZipArchive; |
24 | |
25 | /** |
26 | * Word2007 writer. |
27 | */ |
28 | class Word2007 extends AbstractWriter implements WriterInterface |
29 | { |
30 | /** |
31 | * Content types values. |
32 | * |
33 | * @var array |
34 | */ |
35 | private $contentTypes = ['default' => [], 'override' => []]; |
36 | |
37 | /** |
38 | * Document relationship. |
39 | * |
40 | * @var array |
41 | */ |
42 | private $relationships = []; |
43 | |
44 | /** |
45 | * Create new Word2007 writer. |
46 | * |
47 | * @param \PhpOffice\PhpWord\PhpWord |
48 | */ |
49 | public function __construct(?PhpWord $phpWord = null) |
50 | { |
51 | // Assign PhpWord |
52 | $this->setPhpWord($phpWord); |
53 | |
54 | // Create parts |
55 | // The first four files need to be in this order for Mimetype detection to work |
56 | $this->parts = [ |
57 | 'ContentTypes' => '[Content_Types].xml', |
58 | 'Rels' => '_rels/.rels', |
59 | 'RelsDocument' => 'word/_rels/document.xml.rels', |
60 | 'Document' => 'word/document.xml', |
61 | 'DocPropsApp' => 'docProps/app.xml', |
62 | 'DocPropsCore' => 'docProps/core.xml', |
63 | 'DocPropsCustom' => 'docProps/custom.xml', |
64 | 'Comments' => 'word/comments.xml', |
65 | 'Styles' => 'word/styles.xml', |
66 | 'Numbering' => 'word/numbering.xml', |
67 | 'Settings' => 'word/settings.xml', |
68 | 'WebSettings' => 'word/webSettings.xml', |
69 | 'FontTable' => 'word/fontTable.xml', |
70 | 'Theme' => 'word/theme/theme1.xml', |
71 | 'RelsPart' => '', |
72 | 'Header' => '', |
73 | 'Footer' => '', |
74 | 'Footnotes' => '', |
75 | 'Endnotes' => '', |
76 | 'Chart' => '', |
77 | ]; |
78 | foreach (array_keys($this->parts) as $partName) { |
79 | $partClass = static::class . '\\Part\\' . $partName; |
80 | if (class_exists($partClass)) { |
81 | /** @var \PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart $part Type hint */ |
82 | $part = new $partClass(); |
83 | $part->setParentWriter($this); |
84 | $this->writerParts[strtolower($partName)] = $part; |
85 | } |
86 | } |
87 | |
88 | // Set package paths |
89 | $this->mediaPaths = ['image' => 'word/media/', 'object' => 'word/embeddings/']; |
90 | } |
91 | |
92 | /** |
93 | * Save document by name. |
94 | */ |
95 | public function save(string $filename): void |
96 | { |
97 | $filename = $this->getTempFile($filename); |
98 | $zip = $this->getZipArchive($filename); |
99 | $phpWord = $this->getPhpWord(); |
100 | |
101 | // Content types |
102 | $this->contentTypes['default'] = [ |
103 | 'rels' => 'application/vnd.openxmlformats-package.relationships+xml', |
104 | 'xml' => 'application/xml', |
105 | ]; |
106 | |
107 | // Add section media files |
108 | $sectionMedia = Media::getElements('section'); |
109 | if (!empty($sectionMedia)) { |
110 | $this->addFilesToPackage($zip, $sectionMedia); |
111 | $this->registerContentTypes($sectionMedia); |
112 | foreach ($sectionMedia as $element) { |
113 | $this->relationships[] = $element; |
114 | } |
115 | } |
116 | |
117 | // Add header/footer media files & relations |
118 | $this->addHeaderFooterMedia($zip, 'header'); |
119 | $this->addHeaderFooterMedia($zip, 'footer'); |
120 | |
121 | // Add header/footer contents |
122 | $rId = Media::countElements('section') + 6; //@see Rels::writeDocRels for 6 first elements |
123 | $sections = $phpWord->getSections(); |
124 | foreach ($sections as $section) { |
125 | $this->addHeaderFooterContent($section, $zip, 'header', $rId); |
126 | $this->addHeaderFooterContent($section, $zip, 'footer', $rId); |
127 | } |
128 | |
129 | $this->addNotes($zip, $rId, 'footnote'); |
130 | $this->addNotes($zip, $rId, 'endnote'); |
131 | $this->addComments($zip, $rId); |
132 | $this->addChart($zip, $rId); |
133 | |
134 | // Write parts |
135 | foreach ($this->parts as $partName => $fileName) { |
136 | if ($fileName != '') { |
137 | $zip->addFromString($fileName, $this->getWriterPart($partName)->write()); |
138 | } |
139 | } |
140 | |
141 | // Close zip archive and cleanup temp file |
142 | $zip->close(); |
143 | $this->cleanupTempFile(); |
144 | } |
145 | |
146 | /** |
147 | * Get content types. |
148 | * |
149 | * @return array |
150 | */ |
151 | public function getContentTypes() |
152 | { |
153 | return $this->contentTypes; |
154 | } |
155 | |
156 | /** |
157 | * Get content types. |
158 | * |
159 | * @return array |
160 | */ |
161 | public function getRelationships() |
162 | { |
163 | return $this->relationships; |
164 | } |
165 | |
166 | /** |
167 | * Add header/footer media files, e.g. footer1.xml.rels. |
168 | * |
169 | * @param string $docPart |
170 | */ |
171 | private function addHeaderFooterMedia(ZipArchive $zip, $docPart): void |
172 | { |
173 | $elements = Media::getElements($docPart); |
174 | if (!empty($elements)) { |
175 | foreach ($elements as $file => $media) { |
176 | if (count($media) > 0) { |
177 | if (!empty($media)) { |
178 | $this->addFilesToPackage($zip, $media); |
179 | $this->registerContentTypes($media); |
180 | } |
181 | |
182 | /** @var \PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart $writerPart Type hint */ |
183 | $writerPart = $this->getWriterPart('relspart')->setMedia($media); |
184 | $zip->addFromString("word/_rels/{$file}.xml.rels", $writerPart->write()); |
185 | } |
186 | } |
187 | } |
188 | } |
189 | |
190 | /** |
191 | * Add header/footer content. |
192 | * |
193 | * @param string $elmType header|footer |
194 | * @param int &$rId |
195 | */ |
196 | private function addHeaderFooterContent(Section &$section, ZipArchive $zip, $elmType, &$rId): void |
197 | { |
198 | $getFunction = $elmType == 'header' ? 'getHeaders' : 'getFooters'; |
199 | $elmCount = ($section->getSectionId() - 1) * 3; |
200 | $elements = $section->$getFunction(); |
201 | /** @var \PhpOffice\PhpWord\Element\AbstractElement $element Type hint */ |
202 | foreach ($elements as &$element) { |
203 | ++$elmCount; |
204 | $element->setRelationId(++$rId); |
205 | $elmFile = "{$elmType}{$elmCount}.xml"; // e.g. footer1.xml |
206 | $this->contentTypes['override']["/word/$elmFile"] = $elmType; |
207 | $this->relationships[] = ['target' => $elmFile, 'type' => $elmType, 'rID' => $rId]; |
208 | |
209 | /** @var \PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart $writerPart Type hint */ |
210 | $writerPart = $this->getWriterPart($elmType)->setElement($element); |
211 | $zip->addFromString("word/$elmFile", $writerPart->write()); |
212 | } |
213 | } |
214 | |
215 | /** |
216 | * Add footnotes/endnotes. |
217 | * |
218 | * @param int &$rId |
219 | * @param string $noteType |
220 | */ |
221 | private function addNotes(ZipArchive $zip, &$rId, $noteType = 'footnote'): void |
222 | { |
223 | $phpWord = $this->getPhpWord(); |
224 | $noteType = ($noteType == 'endnote') ? 'endnote' : 'footnote'; |
225 | $partName = "{$noteType}s"; |
226 | $method = 'get' . $partName; |
227 | $collection = $phpWord->$method(); |
228 | |
229 | // Add footnotes media files, relations, and contents |
230 | if ($collection->countItems() > 0) { |
231 | $media = Media::getElements($noteType); |
232 | $this->addFilesToPackage($zip, $media); |
233 | $this->registerContentTypes($media); |
234 | $this->contentTypes['override']["/word/{$partName}.xml"] = $partName; |
235 | $this->relationships[] = ['target' => "{$partName}.xml", 'type' => $partName, 'rID' => ++$rId]; |
236 | |
237 | // Write relationships file, e.g. word/_rels/footnotes.xml |
238 | if (!empty($media)) { |
239 | /** @var \PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart $writerPart Type hint */ |
240 | $writerPart = $this->getWriterPart('relspart')->setMedia($media); |
241 | $zip->addFromString("word/_rels/{$partName}.xml.rels", $writerPart->write()); |
242 | } |
243 | |
244 | // Write content file, e.g. word/footnotes.xml |
245 | $writerPart = $this->getWriterPart($partName)->setElements($collection->getItems()); |
246 | $zip->addFromString("word/{$partName}.xml", $writerPart->write()); |
247 | } |
248 | } |
249 | |
250 | /** |
251 | * Add comments. |
252 | * |
253 | * @param int &$rId |
254 | */ |
255 | private function addComments(ZipArchive $zip, &$rId): void |
256 | { |
257 | $phpWord = $this->getPhpWord(); |
258 | $collection = $phpWord->getComments(); |
259 | $partName = 'comments'; |
260 | |
261 | // Add comment relations and contents |
262 | if ($collection->countItems() > 0) { |
263 | $this->relationships[] = ['target' => "{$partName}.xml", 'type' => $partName, 'rID' => ++$rId]; |
264 | |
265 | // Write content file, e.g. word/comments.xml |
266 | $writerPart = $this->getWriterPart($partName)->setElements($collection->getItems()); |
267 | $zip->addFromString("word/{$partName}.xml", $writerPart->write()); |
268 | } |
269 | } |
270 | |
271 | /** |
272 | * Add chart. |
273 | * |
274 | * @param int &$rId |
275 | */ |
276 | private function addChart(ZipArchive $zip, &$rId): void |
277 | { |
278 | $phpWord = $this->getPhpWord(); |
279 | |
280 | $collection = $phpWord->getCharts(); |
281 | $index = 0; |
282 | if ($collection->countItems() > 0) { |
283 | /** @var \PhpOffice\PhpWord\Element\Chart $chart */ |
284 | foreach ($collection->getItems() as $chart) { |
285 | ++$index; |
286 | ++$rId; |
287 | $filename = "charts/chart{$index}.xml"; |
288 | |
289 | // ContentTypes.xml |
290 | $this->contentTypes['override']["/word/{$filename}"] = 'chart'; |
291 | |
292 | // word/_rels/document.xml.rel |
293 | $this->relationships[] = ['target' => $filename, 'type' => 'chart', 'rID' => $rId]; |
294 | |
295 | // word/charts/chartN.xml |
296 | $chart->setRelationId($rId); |
297 | $writerPart = $this->getWriterPart('Chart'); |
298 | $writerPart->setElement($chart); |
299 | $zip->addFromString("word/{$filename}", $writerPart->write()); |
300 | } |
301 | } |
302 | } |
303 | |
304 | /** |
305 | * Register content types for each media. |
306 | * |
307 | * @param array $media |
308 | */ |
309 | private function registerContentTypes($media): void |
310 | { |
311 | foreach ($media as $medium) { |
312 | $mediumType = $medium['type']; |
313 | if ($mediumType == 'image') { |
314 | $extension = $medium['imageExtension']; |
315 | if (!isset($this->contentTypes['default'][$extension])) { |
316 | $this->contentTypes['default'][$extension] = $medium['imageType']; |
317 | } |
318 | } elseif ($mediumType == 'object') { |
319 | if (!isset($this->contentTypes['default']['bin'])) { |
320 | $this->contentTypes['default']['bin'] = 'application/vnd.openxmlformats-officedocument.oleObject'; |
321 | } |
322 | } |
323 | } |
324 | } |
325 | } |