ILIAS  release_10 Revision v10.1-43-ga1241a92c2f
ContainerWrapper.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
27 
32 final class ContainerWrapper
33 {
34  private const BASE = '/';
35  private array $ignored = ['.', '..', '__MACOSX', '.info', '.DS_Store'];
36 
37  private string $current_level;
38  private array $data;
39  private \ILIAS\ResourceStorage\Services $irss;
40  private bool $use_flavour = true;
41  private ZipReader $reader;
42  private \ILIAS\FileDelivery\Services $file_delivery;
43 
44  public function __construct(
45  private ResourceIdentification $rid,
46  ?string $current_level = self::BASE ?? self::BASE
47  ) {
48  global $DIC;
49  // dependencies
50  $this->irss = $DIC->resourceStorage();
51  $this->file_delivery = $DIC->fileDelivery();
52  $this->reader = new ZipReader(
53  $this->irss->consume()->stream($rid)->getStream()
54  );
55 
56  // Init Data
57  $this->initData($rid);
58 
59  $this->initCurrentLevel($current_level);
60  }
61 
62  protected function initData(
64  ): void {
65  // init ZIP
66 
67  if ($this->use_flavour) {
68  $flavour_definition = new \ZipStructureDefinition();
69  $flavour = $this->irss->flavours()->get(
70  $rid,
71  $flavour_definition
72  );
73  $flavour_stream = $flavour->getStreamResolvers()[0] ?? null;
74  if ($flavour_stream !== null) {
75  $this->data = $flavour_definition->wake((string) $flavour_stream->getStream());
76  }
77  }
78 
79  if (empty($this->data)) {
80  $this->data = $this->reader->getStructure();
81  }
82 
83  // remove items from array with key . or /
84  $this->data = array_filter(
85  $this->data,
86  static fn($key) => !in_array($key, ['.', '/', './', '..'], true),
87  ARRAY_FILTER_USE_KEY
88  );
89  }
90 
91  public function download(string $path_inside_zip): never
92  {
93  [$stream, $info] = $this->reader->getItem($path_inside_zip, $this->data);
94 
95  $supported_for_inline = [
96  'image/*',
97  'application/pdf',
98  ];
99 
100  $regex = array_map(
101  static fn(string $mime_type) => str_replace('*', '.*', preg_quote($mime_type, '/')),
102  $supported_for_inline
103  );
104  $regex = implode('|', $regex);
105 
106  $disposition = preg_match("/($regex)/", $info['mime_type']) ? Disposition::INLINE : Disposition::ATTACHMENT;
107 
108  $this->file_delivery->delivery()->deliver(
109  $stream,
110  $info['basename'],
111  $info['mime_type'],
112  $disposition
113  );
114  }
115 
116  public function unzip(string $path_inside_zip): bool
117  {
118  [$stream, $info] = $this->reader->getItem($path_inside_zip, $this->data);
119  if ($info['mime_type'] !== 'application/zip' && $info['mime_type'] !== 'application/x-zip-compressed') {
120  return false;
121  }
122 
123  // save stream to temporary file
124  $tmp_directory = defined('CLIENT_DATA_DIR') ? \CLIENT_DATA_DIR . '/temp' : sys_get_temp_dir();
125  $tmp_file = tempnam($tmp_directory, 'ilias_zip_');
126 
128  $return = file_put_contents($tmp_file, $stream->detach());
129 
130  $zip_reader = new ZipReader(
131  Streams::ofResource(fopen($tmp_file, 'rb'))
132  );
133 
134  foreach ($zip_reader->getStructure() as $append_path_inside_zip => $item) {
135  if ($item['is_dir']) {
136  continue;
137  }
138  [$stream, $info] = $zip_reader->getItem($append_path_inside_zip, $this->data);
139  $this->irss->manageContainer()->addStreamToContainer(
140  $this->rid,
141  $stream,
142  $this->current_level . '/' . ltrim($append_path_inside_zip, './')
143  );
144  }
145 
146  unlink($tmp_file);
147  return true;
148  }
149 
150  public function getEntries(): \Generator
151  {
152  // sort by basename, but directories after files
153  uasort($this->data, static function (array $a, array $b): int {
154  if ($a['is_dir'] === $b['is_dir']) {
155  return strnatcasecmp($a['basename'], $b['basename']);
156  }
157  return $a['is_dir'] ? 1 : -1;
158  });
159 
160  foreach ($this->data as $path => $path_data) {
161  $dirname = $path_data['dirname'] ?? './';
162  if ($dirname !== $this->current_level) {
163  continue;
164  }
165  $basename = $path_data['basename'] ?? '';
166  if (in_array($basename, $this->ignored, true)) {
167  continue;
168  }
169 
170  if ($path_data['is_dir'] ?? false) {
171  // directory
172  yield $this->directory($path, $path_data);
173  } else {
174  // file
175  $file = $this->file($path, $path_data);
176  if ($file !== null) {
177  yield $file;
178  }
179  }
180  }
181  }
182 
183  public function getData(): array
184  {
185  return $this->data;
186  }
187 
188  private function basename(string $full_path): string
189  {
190  return basename($full_path);
191  // return preg_replace('/^' . preg_quote($this->current_level, '/') . '/', '', $full_path);
192  }
193 
194  public function initCurrentLevel(string $current_level): void
195  {
196  // init current level
197  $current_level = '/' . ltrim($current_level, './');
198  $current_level = rtrim($current_level, '/');
199  $this->current_level = $current_level === '' ? self::BASE : $current_level;
200  }
201 
202  protected function directory(string $path_inside_zip, array $data): ?Dir
203  {
204  return new Dir(
205  $path_inside_zip,
206  $this->basename($path_inside_zip),
207  new \DateTimeImmutable('@' . ($data['modified'] ?? 0))
208  );
209  }
210 
211  protected function file(string $path_inside_zip, array $data): ?File
212  {
213  return new File(
214  $path_inside_zip,
215  $this->basename($path_inside_zip),
216  $data['mime_type'] ?? 'application/octet-stream',
217  $data['size'] ?? 0,
218  new \DateTimeImmutable('@' . ($data['modified'] ?? 0))
219  );
220  }
221 
222 }
$path
Definition: ltiservices.php:30
const CLIENT_DATA_DIR
Definition: constants.php:46
static ofResource($resource)
Wraps an already created resource with the stream abstraction.
Definition: Streams.php:64
__construct(private ResourceIdentification $rid, ?string $current_level=self::BASE ?? self::BASE)
global $DIC
Definition: shib_login.php:25
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples