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