ILIAS  trunk Revision v12.0_alpha-1540-g00f839d5fa1
TreeProxyRepository.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
21namespace ILIAS\WebDAV\Objects;
22
32use Sabre\DAV\Exception\Forbidden;
33
38{
39 private array $storage = [];
40 private ?\ilTree $tree = null;
41 private ?Manager $manager = null;
44
45 public function __construct(
46 private Config $config,
47 private Filter $filter
48 ) {
49 global $DIC; // TODO remove Service Locator
50 $this->stakeholder = new \ilObjFileStakeholder();
51 }
52
53 private function tree(): \ilTree
54 {
55 return $this->tree ?? $this->tree = $GLOBALS['DIC']->repositoryTree();
56 }
57
58 private function manager(): Manager
59 {
60 global $DIC;
61 return $this->manager ?? $this->manager = $DIC->resourceStorage()->manage();
62 }
63
64 private function info(): \ilObjFileInfoRepository
65 {
66 return $this->file_info_repository ?? $this->file_info_repository = new \ilObjFileInfoRepository();
67 }
68
69 public function createObject(Type $type, Container $parent, string $name): ?Proxy
70 {
71 if (!$this->filter->checkName($name)) {
72 return null;
73 }
74
75 if (!$this->filter->canUserIn(Action::WRITE, $parent)) {
76 return null;
77 }
78
79 $object = match ($type) {
80 Type::COURSE => new \ilObjCourse(),
81 Type::GROUP => new \ilObjGroup(),
82 Type::FOLDER => new \ilObjFolder(),
83 Type::CATEGORY => new \ilObjCategory(),
84 Type::FILE => new \ilObjFile(),
85 default => null,
86 };
87
88 if (!$this->filter->canUserCreate($type, $parent)) {
89 return null;
90 }
91
92 if ($object === null) {
93 throw new \InvalidArgumentException(
94 "Cannot create object of type {$type->name}"
95 );
96 }
97
98 $object->setTitle($name);
99
100 if ($type === Type::FILE) {
101 $rid = $this->manager()->stream(
103 $this->stakeholder,
104 '_Empty'
105 );
106
107 $object->setResourceId($rid->serialize());
108 $this->manager()->unpublish(
109 $rid,
110 );
111 }
112
113 $object->create();
114 $object->createReference();
115 $object->putInTree($parent->getObjectProxy()->getRefId());
116 $object->setPermissions($parent->getObjectProxy()->getRefId());
117
118 $proxy = $this->getByRefId($object->getRefId(), $type);
119 if ($proxy === null || !$this->filter->filterProxy($proxy)) {
120 return null;
121 }
122 return $proxy;
123 }
124
125 public function createFile(Container $parent, string $name): ?FileProxy
126 {
127 if (!$this->filter->checkName($name)) {
128 return null;
129 }
130 if (($proxy = $this->get($name, $parent, true)) !== null && $proxy->getType() === Type::FILE) {
131 $ref_id = $parent->getObjectProxy()->getRefId();
132 if ($this->tree()->isDeleted($ref_id)) {
133 // TODO remove this "beauty". But: WebDAV at least on macOS Finder created a file, deleted it and cretes a
134 // new one. we pick up the first from trash if possible to prevent shadow objects.
135
136 \ilRepUtil::restoreObjects($ref_id, [$proxy->getRefId()]);
137 }
138
139 return $proxy;
140 }
141
142 return $this->createObject(
143 Type::FILE,
144 $parent,
145 $name,
146 );
147 }
148
149 public function createContainer(Container $parent, string $name): ?Proxy
150 {
151 $parent_type = $parent->getObjectProxy()?->getType() ?? Type::UNKNOWN;
152 $new_type = match ($parent_type) {
153 Type::COURSE, Type::GROUP, Type::FOLDER => Type::FOLDER,
154 Type::CATEGORY => Type::CATEGORY,
155 default => Type::UNKNOWN,
156 };
157
158 if (Type::UNKNOWN === $new_type) {
159 return null;
160 }
161
162 return $this->createObject(
163 $new_type,
164 $parent,
165 $name,
166 );
167 }
168
169 public function get(
170 string $path,
171 ?Container $parent = null,
172 bool $with_recently_deleted = false,
173 ): ?Proxy {
174 if (!$this->filter->checkName($path)) {
175 return null;
176 }
177
178 if ($path === '') {
179 return null;
180 }
181 if ($parent !== null) {
182 foreach ($this->in($parent, $with_recently_deleted) as $proxy) {
183 if (!$this->filter->filterProxy($proxy)) {
184 continue;
185 }
186
187 if ($proxy->getName() === $path) {
188 return $proxy;
189 }
190 }
191 return null;
192 }
193
194 if (!str_starts_with($path, $this->config->getRefIdPrefix())) {
195 return null;
196 }
197 $ref_id = (int) substr($path, strlen($this->config->getRefIdPrefix()));
198 if ($ref_id < 1) {
199 return null;
200 }
201
202 $proxy = $this->getByRefId($ref_id);
203 if ($proxy === null || !$this->filter->filterProxy($proxy)) {
204 return null;
205 }
206 return $proxy;
207 }
208
209 protected function getByNodeData(array $node_data, ?Type $type = null): ?Proxy
210 {
211 $ref_id = $node_data['ref_id'] ?? 0;
212 if ($ref_id < 1) {
213 return null;
214 }
215
216 if (isset($this->storage[$ref_id])) {
217 return $this->storage[$ref_id];
218 }
219
220 $type ??= Type::tryFrom($node_data['type'] ?? '') ?? Type::UNKNOWN;
221 $infos = null;
222 $stream_resolver = null;
223
224 // Check Title for Compatibility
225 $title = $node_data['title'] ?? '$';
226 if (!$this->filter->checkName($title)) {
227 return null;
228 }
229
230 if ($type === Type::FILE) {
231 // aggregate data from IRSS
232 $infos = $this->info()->getByRefId((int) $node_data['ref_id']);
233 $stream_resolver = new IRSSStreamHandler($infos?->getRID());
234
235 $file_tree_proxy = new FileTreeProxy(
236 $ref_id,
237 $node_data['obj_id'] ?? 0,
238 $title,
239 strtotime($node_data['last_update'] ?? '') ?: 0,
240 $infos?->getMimeType(),
241 (int) $infos?->getFileSize()->inBytes(),
242 $stream_resolver
243 );
244 if (!$this->filter->filterProxy($file_tree_proxy)) {
245 return null;
246 }
247
248 return $this->storage[$ref_id] = $file_tree_proxy;
249 }
250
251 $tree_proxy = new TreeProxy(
252 $ref_id,
253 $node_data['obj_id'] ?? 0,
254 $title,
255 strtotime($node_data['last_update'] ?? '') ?: 0,
256 $type
257 );
258
259 if (!$this->filter->filterProxy($tree_proxy)) {
260 return null;
261 }
262
263 return $this->storage[$ref_id] = $tree_proxy;
264 }
265
266 protected function getByRefId(int $by_ref_id, ?Type $type = null): ?Proxy
267 {
268 return $this->getByNodeData(
269 $this->tree()->getNodeData($by_ref_id),
270 $type
271 );
272 }
273
284 public function analyseProblems(Container $container): array
285 {
286 $ref_id = $container->getObjectProxy()?->getRefId();
287 $duplicates = [];
288 $forbidden = [];
289 $info_name_collision = false;
290 if ($ref_id === null) {
291 return [
292 'duplicates' => $duplicates,
293 'forbidden' => $forbidden,
294 'info_name_collision' => $info_name_collision,
295 ];
296 }
297
298 $object_types = $this->config->getSupportedObjectTypes();
299 $seen = [];
300 foreach ($this->tree()->getChildsByTypeFilter($ref_id, $object_types) as $item) {
301 $title = (string) ($item['title'] ?? '');
302 if ($title === '') {
303 continue;
304 }
305 if ($title === ProblemInfoFile::FILE_NAME) {
306 $info_name_collision = true;
307 continue;
308 }
309 if (!$this->filter->checkName($title)) {
310 $forbidden[] = $title;
311 continue;
312 }
313 if (isset($seen[$title])) {
314 $duplicates[] = $title;
315 continue;
316 }
317 $seen[$title] = true;
318 }
319
320 return [
321 'duplicates' => $duplicates,
322 'forbidden' => $forbidden,
323 'info_name_collision' => $info_name_collision,
324 ];
325 }
326
327 public function in(Container $container, bool $with_recently_deleted = false): \Generator|Proxy
328 {
329 $ref_id = $container->getObjectProxy()?->getRefId();
330 if ($ref_id === null) {
331 return;
332 }
333
334 $object_types = $this->config->getSupportedObjectTypes();
335
336 foreach (
337 $this->tree()->getChildsByTypeFilter($ref_id, $object_types) as $item
338 ) {
339 // check title for compatibility
340 if (!$this->filter->checkName($item['title'] ?? '$')) {
341 continue;
342 }
343
344 $proxy = $this->getByNodeData($item);
345 if ($proxy === null) {
346 continue;
347 }
348 if (!$this->filter->filterProxy($proxy)) {
349 continue;
350 }
351 yield $proxy;
352 }
353
354 // check for deleted objects
355 if ($with_recently_deleted) {
356 foreach ($this->tree()->getSavedNodeData($ref_id) as $saved_node) {
357 if (Type::tryFrom($saved_node['type'] ?? '') !== Type::FILE) {
358 continue;
359 }
360
361 $deleted = new \DateTimeImmutable($saved_node['deleted'] ?? 'now');
362 // check if deleted in the last 30 seconds
363 $threshold = (new \DateTimeImmutable())->modify(
364 "-{$this->config->getDeletedObjectsRetentionPeriod()} seconds"
365 );
366 if ($deleted < $threshold) {
367 continue;
368 }
369
370 $proxy = $this->getByNodeData($saved_node);
371 if ($proxy === null) {
372 continue;
373 }
374 if (!$this->filter->filterProxy($proxy)) {
375 continue;
376 }
377 yield $proxy;
378 }
379 }
380 }
381
382 public function rename(Entity $entity): bool
383 {
384 $ref_id = $entity->getObjectProxy()?->getRefId();
385 if ($ref_id === null) {
386 return false;
387 }
388 if (!$this->filter->checkName($entity->getName())) {
389 return false;
390 }
391
392 if (!$this->filter->canUserFor(Action::WRITE, $entity)) {
393 return false;
394 }
395
397 $object->setTitle($entity->getName());
398 $object->update();
399
400 return true;
401 }
402
403 public function delete(Entity $entity): bool
404 {
405 $ref_id = $entity->getObjectProxy()?->getRefId();
406 if ($ref_id === null) {
407 return false;
408 }
409
410 if (!$this->filter->canUserFor(Action::DELETE, $entity)) {
411 return false;
412 }
413
414 global $DIC;
415 $DIC->repository()->internal()->domain()->deletion()->deleteObjectsByRefIds([$ref_id]);
416
417 return true;
418 }
419
420}
Stream factory which enables the user to create streams without the knowledge of the concrete class.
Definition: Streams.php:32
static ofString(string $string)
Creates a new stream with an initial value.
Definition: Streams.php:41
Virtual text file shown in every container that lists ILIAS objects which cannot be exposed via WebDA...
getByNodeData(array $node_data, ?Type $type=null)
__construct(private Config $config, private Filter $filter)
createObject(Type $type, Container $parent, string $name)
in(Container $container, bool $with_recently_deleted=false)
getByRefId(int $by_ref_id, ?Type $type=null)
analyseProblems(Container $container)
Inspect raw children of a container and report titles that cannot be exposed via WebDAV.
createContainer(Container $parent, string $name)
createFile(Container $parent, string $name)
static getInstanceByRefId(int $ref_id, bool $stop_on_error=true)
get an instance of an Ilias object by reference id
static restoreObjects(int $a_cur_ref_id, array $a_ref_ids)
Move objects from trash back to repository.
Tree class data representation in hierachical trees using the Nested Set Model with Gaps by Joe Celco...
$ref_id
Definition: ltiauth.php:66
$path
Definition: ltiservices.php:30
filter(string $filter_id, array $class_path, string $cmd, bool $activated=true, bool $expanded=true)
global $DIC
Definition: shib_login.php:26
$container
@noRector
Definition: wac.php:37
$GLOBALS["DIC"]
Definition: wac.php:54