• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPOffice / PHPWord / 10288126265

07 Aug 2024 04:27PM UTC coverage: 97.211% (+0.003%) from 97.208%
10288126265

Pull #2618

github

web-flow
Merge 827a570f4 into 7084019cd
Pull Request #2618: [TYPO] Fix hardcoded macro chars in TemplateProcessor method

2 of 2 new or added lines in 1 file covered. (100.0%)

2 existing lines in 1 file now uncovered.

11606 of 11939 relevant lines covered (97.21%)

31.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.31
/src/PhpWord/Shared/ZipArchive.php
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\Shared;
19

20
use PclZip;
21
use PhpOffice\PhpWord\Exception\Exception;
22
use PhpOffice\PhpWord\Settings;
23
use Throwable;
24

25
/**
26
 * ZipArchive wrapper.
27
 *
28
 * Wraps zip archive functionality of PHP ZipArchive and PCLZip. PHP ZipArchive
29
 * properties and methods are bypassed and used as the model for the PCLZip
30
 * emulation. Only needed PHP ZipArchive features are implemented.
31
 *
32
 * @method  bool addFile(string $filename, string $localname = null)
33
 * @method  bool addFromString(string $localname, string $contents)
34
 * @method  false|string getNameIndex(int $index)
35
 * @method  false|int locateName(string $name)
36
 *
37
 * @since   0.10.0
38
 */
39
class ZipArchive
40
{
41
    /** @const int Flags for open method */
42
    const CREATE = 1; // Emulate \ZipArchive::CREATE
43
    const OVERWRITE = 8; // Emulate \ZipArchive::OVERWRITE
44

45
    /**
46
     * Number of files (emulate ZipArchive::$numFiles).
47
     *
48
     * @var int
49
     */
50
    public $numFiles = 0;
51

52
    /**
53
     * Archive filename (emulate ZipArchive::$filename).
54
     *
55
     * @var string
56
     */
57
    public $filename;
58

59
    /**
60
     * Temporary storage directory.
61
     *
62
     * @var string
63
     */
64
    private $tempDir;
65

66
    /**
67
     * Internal zip archive object.
68
     *
69
     * @var PclZip|\ZipArchive
70
     */
71
    private $zip;
72

73
    /**
74
     * Use PCLZip (default behaviour).
75
     *
76
     * @var bool
77
     */
78
    private $usePclzip = true;
79

80
    /**
81
     * Create new instance.
82
     */
83
    public function __construct()
84
    {
85
        $this->usePclzip = (Settings::getZipClass() != 'ZipArchive');
222✔
86
        if ($this->usePclzip) {
222✔
87
            if (!defined('PCLZIP_TEMPORARY_DIR')) {
1✔
88
                define('PCLZIP_TEMPORARY_DIR', Settings::getTempDir() . '/');
1✔
89
            }
90
            require_once 'PCLZip/pclzip.lib.php';
1✔
91
        }
92
    }
93

94
    /**
95
     * Catch function calls: pass to ZipArchive or PCLZip.
96
     *
97
     * `call_user_func_array` can only used for public function, hence the `public` in all `pcl...` methods
98
     *
99
     * @param mixed $function
100
     * @param mixed $args
101
     *
102
     * @return mixed
103
     */
104
    public function __call($function, $args)
105
    {
106
        // Set object and function
107
        $zipFunction = $function;
222✔
108
        if (!$this->usePclzip) {
222✔
109
            $zipObject = $this->zip;
221✔
110
        } else {
111
            $zipObject = $this;
1✔
112
            $zipFunction = "pclzip{$zipFunction}";
1✔
113
        }
114

115
        // Run function
116
        $result = false;
222✔
117
        if (method_exists($zipObject, $zipFunction)) {
222✔
118
            $result = @call_user_func_array([$zipObject, $zipFunction], $args);
222✔
119
        }
120

121
        return $result;
222✔
122
    }
123

124
    /**
125
     * Open a new zip archive.
126
     *
127
     * @param string $filename The file name of the ZIP archive to open
128
     * @param int $flags The mode to use to open the archive
129
     *
130
     * @return bool
131
     */
132
    public function open($filename, $flags = null)
133
    {
134
        $result = true;
222✔
135
        $this->filename = $filename;
222✔
136
        $this->tempDir = Settings::getTempDir();
222✔
137

138
        if (!$this->usePclzip) {
222✔
139
            $zip = new \ZipArchive();
221✔
140

141
            // PHP 8.1 compat - passing null as second arg to \ZipArchive::open() is deprecated
142
            // passing 0 achieves the same behaviour
143
            if ($flags === null) {
221✔
144
                $flags = 0;
13✔
145
            }
146

147
            $result = $zip->open($this->filename, $flags);
221✔
148

149
            // Scrutizer will report the property numFiles does not exist
150
            // See https://github.com/scrutinizer-ci/php-analyzer/issues/190
151
            $this->numFiles = $zip->numFiles;
221✔
152
        } else {
153
            $zip = new PclZip($this->filename);
1✔
154
            $zipContent = $zip->listContent();
1✔
155
            $this->numFiles = is_array($zipContent) ? count($zipContent) : 0;
1✔
156
        }
157
        $this->zip = $zip;
222✔
158

159
        return $result;
222✔
160
    }
161

162
    /**
163
     * Close the active archive.
164
     *
165
     * @return bool
166
     */
167
    public function close()
168
    {
169
        if (!$this->usePclzip) {
223✔
170
            try {
171
                $result = @$this->zip->close();
222✔
172
            } catch (Throwable $e) {
1✔
173
                $result = false;
1✔
174
            }
175
            if ($result === false) {
222✔
176
                throw new Exception("Could not close zip file {$this->filename}: ");
1✔
177
            }
178
        }
179

180
        return true;
223✔
181
    }
182

183
    /**
184
     * Extract the archive contents (emulate \ZipArchive).
185
     *
186
     * @param string $destination
187
     * @param array|string $entries
188
     *
189
     * @return bool
190
     *
191
     * @since 0.10.0
192
     */
193
    public function extractTo($destination, $entries = null)
194
    {
195
        if (!is_dir($destination)) {
7✔
196
            return false;
2✔
197
        }
198

199
        if (!$this->usePclzip) {
7✔
200
            return $this->zip->extractTo($destination, $entries);
6✔
201
        }
202

203
        return $this->pclzipExtractTo($destination, $entries);
1✔
204
    }
205

206
    /**
207
     * Extract file from archive by given file name (emulate \ZipArchive).
208
     *
209
     * @param  string $filename Filename for the file in zip archive
210
     *
211
     * @return string $contents File string contents
212
     */
213
    public function getFromName($filename)
214
    {
215
        if (!$this->usePclzip) {
9✔
216
            $contents = $this->zip->getFromName($filename);
8✔
217
            if ($contents === false) {
8✔
218
                $filename = substr($filename, 1);
1✔
219
                $contents = $this->zip->getFromName($filename);
8✔
220
            }
221
        } else {
222
            $contents = $this->pclzipGetFromName($filename);
1✔
223
        }
224

225
        return $contents;
9✔
226
    }
227

228
    /**
229
     * Add a new file to the zip archive (emulate \ZipArchive).
230
     *
231
     * @param string $filename Directory/Name of the file to add to the zip archive
232
     * @param string $localname Directory/Name of the file added to the zip
233
     *
234
     * @return bool
235
     */
236
    public function pclzipAddFile($filename, $localname = null)
237
    {
238
        /** @var PclZip $zip Type hint */
239
        $zip = $this->zip;
1✔
240

241
        // Bugfix GH-261 https://github.com/PHPOffice/PHPWord/pull/261
242
        $realpathFilename = realpath($filename);
1✔
243
        if ($realpathFilename !== false) {
1✔
244
            $filename = $realpathFilename;
1✔
245
        }
246

247
        $filenameParts = pathinfo($filename);
1✔
248
        $localnameParts = pathinfo($localname);
1✔
249

250
        // To Rename the file while adding it to the zip we
251
        //   need to create a temp file with the correct name
252
        $tempFile = false;
1✔
253
        if ($filenameParts['basename'] != $localnameParts['basename']) {
1✔
254
            $tempFile = true; // temp file created
1✔
255
            $temppath = $this->tempDir . DIRECTORY_SEPARATOR . $localnameParts['basename'];
1✔
256
            copy($filename, $temppath);
1✔
257
            $filename = $temppath;
1✔
258
            $filenameParts = pathinfo($temppath);
1✔
259
        }
260

261
        $pathRemoved = $filenameParts['dirname'];
1✔
262
        $pathAdded = $localnameParts['dirname'];
1✔
263

264
        if (!$this->usePclzip) {
1✔
UNCOV
265
            $pathAdded = $pathAdded . '/' . ltrim(str_replace('\\', '/', substr($filename, strlen($pathRemoved))), '/');
×
266
            //$res = $zip->addFile($filename, $pathAdded);
UNCOV
267
            $res = $zip->addFromString($pathAdded, file_get_contents($filename));       // addFile can't use subfolders in some cases
×
268
        } else {
269
            $res = $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $pathRemoved, PCLZIP_OPT_ADD_PATH, $pathAdded);
1✔
270
        }
271

272
        if ($tempFile) {
1✔
273
            // Remove temp file, if created
274
            unlink($this->tempDir . DIRECTORY_SEPARATOR . $localnameParts['basename']);
1✔
275
        }
276

277
        return $res != 0;
1✔
278
    }
279

280
    /**
281
     * Add a new file to the zip archive from a string of raw data (emulate \ZipArchive).
282
     *
283
     * @param string $localname Directory/Name of the file to add to the zip archive
284
     * @param string $contents String of data to add to the zip archive
285
     *
286
     * @return bool
287
     */
288
    public function pclzipAddFromString($localname, $contents)
289
    {
290
        /** @var PclZip $zip Type hint */
291
        $zip = $this->zip;
1✔
292
        $filenameParts = pathinfo($localname);
1✔
293

294
        // Write $contents to a temp file
295
        $handle = fopen($this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename'], 'wb');
1✔
296
        fwrite($handle, $contents);
1✔
297
        fclose($handle);
1✔
298

299
        // Add temp file to zip
300
        $filename = $this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename'];
1✔
301
        $pathRemoved = $this->tempDir;
1✔
302
        $pathAdded = $filenameParts['dirname'];
1✔
303

304
        $res = $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $pathRemoved, PCLZIP_OPT_ADD_PATH, $pathAdded);
1✔
305

306
        // Remove temp file
307
        @unlink($this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename']);
1✔
308

309
        return $res != 0;
1✔
310
    }
311

312
    /**
313
     * Extract the archive contents (emulate \ZipArchive).
314
     *
315
     * @param string $destination
316
     * @param array|string $entries
317
     *
318
     * @return bool
319
     *
320
     * @since 0.10.0
321
     */
322
    public function pclzipExtractTo($destination, $entries = null)
323
    {
324
        /** @var PclZip $zip Type hint */
325
        $zip = $this->zip;
1✔
326

327
        // Extract all files
328
        if (null === $entries) {
1✔
329
            $result = $zip->extract(PCLZIP_OPT_PATH, $destination);
1✔
330

331
            return $result > 0;
1✔
332
        }
333

334
        // Extract by entries
335
        if (!is_array($entries)) {
1✔
336
            $entries = [$entries];
1✔
337
        }
338
        foreach ($entries as $entry) {
1✔
339
            $entryIndex = $this->locateName($entry);
1✔
340
            $result = $zip->extractByIndex($entryIndex, PCLZIP_OPT_PATH, $destination);
1✔
341
            if ($result <= 0) {
1✔
342
                return false;
1✔
343
            }
344
        }
345

346
        return true;
1✔
347
    }
348

349
    /**
350
     * Extract file from archive by given file name (emulate \ZipArchive).
351
     *
352
     * @param  string $filename Filename for the file in zip archive
353
     *
354
     * @return string $contents File string contents
355
     */
356
    public function pclzipGetFromName($filename)
357
    {
358
        /** @var PclZip $zip Type hint */
359
        $zip = $this->zip;
1✔
360
        $listIndex = $this->pclzipLocateName($filename);
1✔
361
        $contents = false;
1✔
362

363
        if ($listIndex !== false) {
1✔
364
            $extracted = $zip->extractByIndex($listIndex, PCLZIP_OPT_EXTRACT_AS_STRING);
1✔
365
        } else {
366
            $filename = substr($filename, 1);
1✔
367
            $listIndex = $this->pclzipLocateName($filename);
1✔
368
            $extracted = $zip->extractByIndex($listIndex, PCLZIP_OPT_EXTRACT_AS_STRING);
1✔
369
        }
370
        if ((is_array($extracted)) && ($extracted != 0)) {
1✔
371
            $contents = $extracted[0]['content'];
1✔
372
        }
373

374
        return $contents;
1✔
375
    }
376

377
    /**
378
     * Returns the name of an entry using its index (emulate \ZipArchive).
379
     *
380
     * @param int $index
381
     *
382
     * @return bool|string
383
     *
384
     * @since 0.10.0
385
     */
386
    public function pclzipGetNameIndex($index)
387
    {
388
        /** @var PclZip $zip Type hint */
389
        $zip = $this->zip;
1✔
390
        $list = $zip->listContent();
1✔
391
        if (isset($list[$index])) {
1✔
392
            return $list[$index]['filename'];
1✔
393
        }
394

395
        return false;
1✔
396
    }
397

398
    /**
399
     * Returns the index of the entry in the archive (emulate \ZipArchive).
400
     *
401
     * @param string $filename Filename for the file in zip archive
402
     *
403
     * @return false|int
404
     */
405
    public function pclzipLocateName($filename)
406
    {
407
        /** @var PclZip $zip Type hint */
408
        $zip = $this->zip;
1✔
409
        $list = $zip->listContent();
1✔
410
        $listCount = count($list);
1✔
411
        $listIndex = -1;
1✔
412
        for ($i = 0; $i < $listCount; ++$i) {
1✔
413
            if (strtolower($list[$i]['filename']) == strtolower($filename) ||
1✔
414
                strtolower($list[$i]['stored_filename']) == strtolower($filename)) {
1✔
415
                $listIndex = $i;
1✔
416

417
                break;
1✔
418
            }
419
        }
420

421
        return ($listIndex > -1) ? $listIndex : false;
1✔
422
    }
423
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc