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