Test Setup Failed
Push — master ( 0ccb08...acf878 )
by Bingo
03:37
created

ZipArchive::pclzipLocateName()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 11
c 1
b 0
f 0
nc 6
nop 1
dl 0
loc 18
rs 9.6111
1
<?php
2
3
namespace PhpDocxTemplate\Zip;
4
5
class ZipArchive
6
{
7
    /** @const int Flags for open method */
8
    public const CREATE = 1; // Emulate \ZipArchive::CREATE
9
    public const OVERWRITE = 8; // Emulate \ZipArchive::OVERWRITE
10
11
    /**
12
     * Number of files (emulate ZipArchive::$numFiles)
13
     *
14
     * @var int
15
     */
16
    public $numFiles = 0;
17
18
    /**
19
     * Archive filename (emulate ZipArchive::$filename)
20
     *
21
     * @var string
22
     */
23
    public $filename;
24
25
    /**
26
     * Temporary storage directory
27
     *
28
     * @var string
29
     */
30
    private $tempDir;
31
32
    /**
33
     * Internal zip archive object
34
     *
35
     * @var \ZipArchive|\PclZip
36
     */
37
    private $zip;
38
39
    /**
40
     * Use PCLZip (default behaviour)
41
     *
42
     * @var bool
43
     */
44
    private $usePclzip = true;
45
46
    /**
47
     * Create new instance
48
     */
49
    public function __construct()
50
    {
51
        $this->usePclzip = (Settings::getZipClass() != 'ZipArchive');
52
        if ($this->usePclzip) {
53
            if (!defined('PCLZIP_TEMPORARY_DIR')) {
54
                define('PCLZIP_TEMPORARY_DIR', Settings::getTempDir() . '/');
55
            }
56
            require_once 'PCLZip/pclzip.lib.php';
57
        }
58
    }
59
60
    /**
61
     * Catch function calls: pass to ZipArchive or PCLZip
62
     *
63
     * `call_user_func_array` can only used for public function, hence the `public` in all `pcl...` methods
64
     *
65
     * @param mixed $function
66
     * @param mixed $args
67
     * @return mixed
68
     */
69
    public function __call($function, $args)
70
    {
71
        // Set object and function
72
        $zipFunction = $function;
73
        if (!$this->usePclzip) {
74
            $zipObject = $this->zip;
75
        } else {
76
            $zipObject = $this;
77
            $zipFunction = "pclzip{$zipFunction}";
78
        }
79
80
        // Run function
81
        $result = false;
82
        if (method_exists($zipObject, $zipFunction)) {
83
            $result = @call_user_func_array(array($zipObject, $zipFunction), $args);
84
        }
85
86
        return $result;
87
    }
88
89
    /**
90
     * Open a new zip archive
91
     *
92
     * @param string $filename The file name of the ZIP archive to open
93
     * @param int $flags The mode to use to open the archive
94
     * @return bool
95
     */
96
    public function open(string $filename, ?int $flags = null): bool
97
    {
98
        $result = true;
99
        $this->filename = $filename;
100
        $this->tempDir = Settings::getTempDir();
101
102
        if (!$this->usePclzip) {
103
            $zip = new \ZipArchive();
104
            $result = $zip->open($this->filename, $flags);
105
106
            // Scrutizer will report the property numFiles does not exist
107
            // See https://github.com/scrutinizer-ci/php-analyzer/issues/190
108
            $this->numFiles = $zip->numFiles;
109
        } else {
110
            $zip = new \PclZip($this->filename);
111
            $zipContent = $zip->listContent();
112
            $this->numFiles = is_array($zipContent) ? count($zipContent) : 0;
0 ignored issues
show
introduced by
The condition is_array($zipContent) is always false.
Loading history...
113
        }
114
        $this->zip = $zip;
115
116
        return $result;
117
    }
118
119
    /**
120
     * Close the active archive
121
     *
122
     * @throws \Exception
123
     *
124
     * @return bool
125
     *
126
     * @codeCoverageIgnore Can't find any test case. Uncomment when found.
127
     */
128
    public function close(): bool
129
    {
130
        if (!$this->usePclzip) {
131
            if ($this->zip->close() === false) {
0 ignored issues
show
Bug introduced by
The method close() does not exist on PclZip. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

131
            if ($this->zip->/** @scrutinizer ignore-call */ close() === false) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
132
                throw new \Exception("Could not close zip file {$this->filename}: ");
133
            }
134
        }
135
136
        return true;
137
    }
138
139
    /**
140
     * Extract the archive contents (emulate \ZipArchive)
141
     *
142
     * @param string $destination
143
     * @param mixed $entries
144
     * @return bool
145
     */
146
    public function extractTo(string $destination, $entries = null): bool
147
    {
148
        if (!is_dir($destination)) {
149
            return false;
150
        }
151
152
        if (!$this->usePclzip) {
153
            return $this->zip->extractTo($destination, $entries);
0 ignored issues
show
Bug introduced by
The method extractTo() does not exist on PclZip. Did you maybe mean extract()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

153
            return $this->zip->/** @scrutinizer ignore-call */ extractTo($destination, $entries);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
154
        }
155
156
        return $this->pclzipExtractTo($destination, $entries);
157
    }
158
159
    /**
160
     * Extract file from archive by given file name (emulate \ZipArchive)
161
     *
162
     * @param  string $filename Filename for the file in zip archive
163
     * @return string $contents File string contents
164
     */
165
    public function getFromName(string $filename): string
166
    {
167
        if (!$this->usePclzip) {
168
            $contents = $this->zip->getFromName($filename);
0 ignored issues
show
Bug introduced by
The method getFromName() does not exist on PclZip. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

168
            /** @scrutinizer ignore-call */ 
169
            $contents = $this->zip->getFromName($filename);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
169
            if ($contents === false) {
170
                $filename = substr($filename, 1);
171
                $contents = $this->zip->getFromName($filename);
172
            }
173
        } else {
174
            $contents = $this->pclzipGetFromName($filename);
175
        }
176
177
        return $contents;
178
    }
179
180
    /**
181
     * Add a new file to the zip archive (emulate \ZipArchive)
182
     *
183
     * @param string $filename Directory/Name of the file to add to the zip archive
184
     * @param string $localname Directory/Name of the file added to the zip
185
     * @return bool
186
     */
187
    public function pclzipAddFile(string $filename, ?string $localname = null): bool
188
    {
189
        /** @var \PclZip $zip Type hint */
190
        $zip = $this->zip;
191
192
        // Bugfix GH-261 https://github.com/PHPOffice/PHPWord/pull/261
193
        $realpathFilename = realpath($filename);
194
        if ($realpathFilename !== false) {
195
            $filename = $realpathFilename;
196
        }
197
198
        $filenameParts = pathinfo($filename);
199
        $localnameParts = pathinfo($localname);
0 ignored issues
show
Bug introduced by
It seems like $localname can also be of type null; however, parameter $path of pathinfo() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

199
        $localnameParts = pathinfo(/** @scrutinizer ignore-type */ $localname);
Loading history...
200
201
        // To Rename the file while adding it to the zip we
202
        //   need to create a temp file with the correct name
203
        $tempFile = false;
204
        if ($filenameParts['basename'] != $localnameParts['basename']) {
205
            $tempFile = true; // temp file created
206
            $temppath = $this->tempDir . DIRECTORY_SEPARATOR . $localnameParts['basename'];
207
            copy($filename, $temppath);
208
            $filename = $temppath;
209
            $filenameParts = pathinfo($temppath);
210
        }
211
212
        $pathRemoved = $filenameParts['dirname'];
213
        $pathAdded = $localnameParts['dirname'];
214
215
        if (!$this->usePclzip) {
216
            $pathAdded = $pathAdded . '/' . ltrim(str_replace('\\', '/', substr($filename, strlen($pathRemoved))), '/');
217
            //$res = $zip->addFile($filename, $pathAdded);
218
            $res = $zip->addFromString($pathAdded, file_get_contents($filename));       // addFile can't use subfolders in some cases
0 ignored issues
show
Bug introduced by
The method addFromString() does not exist on PclZip. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

218
            /** @scrutinizer ignore-call */ 
219
            $res = $zip->addFromString($pathAdded, file_get_contents($filename));       // addFile can't use subfolders in some cases

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
219
        } else {
220
            $res = $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $pathRemoved, PCLZIP_OPT_ADD_PATH, $pathAdded);
221
        }
222
223
        if ($tempFile) {
224
            // Remove temp file, if created
225
            unlink($this->tempDir . DIRECTORY_SEPARATOR . $localnameParts['basename']);
226
        }
227
228
        return $res != 0;
229
    }
230
231
    /**
232
     * Add a new file to the zip archive from a string of raw data (emulate \ZipArchive)
233
     *
234
     * @param string $localname Directory/Name of the file to add to the zip archive
235
     * @param string $contents String of data to add to the zip archive
236
     * @return bool
237
     */
238
    public function pclzipAddFromString(string $localname, string $contents): bool
239
    {
240
        /** @var \PclZip $zip Type hint */
241
        $zip = $this->zip;
242
        $filenameParts = pathinfo($localname);
243
244
        // Write $contents to a temp file
245
        $handle = fopen($this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename'], 'wb');
246
        fwrite($handle, $contents);
247
        fclose($handle);
248
249
        // Add temp file to zip
250
        $filename = $this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename'];
251
        $pathRemoved = $this->tempDir;
252
        $pathAdded = $filenameParts['dirname'];
253
254
        $res = $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $pathRemoved, PCLZIP_OPT_ADD_PATH, $pathAdded);
255
256
        // Remove temp file
257
        @unlink($this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename']);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

257
        /** @scrutinizer ignore-unhandled */ @unlink($this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename']);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
258
259
        return $res != 0;
260
    }
261
262
    /**
263
     * Extract the archive contents (emulate \ZipArchive)
264
     *
265
     * @param string $destination
266
     * @param mixed $entries
267
     * @return bool
268
     */
269
    public function pclzipExtractTo(string $destination, $entries = null): bool
270
    {
271
        /** @var \PclZip $zip Type hint */
272
        $zip = $this->zip;
273
274
        // Extract all files
275
        if (is_null($entries)) {
276
            $result = $zip->extract(PCLZIP_OPT_PATH, $destination);
277
278
            return $result > 0;
279
        }
280
281
        // Extract by entries
282
        if (!is_array($entries)) {
283
            $entries = array($entries);
284
        }
285
        foreach ($entries as $entry) {
286
            $entryIndex = $this->locateName($entry);
0 ignored issues
show
Bug introduced by
The method locateName() does not exist on PhpDocxTemplate\Zip\ZipArchive. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

286
            /** @scrutinizer ignore-call */ 
287
            $entryIndex = $this->locateName($entry);
Loading history...
287
            $result = $zip->extractByIndex($entryIndex, PCLZIP_OPT_PATH, $destination);
288
            if ($result <= 0) {
289
                return false;
290
            }
291
        }
292
293
        return true;
294
    }
295
296
    /**
297
     * Extract file from archive by given file name (emulate \ZipArchive)
298
     *
299
     * @param  string $filename Filename for the file in zip archive
300
     * @return string $contents File string contents
301
     */
302
    public function pclzipGetFromName(string $filename): string
303
    {
304
        /** @var \PclZip $zip Type hint */
305
        $zip = $this->zip;
306
        $listIndex = $this->pclzipLocateName($filename);
307
        $contents = false;
308
309
        if ($listIndex !== false) {
0 ignored issues
show
introduced by
The condition $listIndex !== false is always true.
Loading history...
310
            $extracted = $zip->extractByIndex($listIndex, PCLZIP_OPT_EXTRACT_AS_STRING);
311
        } else {
312
            $filename = substr($filename, 1);
313
            $listIndex = $this->pclzipLocateName($filename);
314
            $extracted = $zip->extractByIndex($listIndex, PCLZIP_OPT_EXTRACT_AS_STRING);
315
        }
316
        if ((is_array($extracted)) && ($extracted != 0)) {
0 ignored issues
show
introduced by
The condition is_array($extracted) is always false.
Loading history...
317
            $contents = $extracted[0]['content'];
318
        }
319
320
        return $contents;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $contents returns the type false which is incompatible with the type-hinted return string.
Loading history...
321
    }
322
323
    /**
324
     * Returns the name of an entry using its index (emulate \ZipArchive)
325
     *
326
     * @param int $index
327
     * @return string|bool
328
     */
329
    public function pclzipGetNameIndex(int $index)
330
    {
331
        /** @var \PclZip $zip Type hint */
332
        $zip = $this->zip;
333
        $list = $zip->listContent();
334
        if (isset($list[$index])) {
335
            return $list[$index]['filename'];
336
        }
337
338
        return false;
339
    }
340
341
    /**
342
     * Returns the index of the entry in the archive (emulate \ZipArchive)
343
     *
344
     * @param string $filename Filename for the file in zip archive
345
     * @return int
346
     */
347
    public function pclzipLocateName(string $filename): int
348
    {
349
        /** @var \PclZip $zip Type hint */
350
        $zip = $this->zip;
351
        $list = $zip->listContent();
352
        $listCount = count($list);
0 ignored issues
show
Bug introduced by
$list of type integer is incompatible with the type Countable|array expected by parameter $value of count(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

352
        $listCount = count(/** @scrutinizer ignore-type */ $list);
Loading history...
353
        $listIndex = -1;
354
        for ($i = 0; $i < $listCount; ++$i) {
355
            if (
356
                strtolower($list[$i]['filename']) == strtolower($filename) ||
357
                strtolower($list[$i]['stored_filename']) == strtolower($filename)
358
            ) {
359
                $listIndex = $i;
360
                break;
361
            }
362
        }
363
364
        return ($listIndex > -1) ? $listIndex : false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $listIndex > -1 ? $listIndex : false could return the type false which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
365
    }
366
}
367