1 | <?php |
||
2 | namespace wapmorgan\UnifiedArchive\Drivers; |
||
3 | |||
4 | use Exception; |
||
5 | use wapmorgan\UnifiedArchive\Abilities; |
||
6 | use wapmorgan\UnifiedArchive\Archive7z; |
||
7 | use wapmorgan\UnifiedArchive\ArchiveEntry; |
||
8 | use wapmorgan\UnifiedArchive\ArchiveInformation; |
||
9 | use wapmorgan\UnifiedArchive\Drivers\Basic\BasicDriver; |
||
10 | use wapmorgan\UnifiedArchive\Drivers\Basic\BasicUtilityDriver; |
||
11 | use wapmorgan\UnifiedArchive\Exceptions\ArchiveCreationException; |
||
12 | use wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException; |
||
13 | use wapmorgan\UnifiedArchive\Exceptions\ArchiveModificationException; |
||
14 | use wapmorgan\UnifiedArchive\Exceptions\NonExistentArchiveFileException; |
||
15 | use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException; |
||
16 | use wapmorgan\UnifiedArchive\Formats; |
||
17 | |||
18 | class SevenZip extends BasicUtilityDriver |
||
19 | { |
||
20 | /** @var Archive7z */ |
||
21 | protected $sevenZip; |
||
22 | |||
23 | /** |
||
24 | * @var string |
||
25 | */ |
||
26 | protected $format; |
||
27 | |||
28 | 1 | const COMMENT_FILE = 'descript.ion'; |
|
29 | |||
30 | public static function isInstalled() |
||
31 | 1 | { |
|
32 | return class_exists('\Archive7z\Archive7z') && Archive7z::getBinaryVersion() !== false; |
||
33 | } |
||
34 | |||
35 | /** |
||
36 | * @inheritDoc |
||
37 | */ |
||
38 | public static function getInstallationInstruction() |
||
39 | { |
||
40 | if (!class_exists('\Archive7z\Archive7z')) |
||
41 | return 'install library [gemorroj/archive7z]: `composer require gemorroj/archive7z`' . "\n" |
||
42 | . ' and console program p7zip [7za]: `apt install p7zip-full` - depends on OS'; |
||
43 | |||
44 | if (Archive7z::getBinaryVersion() === false) |
||
45 | return 'install console program p7zip [7za]: `apt install p7zip-full` - depends on OS'; |
||
46 | |||
47 | return null; |
||
48 | } |
||
49 | |||
50 | /** |
||
51 | * @inheritDoc |
||
52 | */ |
||
53 | public static function getDescription() |
||
54 | { |
||
55 | return 'php-library and console program' |
||
56 | .(class_exists('\Archive7z\Archive7z') && ($version = Archive7z::getBinaryVersion()) !== false |
||
57 | ? ' ('.$version.')' |
||
58 | 3 | : null); |
|
59 | } |
||
60 | 3 | ||
61 | 3 | /** |
|
62 | * @return array |
||
63 | */ |
||
64 | public static function getFormats() |
||
65 | 3 | { |
|
66 | return [ |
||
67 | Formats::SEVEN_ZIP, |
||
68 | Formats::ZIP, |
||
69 | 3 | // Formats::RAR, |
|
70 | 3 | Formats::TAR, |
|
71 | 2 | // disabled |
|
72 | 2 | // Formats::TAR_GZIP, |
|
73 | // Formats::TAR_BZIP, |
||
74 | Formats::CAB, |
||
75 | 1 | Formats::ISO, |
|
76 | 1 | Formats::ARJ, |
|
77 | // Formats::LZMA, |
||
78 | Formats::UEFI, |
||
79 | Formats::GPT, |
||
80 | Formats::MBR, |
||
81 | Formats::MSI, |
||
82 | Formats::DMG, |
||
83 | Formats::RPM, |
||
84 | Formats::DEB, |
||
85 | Formats::UDF, |
||
86 | ]; |
||
87 | 3 | } |
|
88 | |||
89 | /** |
||
90 | * @param string $format |
||
91 | * @return array |
||
92 | * @throws \Archive7z\Exception |
||
93 | */ |
||
94 | public static function getFormatAbilities($format) |
||
95 | { |
||
96 | if (!static::isInstalled()) { |
||
97 | return []; |
||
98 | } |
||
99 | |||
100 | // in 4.0.0 version it was supporting only 7z |
||
101 | if (!Archive7z::supportsAllFormats() && $format !== Formats::SEVEN_ZIP) { |
||
102 | return []; |
||
103 | } |
||
104 | |||
105 | $abilities = [ |
||
106 | Abilities::OPEN, |
||
107 | Abilities::EXTRACT_CONTENT, |
||
108 | ]; |
||
109 | |||
110 | if (static::canRenameFiles()) { |
||
111 | if (in_array($format, [Formats::SEVEN_ZIP, Formats::RAR, Formats::ZIP], true)) { |
||
112 | $abilities[] = Abilities::OPEN_ENCRYPTED; |
||
113 | } |
||
114 | |||
115 | if (in_array($format, [Formats::ZIP, Formats::SEVEN_ZIP], true)) { |
||
116 | $abilities[] = Abilities::CREATE_ENCRYPTED; |
||
117 | } |
||
118 | |||
119 | 6 | if (in_array($format, [Formats::SEVEN_ZIP, |
|
120 | Formats::BZIP, |
||
121 | Formats::GZIP, |
||
122 | 6 | Formats::TAR, |
|
123 | 6 | Formats::LZMA, |
|
124 | 6 | Formats::ZIP], true)) { |
|
125 | 6 | $abilities[] = Abilities::CREATE; |
|
126 | $abilities[] = Abilities::APPEND; |
||
127 | $abilities[] = Abilities::DELETE; |
||
128 | } |
||
129 | 6 | ||
130 | if ($format === Formats::SEVEN_ZIP) { |
||
131 | $abilities[] = Abilities::GET_COMMENT; |
||
132 | $abilities[] = Abilities::SET_COMMENT; |
||
133 | } |
||
134 | 6 | ||
135 | } |
||
136 | 6 | return $abilities; |
|
137 | } |
||
138 | 6 | ||
139 | 6 | /** |
|
140 | * @inheritDoc |
||
141 | * @throws Exception |
||
142 | */ |
||
143 | 6 | public function __construct($archiveFileName, $format, $password = null) |
|
144 | 6 | { |
|
145 | parent::__construct($archiveFileName, $format); |
||
146 | 6 | try { |
|
147 | 6 | $this->format = $format; |
|
148 | $this->sevenZip = new Archive7z($archiveFileName, null, null); |
||
149 | 6 | if ($password !== null) |
|
150 | 6 | $this->sevenZip->setPassword($password); |
|
151 | } catch (\Archive7z\Exception $e) { |
||
152 | 6 | throw new Exception('Could not open 7Zip archive: '.$e->getMessage(), $e->getCode(), $e); |
|
153 | } |
||
154 | } |
||
155 | |||
156 | /** |
||
157 | * @return ArchiveInformation |
||
158 | */ |
||
159 | public function getArchiveInformation() |
||
160 | { |
||
161 | $information = new ArchiveInformation(); |
||
162 | |||
163 | foreach ($this->sevenZip->getEntries() as $entry) { |
||
164 | if ($entry->isDirectory()) { |
||
165 | continue; |
||
166 | } |
||
167 | |||
168 | if (!isset($can_get_unix_path)) |
||
169 | $can_get_unix_path = method_exists($entry, 'getUnixPath'); |
||
170 | |||
171 | $information->files[] = $can_get_unix_path |
||
172 | ? $entry->getUnixPath() |
||
173 | : str_replace('\\', '/', $entry->getPath()); |
||
174 | $information->compressedFilesSize += (int)$entry->getPackedSize(); |
||
175 | $information->uncompressedFilesSize += (int)$entry->getSize(); |
||
176 | } |
||
177 | return $information; |
||
178 | } |
||
179 | |||
180 | /** |
||
181 | * @return array |
||
182 | */ |
||
183 | public function getFileNames() |
||
184 | 1 | { |
|
185 | $files = []; |
||
186 | 1 | foreach ($this->sevenZip->getEntries() as $entry) { |
|
187 | 1 | if ($entry->isDirectory()) |
|
188 | 1 | continue; |
|
189 | $files[] = $entry->getPath(); |
||
190 | } |
||
191 | return $files; |
||
192 | } |
||
193 | |||
194 | /** |
||
195 | * @param string $fileName |
||
196 | 1 | * |
|
197 | * @return bool |
||
198 | 1 | */ |
|
199 | 1 | public function isFileExists($fileName) |
|
200 | { |
||
201 | return $this->sevenZip->getEntry($fileName) !== null; |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * @param string $fileName |
||
206 | * |
||
207 | 1 | * @return ArchiveEntry|false |
|
208 | */ |
||
209 | 1 | public function getFileData($fileName) |
|
210 | 1 | { |
|
211 | $entry = $this->sevenZip->getEntry($fileName); |
||
212 | return new ArchiveEntry( |
||
213 | $fileName, |
||
214 | $entry->getPackedSize(), |
||
215 | $entry->getSize(), |
||
216 | strtotime($entry->getModified()), |
||
217 | $entry->getSize() !== $entry->getPackedSize(), |
||
218 | $entry->getComment(), |
||
219 | $this->format === Formats::ZIP ? $entry->getCrc() : null |
||
220 | ); |
||
221 | } |
||
222 | |||
223 | /** |
||
224 | * @param string $fileName |
||
225 | * |
||
226 | * @return string|false |
||
227 | * @throws NonExistentArchiveFileException |
||
228 | */ |
||
229 | public function getFileContent($fileName) |
||
230 | { |
||
231 | $entry = $this->sevenZip->getEntry($fileName); |
||
232 | if ($entry === null) { |
||
233 | throw new NonExistentArchiveFileException('File ' . $fileName . ' does not exist'); |
||
234 | } |
||
235 | return $entry->getContent(); |
||
236 | } |
||
237 | |||
238 | /** |
||
239 | * @param string $fileName |
||
240 | * |
||
241 | * @return bool|resource|string |
||
242 | */ |
||
243 | public function getFileStream($fileName) |
||
244 | { |
||
245 | $entry = $this->sevenZip->getEntry($fileName); |
||
246 | return self::wrapStringInStream($entry->getContent()); |
||
247 | } |
||
248 | |||
249 | /** |
||
250 | * @param string $outputFolder |
||
251 | * @param array $files |
||
252 | * @return int |
||
253 | * @throws ArchiveExtractionException |
||
254 | */ |
||
255 | public function extractFiles($outputFolder, array $files) |
||
256 | { |
||
257 | $count = 0; |
||
258 | try { |
||
259 | $this->sevenZip->setOutputDirectory($outputFolder); |
||
260 | |||
261 | foreach ($files as $file) { |
||
262 | $this->sevenZip->extractEntry($file); |
||
263 | $count++; |
||
264 | } |
||
265 | return $count; |
||
266 | } catch (Exception $e) { |
||
267 | throw new ArchiveExtractionException('Could not extract archive: '.$e->getMessage(), $e->getCode(), $e); |
||
268 | } |
||
269 | } |
||
270 | |||
271 | /** |
||
272 | * @param string $outputFolder |
||
273 | * |
||
274 | * @return bool |
||
275 | * @throws ArchiveExtractionException |
||
276 | */ |
||
277 | public function extractArchive($outputFolder) |
||
278 | { |
||
279 | try { |
||
280 | $this->sevenZip->setOutputDirectory($outputFolder); |
||
281 | $this->sevenZip->extract(); |
||
282 | return true; |
||
0 ignored issues
–
show
|
|||
283 | } catch (Exception $e) { |
||
284 | throw new ArchiveExtractionException('Could not extract archive: '.$e->getMessage(), $e->getCode(), $e); |
||
285 | } |
||
286 | } |
||
287 | |||
288 | /** |
||
289 | * @param array $files |
||
290 | * @return int Number of deleted files |
||
291 | * @throws ArchiveModificationException |
||
292 | */ |
||
293 | public function deleteFiles(array $files) |
||
294 | { |
||
295 | $count = 0; |
||
296 | try { |
||
297 | foreach ($files as $file) { |
||
298 | $this->sevenZip->delEntry($file); |
||
299 | $count++; |
||
300 | } |
||
301 | return $count; |
||
302 | } catch (Exception $e) { |
||
303 | throw new ArchiveModificationException('Could not modify archive: '.$e->getMessage(), $e->getCode(), $e); |
||
304 | } |
||
305 | } |
||
306 | |||
307 | /** |
||
308 | * @param array $files |
||
309 | * |
||
310 | * @return int |
||
311 | * @throws ArchiveModificationException |
||
312 | */ |
||
313 | public function addFiles(array $files) |
||
314 | { |
||
315 | $added_files = 0; |
||
316 | try { |
||
317 | foreach ($files as $localName => $filename) { |
||
318 | if (!is_null($filename)) { |
||
319 | $this->sevenZip->addEntry($filename); |
||
320 | $this->sevenZip->renameEntry($filename, $localName); |
||
321 | $added_files++; |
||
322 | } |
||
323 | } |
||
324 | return $added_files; |
||
325 | } catch (Exception $e) { |
||
326 | throw new ArchiveModificationException('Could not modify archive: '.$e->getMessage(), $e->getCode(), $e); |
||
327 | } |
||
328 | } |
||
329 | |||
330 | /** |
||
331 | * @param string $inArchiveName |
||
332 | * @param string $content |
||
333 | * @return bool|void |
||
334 | * @throws ArchiveModificationException |
||
335 | * @throws \Archive7z\Exception |
||
336 | */ |
||
337 | public function addFileFromString($inArchiveName, $content) |
||
338 | { |
||
339 | $tmp_file = tempnam(sys_get_temp_dir(), 'ua'); |
||
340 | if (!$tmp_file) |
||
341 | throw new ArchiveModificationException('Could not create temporarily file'); |
||
342 | |||
343 | file_put_contents($tmp_file, $content); |
||
344 | $this->sevenZip->addEntry($tmp_file, true); |
||
345 | $this->sevenZip->renameEntry($tmp_file, $inArchiveName); |
||
346 | unlink($tmp_file); |
||
347 | return true; |
||
348 | } |
||
349 | |||
350 | /** |
||
351 | * @param array $files |
||
352 | * @param string $archiveFileName |
||
353 | * @param int $archiveFormat |
||
354 | * @param int $compressionLevel |
||
355 | * @param null $password |
||
356 | * @param $fileProgressCallable |
||
357 | * @return int |
||
358 | * @throws ArchiveCreationException |
||
359 | * @throws UnsupportedOperationException |
||
360 | */ |
||
361 | public static function createArchive( |
||
362 | array $files, |
||
363 | $archiveFileName, |
||
364 | $archiveFormat, |
||
365 | $compressionLevel = self::COMPRESSION_AVERAGE, |
||
366 | $password = null, |
||
367 | $fileProgressCallable = null |
||
368 | ) |
||
369 | { |
||
370 | static $compressionLevelMap = [ |
||
371 | self::COMPRESSION_NONE => 0, |
||
372 | self::COMPRESSION_WEAK => 2, |
||
373 | self::COMPRESSION_AVERAGE => 4, |
||
374 | self::COMPRESSION_STRONG => 7, |
||
375 | self::COMPRESSION_MAXIMUM => 9, |
||
376 | ]; |
||
377 | |||
378 | if ($password !== null && !static::canEncrypt($archiveFormat)) { |
||
379 | throw new UnsupportedOperationException('SevenZip could not encrypt an archive of '.$archiveFormat.' format'); |
||
380 | } |
||
381 | |||
382 | if ($fileProgressCallable !== null && !is_callable($fileProgressCallable)) { |
||
383 | throw new ArchiveCreationException('File progress callable is not callable'); |
||
384 | } |
||
385 | |||
386 | try { |
||
387 | $current_file = 0; |
||
388 | $total_files = count($files); |
||
389 | |||
390 | $seven_zip = new Archive7z($archiveFileName); |
||
391 | if ($password !== null) |
||
392 | $seven_zip->setPassword($password); |
||
393 | $seven_zip->setCompressionLevel($compressionLevelMap[$compressionLevel]); |
||
394 | foreach ($files as $archiveName => $localName) { |
||
395 | if ($localName !== null) { |
||
396 | $seven_zip->addEntry($localName, true); |
||
397 | $seven_zip->renameEntry($localName, $archiveName); |
||
398 | } |
||
399 | if ($fileProgressCallable !== null) { |
||
400 | call_user_func_array($fileProgressCallable, [$current_file++, $total_files, $localName, $archiveName]); |
||
401 | } |
||
402 | } |
||
403 | unset($seven_zip); |
||
404 | } catch (Exception $e) { |
||
405 | throw new ArchiveCreationException('Could not create archive: '.$e->getMessage(), $e->getCode(), $e); |
||
406 | } |
||
407 | return count($files); |
||
408 | } |
||
409 | |||
410 | /** |
||
411 | * @return bool |
||
412 | * @throws \Archive7z\Exception |
||
413 | */ |
||
414 | protected static function canRenameFiles() |
||
415 | { |
||
416 | $version = Archive7z::getBinaryVersion(); |
||
417 | return $version !== false && version_compare('9.30', $version, '<='); |
||
418 | } |
||
419 | |||
420 | /** |
||
421 | * @param $format |
||
422 | * @return bool |
||
423 | * @throws \Archive7z\Exception |
||
424 | */ |
||
425 | public static function canEncrypt($format) |
||
426 | { |
||
427 | return in_array($format, [Formats::ZIP, Formats::SEVEN_ZIP]) && self::canRenameFiles(); |
||
428 | } |
||
429 | |||
430 | /** |
||
431 | * @return string|null |
||
432 | */ |
||
433 | public function getComment() |
||
434 | { |
||
435 | if ($this->format !== Formats::SEVEN_ZIP) { |
||
436 | return null; |
||
437 | } |
||
438 | try { |
||
439 | return $this->getFileContent(static::COMMENT_FILE); |
||
0 ignored issues
–
show
The expression
return $this->getFileCon...t(static::COMMENT_FILE) could also return false which is incompatible with the documented return type null|string . Did you maybe forget to handle an error condition?
If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.
Loading history...
|
|||
440 | } catch (NonExistentArchiveFileException $e) { |
||
441 | return null; |
||
442 | } |
||
443 | } |
||
444 | |||
445 | /** |
||
446 | * @param string|null $comment |
||
447 | * @return null |
||
448 | * @throws ArchiveModificationException |
||
449 | * @throws \Archive7z\Exception |
||
450 | */ |
||
451 | public function setComment($comment) |
||
452 | { |
||
453 | if ($this->format !== Formats::SEVEN_ZIP) { |
||
454 | return null; |
||
455 | } |
||
456 | $this->addFileFromString(static::COMMENT_FILE, $comment); |
||
457 | } |
||
458 | } |
||
459 |
In the issue above, the returned value is violating the contract defined by the mentioned interface.
Let's take a look at an example: