1 | <?php |
||||
2 | namespace wapmorgan\UnifiedArchive\Drivers; |
||||
3 | |||||
4 | use Exception; |
||||
5 | use FilesystemIterator; |
||||
6 | use Phar; |
||||
7 | use PharData; |
||||
8 | use PharFileInfo; |
||||
9 | use RecursiveIteratorIterator; |
||||
10 | use wapmorgan\UnifiedArchive\Abilities; |
||||
11 | use wapmorgan\UnifiedArchive\ArchiveEntry; |
||||
12 | use wapmorgan\UnifiedArchive\ArchiveInformation; |
||||
13 | use wapmorgan\UnifiedArchive\Drivers\Basic\BasicExtensionDriver; |
||||
14 | use wapmorgan\UnifiedArchive\Exceptions\ArchiveCreationException; |
||||
15 | use wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException; |
||||
16 | use wapmorgan\UnifiedArchive\Exceptions\ArchiveModificationException; |
||||
17 | use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException; |
||||
18 | use wapmorgan\UnifiedArchive\Formats; |
||||
19 | |||||
20 | class TarByPhar extends BasicExtensionDriver |
||||
21 | { |
||||
22 | const EXTENSION_NAME = 'phar'; |
||||
23 | |||||
24 | public static $disabled = false; |
||||
25 | |||||
26 | /** |
||||
27 | * @var PharData |
||||
28 | */ |
||||
29 | protected $tar; |
||||
30 | |||||
31 | /** |
||||
32 | * @var float |
||||
33 | */ |
||||
34 | protected $compressRatio; |
||||
35 | |||||
36 | protected $pureFilesNumber; |
||||
37 | |||||
38 | /** |
||||
39 | 1 | * @var int Flags for iterator |
|||
40 | */ |
||||
41 | const PHAR_FLAGS = FilesystemIterator::UNIX_PATHS; |
||||
42 | 1 | ||||
43 | /** |
||||
44 | * @inheritDoc |
||||
45 | */ |
||||
46 | public static function getInstallationInstruction() |
||||
47 | { |
||||
48 | return 'install `phar` extension and optionally php-extensions (zlib, bz2)'; |
||||
49 | } |
||||
50 | |||||
51 | /** |
||||
52 | * @inheritDoc |
||||
53 | 3 | */ |
|||
54 | public static function getDescription() |
||||
55 | 3 | { |
|||
56 | return 'adapter for ext-phar'; |
||||
57 | 3 | } |
|||
58 | |||||
59 | 1 | /** |
|||
60 | 2 | * @return array |
|||
61 | 1 | */ |
|||
62 | 1 | public static function getFormats() |
|||
63 | 1 | { |
|||
64 | return [ |
||||
65 | Formats::TAR, |
||||
66 | Formats::TAR_GZIP, |
||||
67 | Formats::TAR_BZIP, |
||||
68 | Formats::ZIP, |
||||
69 | ]; |
||||
70 | } |
||||
71 | |||||
72 | /** |
||||
73 | * @param $format |
||||
74 | * @return array |
||||
75 | */ |
||||
76 | public static function getFormatAbilities($format) |
||||
77 | { |
||||
78 | if (static::$disabled || !static::isInstalled()) { |
||||
79 | return []; |
||||
80 | } |
||||
81 | |||||
82 | $abilities = [ |
||||
83 | Abilities::OPEN, |
||||
84 | Abilities::EXTRACT_CONTENT, |
||||
85 | Abilities::STREAM_CONTENT, |
||||
86 | Abilities::APPEND, |
||||
87 | Abilities::DELETE, |
||||
88 | 10 | Abilities::CREATE, |
|||
89 | ]; |
||||
90 | 10 | ||||
91 | 10 | switch ($format) { |
|||
92 | 10 | case Formats::TAR: |
|||
93 | case Formats::ZIP: |
||||
94 | return $abilities; |
||||
95 | |||||
96 | case Formats::TAR_GZIP: |
||||
97 | 10 | return extension_loaded('zlib') |
|||
98 | ? $abilities |
||||
99 | 10 | : []; |
|||
100 | 10 | case Formats::TAR_BZIP: |
|||
101 | return extension_loaded('bz2') |
||||
102 | ? $abilities |
||||
103 | : []; |
||||
104 | } |
||||
105 | 10 | } |
|||
106 | |||||
107 | 10 | /** |
|||
108 | 10 | * @inheritDoc |
|||
109 | */ |
||||
110 | public function __construct($archiveFileName, $format, $password = null) |
||||
111 | { |
||||
112 | parent::__construct($archiveFileName, $format); |
||||
113 | 10 | $this->open(); |
|||
114 | 10 | } |
|||
115 | 10 | ||||
116 | 10 | /** |
|||
117 | * |
||||
118 | 10 | */ |
|||
119 | protected function open() |
||||
120 | { |
||||
121 | $this->tar = new PharData($this->fileName, self::PHAR_FLAGS); |
||||
122 | } |
||||
123 | |||||
124 | /** |
||||
125 | * @inheritDoc |
||||
126 | */ |
||||
127 | public function getArchiveInformation() |
||||
128 | { |
||||
129 | $information = new ArchiveInformation(); |
||||
130 | $stream_path_length = strlen('phar://'.$this->fileName.'/'); |
||||
131 | $information->compressedFilesSize = filesize($this->fileName); |
||||
132 | /** |
||||
133 | * @var string $i |
||||
134 | * @var PharFileInfo $file |
||||
135 | */ |
||||
136 | foreach (new RecursiveIteratorIterator($this->tar) as $i => $file) { |
||||
137 | $information->files[] = substr($file->getPathname(), $stream_path_length); |
||||
138 | $information->uncompressedFilesSize += $file->getSize(); |
||||
139 | } |
||||
140 | $this->compressRatio = $information->compressedFilesSize > 0 |
||||
141 | ? $information->uncompressedFilesSize / $information->compressedFilesSize |
||||
142 | : 0; |
||||
143 | $this->pureFilesNumber = count($information->files); |
||||
144 | return $information; |
||||
145 | } |
||||
146 | |||||
147 | /** |
||||
148 | * @inheritDoc |
||||
149 | */ |
||||
150 | public function getFileNames() |
||||
151 | { |
||||
152 | 3 | $files = []; |
|||
153 | |||||
154 | $stream_path_length = strlen('phar://'.$this->fileName.'/'); |
||||
155 | 3 | foreach (new RecursiveIteratorIterator($this->tar) as $i => $file) { |
|||
156 | 3 | $files[] = substr($file->getPathname(), $stream_path_length); |
|||
157 | 3 | } |
|||
158 | |||||
159 | return $files; |
||||
160 | } |
||||
161 | |||||
162 | /** |
||||
163 | 4 | * @inheritDoc |
|||
164 | */ |
||||
165 | 4 | public function isFileExists($fileName) |
|||
166 | { |
||||
167 | try { |
||||
168 | $this->tar->offsetGet($fileName); |
||||
169 | return true; |
||||
170 | } catch (Exception $e) { |
||||
171 | 3 | return false; |
|||
172 | } |
||||
173 | 3 | } |
|||
174 | |||||
175 | /** |
||||
176 | * @inheritDoc |
||||
177 | */ |
||||
178 | public function getFileData($fileName) |
||||
179 | { |
||||
180 | /** @var \PharFileInfo $entry_info */ |
||||
181 | $entry_info = $this->tar->offsetGet($fileName); |
||||
182 | return new ArchiveEntry( |
||||
183 | $fileName, |
||||
184 | ( |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
185 | // $entry_info->getCompressedSize() > $entry_info->getSize() |
||||
186 | $this->compressRatio > 1 |
||||
187 | ? floor($entry_info->getSize() / $this->compressRatio) |
||||
188 | : ( |
||||
189 | $entry_info->getCompressedSize() > 0 |
||||
190 | ? $entry_info->getCompressedSize() |
||||
191 | : 0 |
||||
192 | ) |
||||
193 | ), //$entry_info->getCompressedSize(), |
||||
194 | $entry_info->getSize(), |
||||
195 | $entry_info->getMTime(), |
||||
196 | $entry_info->isCompressed()); |
||||
197 | } |
||||
198 | |||||
199 | /** |
||||
200 | * @inheritDoc |
||||
201 | */ |
||||
202 | public function getFileContent($fileName) |
||||
203 | { |
||||
204 | 1 | return $this->tar->offsetGet($fileName)->getContent(); |
|||
205 | } |
||||
206 | 1 | ||||
207 | /** |
||||
208 | 1 | * @inheritDoc |
|||
209 | 1 | */ |
|||
210 | 1 | public function getFileStream($fileName) |
|||
211 | { |
||||
212 | return fopen('phar://'.$this->fileName . '/' . $fileName, 'rb'); |
||||
213 | 1 | } |
|||
214 | 1 | ||||
215 | /** |
||||
216 | 1 | * @inheritDoc |
|||
217 | */ |
||||
218 | public function extractFiles($outputFolder, array $files) |
||||
219 | { |
||||
220 | $result = $this->tar->extractTo($outputFolder, $files, true); |
||||
221 | if ($result === false) { |
||||
222 | 1 | throw new ArchiveExtractionException('Error when extracting from '.$this->fileName); |
|||
223 | } |
||||
224 | 1 | return count($files); |
|||
225 | } |
||||
226 | 1 | ||||
227 | 1 | /** |
|||
228 | * @inheritDoc |
||||
229 | */ |
||||
230 | 1 | public function extractArchive($outputFolder) |
|||
231 | 1 | { |
|||
232 | $result = $this->tar->extractTo($outputFolder, null, true); |
||||
233 | if ($result === false) { |
||||
234 | throw new ArchiveExtractionException('Error when extracting from '.$this->fileName); |
||||
235 | } |
||||
236 | |||||
237 | 1 | return $this->pureFilesNumber; |
|||
238 | } |
||||
239 | 1 | ||||
240 | 1 | /** |
|||
241 | * @inheritDoc |
||||
242 | */ |
||||
243 | public function deleteFiles(array $files) |
||||
244 | { |
||||
245 | $deleted = 0; |
||||
246 | |||||
247 | 1 | foreach ($files as $i => $file) { |
|||
248 | if ($this->tar->delete($file)) |
||||
249 | 1 | $deleted++; |
|||
250 | } |
||||
251 | |||||
252 | $this->tar = null; |
||||
253 | $this->open(); |
||||
254 | |||||
255 | return $deleted; |
||||
256 | 1 | } |
|||
257 | |||||
258 | 1 | /** |
|||
259 | * @inheritDoc |
||||
260 | */ |
||||
261 | public function addFiles(array $files) |
||||
262 | { |
||||
263 | $added = 0; |
||||
264 | try { |
||||
265 | 1 | foreach ($files as $localName => $filename) { |
|||
266 | if (is_null($filename)) { |
||||
267 | 1 | $this->tar->addEmptyDir($localName); |
|||
268 | } else { |
||||
269 | $this->tar->addFile($filename, $localName); |
||||
270 | $added++; |
||||
271 | } |
||||
272 | } |
||||
273 | } catch (Exception $e) { |
||||
274 | throw new ArchiveModificationException('Could not add file "'.$filename.'": '.$e->getMessage(), $e->getCode()); |
||||
275 | } |
||||
276 | $this->tar = null; |
||||
277 | // reopen to refresh files list properly |
||||
278 | $this->open(); |
||||
279 | return $added; |
||||
280 | 1 | } |
|||
281 | |||||
282 | 1 | /** |
|||
283 | * @param array $files |
||||
284 | * @param string $archiveFileName |
||||
285 | * @param int $archiveFormat |
||||
286 | 1 | * @param int $compressionLevel |
|||
287 | * @param null $password |
||||
0 ignored issues
–
show
|
|||||
288 | * @param $fileProgressCallable |
||||
289 | * @return int |
||||
290 | 1 | * @throws ArchiveCreationException |
|||
291 | 1 | * @throws UnsupportedOperationException |
|||
292 | */ |
||||
293 | public static function createArchive( |
||||
294 | 1 | array $files, |
|||
295 | $archiveFileName, |
||||
296 | 1 | $archiveFormat, |
|||
297 | 1 | $compressionLevel = self::COMPRESSION_AVERAGE, |
|||
298 | $password = null, |
||||
299 | $fileProgressCallable = null |
||||
300 | 1 | ) |
|||
301 | 1 | { |
|||
302 | if ($password !== null) { |
||||
0 ignored issues
–
show
|
|||||
303 | throw new UnsupportedOperationException('Driver (' . __CLASS__ . ') could not encrypt an archive'); |
||||
304 | } |
||||
305 | |||||
306 | 1 | if ($fileProgressCallable !== null && !is_callable($fileProgressCallable)) { |
|||
307 | throw new ArchiveCreationException('File progress callable is not callable'); |
||||
308 | } |
||||
309 | 1 | ||||
310 | if (preg_match('~^(.+)\.(tar\.(gz|bz2))$~i', $archiveFileName, $match)) { |
||||
311 | $ext = $match[2]; |
||||
312 | $basename = $match[1]; |
||||
313 | } else { |
||||
314 | $ext = pathinfo($archiveFileName, PATHINFO_EXTENSION); |
||||
315 | 1 | $basename = dirname($archiveFileName).'/'.basename($archiveFileName, '.'.$ext); |
|||
0 ignored issues
–
show
Are you sure
$ext of type array|string can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
316 | 1 | } |
|||
317 | 1 | ||||
318 | $compression = null; |
||||
319 | switch ($ext) { |
||||
320 | case 'tar.gz': |
||||
321 | 1 | case 'tgz': |
|||
322 | 1 | $compression = Phar::GZ; |
|||
323 | 1 | break; |
|||
324 | 1 | case 'tar.bz2': |
|||
325 | 1 | case 'tbz2': |
|||
326 | $compression = Phar::BZ2; |
||||
327 | break; |
||||
328 | } |
||||
329 | 1 | ||||
330 | 1 | $destination_file = $basename . '.' . ($archiveFormat === Formats::ZIP ? 'zip' : 'tar'); |
|||
0 ignored issues
–
show
|
|||||
331 | // if compression used and there is .tar archive with that's name, |
||||
332 | // use temp file |
||||
333 | if ($compression !== null && file_exists($basename . '.' . ($archiveFormat === Formats::ZIP ? 'zip' : 'tar'))) { |
||||
0 ignored issues
–
show
|
|||||
334 | $temp_basename = tempnam(sys_get_temp_dir(), 'tar-archive'); |
||||
335 | unlink($temp_basename); |
||||
336 | $destination_file = $temp_basename. '.' . ($archiveFormat === Formats::ZIP ? 'zip' : 'tar'); |
||||
0 ignored issues
–
show
|
|||||
337 | } |
||||
338 | |||||
339 | 1 | $tar = new PharData( |
|||
340 | $destination_file, |
||||
341 | 0, null, $archiveFormat === Formats::ZIP ? Phar::ZIP : Phar::TAR |
||||
0 ignored issues
–
show
|
|||||
342 | 1 | ); |
|||
343 | |||||
344 | try { |
||||
345 | $current_file = 0; |
||||
346 | 1 | $total_files = count($files); |
|||
347 | |||||
348 | foreach ($files as $localName => $filename) { |
||||
349 | 1 | if (is_null($filename)) { |
|||
350 | if (!in_array($localName, ['/', ''], true)) { |
||||
351 | if ($tar->addEmptyDir($localName) === false) { |
||||
0 ignored issues
–
show
Are you sure the usage of
$tar->addEmptyDir($localName) targeting Phar::addEmptyDir() seems to always return null.
This check looks for function or method calls that always return null and whose return value is used. class A
{
function getObject()
{
return null;
}
}
$a = new A();
if ($a->getObject()) {
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||
352 | throw new ArchiveCreationException('Error when adding directory '.$localName.' to archive'); |
||||
353 | } |
||||
354 | 1 | } |
|||
355 | } else { |
||||
356 | if ($tar->addFile($filename, $localName) === false) { |
||||
0 ignored issues
–
show
Are you sure the usage of
$tar->addFile($filename, $localName) targeting Phar::addFile() seems to always return null.
This check looks for function or method calls that always return null and whose return value is used. class A
{
function getObject()
{
return null;
}
}
$a = new A();
if ($a->getObject()) {
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||
357 | throw new ArchiveCreationException('Error when adding file '.$localName.' to archive'); |
||||
358 | 1 | } |
|||
359 | } |
||||
360 | if ($fileProgressCallable !== null) { |
||||
361 | call_user_func_array($fileProgressCallable, [$current_file++, $total_files, $filename, $localName]); |
||||
362 | } |
||||
363 | } |
||||
364 | } catch (Exception $e) { |
||||
365 | throw new ArchiveCreationException('Error when creating archive: '.$e->getMessage(), $e->getCode(), $e); |
||||
366 | } |
||||
367 | |||||
368 | switch ($compression) { |
||||
369 | case Phar::GZ: |
||||
370 | $tar->compress(Phar::GZ, $ext); |
||||
371 | break; |
||||
372 | case Phar::BZ2: |
||||
373 | $tar->compress(Phar::BZ2, $ext); |
||||
374 | break; |
||||
375 | } |
||||
376 | $tar = null; |
||||
0 ignored issues
–
show
|
|||||
377 | |||||
378 | // if compression used and original .tar file exist, clean it |
||||
379 | if ($compression !== null && file_exists($destination_file)) { |
||||
380 | unlink($destination_file); |
||||
381 | } |
||||
382 | |||||
383 | // it temp file was used, rename it to destination archive name |
||||
384 | if (isset($temp_basename)) { |
||||
385 | rename($temp_basename . '.' . $ext, $archiveFileName); |
||||
386 | } |
||||
387 | |||||
388 | return count($files); |
||||
389 | } |
||||
390 | |||||
391 | /** |
||||
392 | * @param string $inArchiveName |
||||
393 | * @param string $content |
||||
394 | * @return bool |
||||
395 | */ |
||||
396 | public function addFileFromString($inArchiveName, $content) |
||||
397 | { |
||||
398 | $this->tar->addFromString($inArchiveName, $content); |
||||
399 | return true; |
||||
400 | } |
||||
401 | } |
||||
402 |