Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
74 / 74
100.00% covered (success)
100.00%
17 / 17
CRAP
100.00% covered (success)
100.00%
1 / 1
AbstractWriter
100.00% covered (success)
100.00%
74 / 74
100.00% covered (success)
100.00%
17 / 17
45
100.00% covered (success)
100.00%
1 / 1
 getPhpWord
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setPhpWord
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getWriterPart
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 isUseDiskCaching
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setUseDiskCaching
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 getDiskCachingDirectory
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTempDir
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setTempDir
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getTempFile
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 cleanupTempFile
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 clearTempDir
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getZipArchive
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 openFile
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 writeFile
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 addFilesToPackage
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 addFileToPackage
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
5
 deleteDir
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
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
18namespace PhpOffice\PhpWord\Writer;
19
20use PhpOffice\PhpWord\Exception\CopyFileException;
21use PhpOffice\PhpWord\Exception\Exception;
22use PhpOffice\PhpWord\PhpWord;
23use PhpOffice\PhpWord\Settings;
24use PhpOffice\PhpWord\Shared\ZipArchive;
25
26/**
27 * Abstract writer class.
28 *
29 * @since 0.10.0
30 */
31abstract class AbstractWriter implements WriterInterface
32{
33    /**
34     * PHPWord object.
35     *
36     * @var \PhpOffice\PhpWord\PhpWord
37     */
38    protected $phpWord;
39
40    /**
41     * Part name and file name pairs.
42     *
43     * @var array
44     */
45    protected $parts = [];
46
47    /**
48     * Individual writers.
49     *
50     * @var array
51     */
52    protected $writerParts = [];
53
54    /**
55     * Paths to store media files.
56     *
57     * @var array
58     */
59    protected $mediaPaths = ['image' => '', 'object' => ''];
60
61    /**
62     * Use disk caching.
63     *
64     * @var bool
65     */
66    private $useDiskCaching = false;
67
68    /**
69     * Disk caching directory.
70     *
71     * @var string
72     */
73    private $diskCachingDirectory = './';
74
75    /**
76     * Temporary directory.
77     *
78     * @var string
79     */
80    private $tempDir = '';
81
82    /**
83     * Original file name.
84     *
85     * @var string
86     */
87    private $originalFilename;
88
89    /**
90     * Temporary file name.
91     *
92     * @var string
93     */
94    private $tempFilename;
95
96    /**
97     * Get PhpWord object.
98     *
99     * @return \PhpOffice\PhpWord\PhpWord
100     */
101    public function getPhpWord()
102    {
103        if (null !== $this->phpWord) {
104            return $this->phpWord;
105        }
106
107        throw new Exception('No PhpWord assigned.');
108    }
109
110    /**
111     * Set PhpWord object.
112     *
113     * @param \PhpOffice\PhpWord\PhpWord
114     *
115     * @return self
116     */
117    public function setPhpWord(?PhpWord $phpWord = null)
118    {
119        $this->phpWord = $phpWord;
120
121        return $this;
122    }
123
124    /**
125     * Get writer part.
126     *
127     * @param string $partName Writer part name
128     *
129     * @return mixed
130     */
131    public function getWriterPart($partName = '')
132    {
133        if ($partName != '' && isset($this->writerParts[strtolower($partName)])) {
134            return $this->writerParts[strtolower($partName)];
135        }
136
137        return null;
138    }
139
140    /**
141     * Get use disk caching status.
142     *
143     * @return bool
144     */
145    public function isUseDiskCaching()
146    {
147        return $this->useDiskCaching;
148    }
149
150    /**
151     * Set use disk caching status.
152     *
153     * @param bool $value
154     * @param string $directory
155     *
156     * @return self
157     */
158    public function setUseDiskCaching($value = false, $directory = null)
159    {
160        $this->useDiskCaching = $value;
161
162        if (null !== $directory) {
163            if (is_dir($directory)) {
164                $this->diskCachingDirectory = $directory;
165            } else {
166                throw new Exception("Directory does not exist: $directory");
167            }
168        }
169
170        return $this;
171    }
172
173    /**
174     * Get disk caching directory.
175     *
176     * @return string
177     */
178    public function getDiskCachingDirectory()
179    {
180        return $this->diskCachingDirectory;
181    }
182
183    /**
184     * Get temporary directory.
185     *
186     * @return string
187     */
188    public function getTempDir()
189    {
190        return $this->tempDir;
191    }
192
193    /**
194     * Set temporary directory.
195     *
196     * @param string $value
197     *
198     * @return self
199     */
200    public function setTempDir($value)
201    {
202        if (!is_dir($value)) {
203            mkdir($value);
204        }
205        $this->tempDir = $value;
206
207        return $this;
208    }
209
210    /**
211     * Get temporary file name.
212     *
213     * If $filename is php://output or php://stdout, make it a temporary file
214     *
215     * @param string $filename
216     *
217     * @return string
218     */
219    protected function getTempFile($filename)
220    {
221        // Temporary directory
222        $this->setTempDir(Settings::getTempDir() . uniqid('/PHPWordWriter_', true) . '/');
223
224        // Temporary file
225        $this->originalFilename = $filename;
226        if (strpos(strtolower($filename), 'php://') === 0) {
227            $filename = tempnam(Settings::getTempDir(), 'PhpWord');
228            if (false === $filename) {
229                $filename = $this->originalFilename; // @codeCoverageIgnore
230            } // @codeCoverageIgnore
231        }
232        $this->tempFilename = $filename;
233
234        return $this->tempFilename;
235    }
236
237    /**
238     * Cleanup temporary file.
239     */
240    protected function cleanupTempFile(): void
241    {
242        if ($this->originalFilename != $this->tempFilename) {
243            // @codeCoverageIgnoreStart
244            // Can't find any test case. Uncomment when found.
245            if (false === copy($this->tempFilename, $this->originalFilename)) {
246                throw new CopyFileException($this->tempFilename, $this->originalFilename);
247            }
248            // @codeCoverageIgnoreEnd
249            @unlink($this->tempFilename);
250        }
251
252        $this->clearTempDir();
253    }
254
255    /**
256     * Clear temporary directory.
257     */
258    protected function clearTempDir(): void
259    {
260        if (is_dir($this->tempDir)) {
261            $this->deleteDir($this->tempDir);
262        }
263    }
264
265    /**
266     * Get ZipArchive object.
267     *
268     * @param string $filename
269     *
270     * @return \PhpOffice\PhpWord\Shared\ZipArchive
271     */
272    protected function getZipArchive($filename)
273    {
274        // Remove any existing file
275        if (file_exists($filename)) {
276            unlink($filename);
277        }
278
279        // Try opening the ZIP file
280        $zip = new ZipArchive();
281
282        // @codeCoverageIgnoreStart
283        // Can't find any test case. Uncomment when found.
284        if ($zip->open($filename, ZipArchive::OVERWRITE) !== true) {
285            if ($zip->open($filename, ZipArchive::CREATE) !== true) {
286                throw new \Exception("Could not open '{$filename}' for writing.");
287            }
288        }
289        // @codeCoverageIgnoreEnd
290
291        return $zip;
292    }
293
294    /**
295     * Open file for writing.
296     *
297     * @since 0.11.0
298     *
299     * @param string $filename
300     *
301     * @return resource
302     */
303    protected function openFile($filename)
304    {
305        $filename = $this->getTempFile($filename);
306        $fileHandle = fopen($filename, 'wb');
307        // @codeCoverageIgnoreStart
308        // Can't find any test case. Uncomment when found.
309        if ($fileHandle === false) {
310            throw new \Exception("Could not open '{$filename}' for writing.");
311        }
312        // @codeCoverageIgnoreEnd
313
314        return $fileHandle;
315    }
316
317    /**
318     * Write content to file.
319     *
320     * @since 0.11.0
321     *
322     * @param resource $fileHandle
323     * @param string $content
324     */
325    protected function writeFile($fileHandle, $content): void
326    {
327        fwrite($fileHandle, $content);
328        fclose($fileHandle);
329        $this->cleanupTempFile();
330    }
331
332    /**
333     * Add files to package.
334     *
335     * @param mixed $elements
336     */
337    protected function addFilesToPackage(ZipArchive $zip, $elements): void
338    {
339        foreach ($elements as $element) {
340            $type = $element['type']; // image|object|link
341
342            // Skip nonregistered types and set target
343            if (!isset($this->mediaPaths[$type])) {
344                continue;
345            }
346            $target = $this->mediaPaths[$type] . $element['target'];
347
348            // Retrive GD image content or get local media
349            if (isset($element['isMemImage']) && $element['isMemImage']) {
350                $imageContents = $element['imageString'];
351                $zip->addFromString($target, $imageContents);
352            } else {
353                $this->addFileToPackage($zip, $element['source'], $target);
354            }
355        }
356    }
357
358    /**
359     * Add file to package.
360     *
361     * Get the actual source from an archive image.
362     *
363     * @param \PhpOffice\PhpWord\Shared\ZipArchive $zipPackage
364     * @param string $source
365     * @param string $target
366     */
367    protected function addFileToPackage($zipPackage, $source, $target): void
368    {
369        $isArchive = strpos($source, 'zip://') !== false;
370        $actualSource = null;
371        if ($isArchive) {
372            $source = substr($source, 6);
373            [$zipFilename, $imageFilename] = explode('#', $source);
374
375            $zip = new ZipArchive();
376            if ($zip->open($zipFilename) !== false) {
377                if ($zip->locateName($imageFilename)) {
378                    $zip->extractTo($this->getTempDir(), $imageFilename);
379                    $actualSource = $this->getTempDir() . DIRECTORY_SEPARATOR . $imageFilename;
380                }
381            }
382            $zip->close();
383        } else {
384            $actualSource = $source;
385        }
386
387        if (null !== $actualSource) {
388            $zipPackage->addFile($actualSource, $target);
389        }
390    }
391
392    /**
393     * Delete directory.
394     *
395     * @param string $dir
396     */
397    private function deleteDir($dir): void
398    {
399        foreach (scandir($dir) as $file) {
400            if ($file === '.' || $file === '..') {
401                continue;
402            } elseif (is_file($dir . '/' . $file)) {
403                unlink($dir . '/' . $file);
404            } elseif (is_dir($dir . '/' . $file)) {
405                $this->deleteDir($dir . '/' . $file);
406            }
407        }
408
409        rmdir($dir);
410    }
411}