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

PHPOffice / PHPWord / 13025401639

29 Jan 2025 05:48AM UTC coverage: 96.825% (-0.4%) from 97.217%
13025401639

Pull #2562

github

web-flow
Merge ebe3e5dac into 2d2759585
Pull Request #2562: TemplateProcessor SetComplexBlock/Value and Sections

6 of 7 new or added lines in 1 file covered. (85.71%)

245 existing lines in 40 files now uncovered.

12227 of 12628 relevant lines covered (96.82%)

34.56 hits per line

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

96.64
/src/PhpWord/Shared/ZipArchive.php
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

19
namespace PhpOffice\PhpWord\Shared;
20

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

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

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

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

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

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

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

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

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

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

122
        return $result;
249✔
123
    }
124

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

139
        if (!$this->usePclzip) {
249✔
140
            $zip = new \ZipArchive();
248✔
141

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

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

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

160
        return $result;
249✔
161
    }
162

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

181
        return true;
250✔
182
    }
183

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

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

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

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

226
        return $contents;
10✔
227
    }
228

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

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

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

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

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

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

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

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

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

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

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

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

309
        // Remove temp file
310
        @unlink($this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename']);
1✔
311

312
        return $res != 0;
1✔
313
    }
314

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

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

334
            return $result > 0;
1✔
335
        }
336

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

349
        return true;
1✔
350
    }
351

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

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

377
        return $contents;
1✔
378
    }
379

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

398
        return false;
1✔
399
    }
400

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

420
                break;
1✔
421
            }
422
        }
423

424
        return ($listIndex > -1) ? $listIndex : false;
1✔
425
    }
426
}
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