ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
Unzip.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
26
30class Unzip
31{
32 use PathHelper;
33
34 protected const URI = 'uri';
36 public const DS_UNIX = "/";
37 public const DS_WIN = "\\";
38 public const BASE_DIR = '.';
39 protected \ZipArchive $zip;
40 protected bool $error_reading_zip = false;
41 protected string $path_to_zip;
42 private int $amount_of_entries = 0;
43
44 public function __construct(
45 protected UnzipOptions $options,
46 protected FileStream $stream
47 ) {
48 $this->path_to_zip = $this->stream->getMetadata(self::URI);
49 $this->zip = new \ZipArchive();
50 try {
51 $this->zip->open($this->path_to_zip, \ZipArchive::RDONLY);
52 $this->amount_of_entries = $this->zip->count();
53 } catch (\Throwable) {
54 $this->error_reading_zip = true;
55 }
56 }
57
61 protected function pathToStreamGenerator(): \Closure
62 {
63 return function (\Generator $paths): \Generator {
64 foreach ($paths as $path) {
65 $resource = $this->zip->getStream($path);
66
67 yield Streams::ofResource($resource);
68 }
69 };
70 }
71
75 public function getPaths(): \Generator
76 {
77 if (!$this->error_reading_zip) {
78 for ($i = 0, $i_max = $this->amount_of_entries; $i < $i_max; $i++) {
79 $path = $this->zip->getNameIndex($i, \ZipArchive::FL_UNCHANGED);
80 if ($this->isPathIgnored($path, $this->options)) {
81 continue;
82 }
83 yield $path;
84 }
85 }
86 }
87
91 public function getStreams(): \Generator
92 {
93 $paths_to_stream_generator = $this->pathToStreamGenerator();
94
95 if ($this->options->getDirectoryHandling() === ZipDirectoryHandling::FLAT_STRUCTURE) {
96 yield from $paths_to_stream_generator($this->getFiles());
97 } else {
98 yield from $paths_to_stream_generator($this->getPaths());
99 }
100 }
101
105 public function getFileStreams(): \Generator
106 {
107 $paths_to_stream_generator = $this->pathToStreamGenerator();
108
109 yield from $paths_to_stream_generator($this->getFiles());
110 }
111
112 public function getAmountOfDirectories(): int
113 {
114 return iterator_count($this->getDirectories());
115 }
116
122 public function getDirectories(): \Generator
123 {
124 $directories = [];
125 foreach ($this->getPaths() as $path) {
126 if (substr($path, -1) === self::DS_UNIX || substr($path, -1) === self::DS_WIN) {
127 $directories[] = $path;
128 continue;
129 }
130 if ((str_contains($path, self::DS_UNIX) || str_contains($path, self::DS_WIN))) {
131 $directories[] = dirname($path) . self::DIRECTORY_SEPARATOR;
132 }
133 }
134
135 $directories_with_parents = [];
136
137 foreach ($directories as $directory) {
138 $parent = dirname($directory) . self::DIRECTORY_SEPARATOR;
139 if ($parent !== self::BASE_DIR . self::DIRECTORY_SEPARATOR && !in_array($parent, $directories, true)) {
140 $directories_with_parents[] = $parent;
141 }
142 $directories_with_parents[] = $directory;
143 }
144
145 $directories_with_parents = array_unique($directories_with_parents);
146 sort($directories_with_parents);
147 yield from $directories_with_parents;
148 }
149
150 public function getAmountOfFiles(): int
151 {
152 return iterator_count($this->getFiles());
153 }
154
159 public function getFiles(): \Generator
160 {
161 $files = [];
162 foreach ($this->getPaths() as $path) {
163 if (substr($path, -1) !== self::DS_UNIX && substr($path, -1) !== self::DS_WIN) {
164 $files[] = $path;
165 }
166 }
167 sort($files);
168 yield from $files;
169 }
170
171 public function hasMultipleRootEntriesInZip(): bool
172 {
173 $amount = 0;
174 foreach ($this->getDirectories() as $zip_directory) {
175 $dirname = dirname($zip_directory);
176 if ($dirname === self::BASE_DIR) {
177 $amount++;
178 }
179 if ($amount > 1) {
180 return true;
181 }
182 }
183 foreach ($this->getFiles() as $zip_file) {
184 $dirname = dirname($zip_file);
185 if ($dirname === self::BASE_DIR) {
186 $amount++;
187 }
188 if ($amount > 1) {
189 return true;
190 }
191 }
192 return false;
193 }
194
195 public function extract(): bool
196 {
197 if ($this->error_reading_zip) {
198 return false;
199 }
200
201 $destination_path = $this->options->getZipOutputPath();
202 if ($destination_path === null) {
203 return false;
204 }
205
206 switch ($this->options->getDirectoryHandling()) {
207 case ZipDirectoryHandling::KEEP_STRUCTURE:
208 break;
209 case ZipDirectoryHandling::ENSURE_SINGLE_TOP_DIR:
210 // top directory with same name as the ZIP without suffix
211 $zip_path = $this->stream->getMetadata(self::URI);
212 $sufix = '.' . pathinfo((string) $zip_path, PATHINFO_EXTENSION);
213 $top_directory = basename((string) $zip_path, $sufix);
214
215 // first we check if the ZIP contains the top directory
216 $has_top_directory = true;
217 foreach ($this->getPaths() as $path) {
218 $has_top_directory = str_starts_with($path, $top_directory) && $has_top_directory;
219 }
220
221 // if not, we prepend the top directory to the destination path
222 if (!$has_top_directory) {
223 $destination_path .= self::DIRECTORY_SEPARATOR . $top_directory;
224 }
225 break;
227 if (!is_dir($destination_path) && (!mkdir($destination_path, 0777, true) && !is_dir($destination_path))) {
228 throw new \RuntimeException(sprintf('Directory "%s" was not created', $destination_path));
229 }
230
231 foreach ($this->getStreams() as $stream) {
232 $uri = $stream->getMetadata(self::URI);
233 if (substr((string) $uri, -1) === self::DIRECTORY_SEPARATOR) {
234 continue; // Skip directories
235 }
236 $file_name = Util::sanitizeFileName($destination_path . self::DIRECTORY_SEPARATOR . basename((string) $uri));
237 file_put_contents(
238 $file_name,
239 $stream->getContents()
240 );
241 }
242 return true; // Stop here
243 }
244
245 $this->zip->extractTo($destination_path, iterator_to_array($this->getPaths()));
246
247 return true;
248 }
249
250 public function hasZipReadingError(): bool
251 {
253 }
254}
The scope of this class is split ilias-conform URI's into components.
Definition: URI.php:35
Stream factory which enables the user to create streams without the knowledge of the concrete class.
Definition: Streams.php:32
static ofResource($resource)
Wraps an already created resource with the stream abstraction.
Definition: Streams.php:64
__construct(protected UnzipOptions $options, protected FileStream $stream)
Definition: Unzip.php:44
getDirectories()
Yields the directory-paths of the currently open zip-archive.
Definition: Unzip.php:122
getFiles()
Yields the file-paths of the currently open zip-archive.
Definition: Unzip.php:159
static sanitizeFileName(string $filename)
Definition: Util.php:48
The base interface for all filesystem streams.
Definition: FileStream.php:32
$path
Definition: ltiservices.php:30
@ FLAT_STRUCTURE
@description Will keep the top directory of the ZIP file if there is one (simple unzip).
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...