These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | /** |
||
6 | * balloon |
||
7 | * |
||
8 | * @copyright Copryright (c) 2012-2019 gyselroth GmbH (https://gyselroth.com) |
||
9 | * @license GPL-3.0 https://opensource.org/licenses/GPL-3.0 |
||
10 | */ |
||
11 | |||
12 | namespace Balloon\Filesystem\Node; |
||
13 | |||
14 | use Balloon\Filesystem; |
||
15 | use Balloon\Filesystem\Acl; |
||
16 | use Balloon\Filesystem\Acl\Exception\Forbidden as ForbiddenException; |
||
17 | use Balloon\Filesystem\Exception; |
||
18 | use Balloon\Hook; |
||
19 | use Balloon\Server; |
||
20 | use Balloon\Server\User; |
||
21 | use MimeType\MimeType; |
||
22 | use MongoDB\BSON\ObjectId; |
||
23 | use MongoDB\BSON\UTCDateTime; |
||
24 | use MongoDB\Database; |
||
25 | use Normalizer; |
||
26 | use Psr\Log\LoggerInterface; |
||
27 | use ZipStream\ZipStream; |
||
28 | |||
29 | abstract class AbstractNode implements NodeInterface |
||
30 | { |
||
31 | /** |
||
32 | * name max lenght. |
||
33 | */ |
||
34 | const MAX_NAME_LENGTH = 255; |
||
35 | |||
36 | /** |
||
37 | * Unique id. |
||
38 | * |
||
39 | * @var ObjectId |
||
40 | */ |
||
41 | protected $_id; |
||
42 | |||
43 | /** |
||
44 | * Node name. |
||
45 | * |
||
46 | * @var string |
||
47 | */ |
||
48 | protected $name = ''; |
||
49 | |||
50 | /** |
||
51 | * Owner. |
||
52 | * |
||
53 | * @var ObjectId |
||
54 | */ |
||
55 | protected $owner; |
||
56 | |||
57 | /** |
||
58 | * Mime. |
||
59 | * |
||
60 | * @var string |
||
61 | */ |
||
62 | protected $mime; |
||
63 | |||
64 | /** |
||
65 | * Meta attributes. |
||
66 | * |
||
67 | * @var array |
||
68 | */ |
||
69 | protected $meta = []; |
||
70 | |||
71 | /** |
||
72 | * Parent collection. |
||
73 | * |
||
74 | * @var ObjectId |
||
75 | */ |
||
76 | protected $parent; |
||
77 | |||
78 | /** |
||
79 | * Is file deleted. |
||
80 | * |
||
81 | * @var bool|UTCDateTime |
||
82 | */ |
||
83 | protected $deleted = false; |
||
84 | |||
85 | /** |
||
86 | * Is shared? |
||
87 | * |
||
88 | * @var bool |
||
89 | */ |
||
90 | protected $shared = false; |
||
91 | |||
92 | /** |
||
93 | * Destory at a certain time. |
||
94 | * |
||
95 | * @var UTCDateTime |
||
96 | */ |
||
97 | protected $destroy; |
||
98 | |||
99 | /** |
||
100 | * Changed timestamp. |
||
101 | * |
||
102 | * @var UTCDateTime |
||
103 | */ |
||
104 | protected $changed; |
||
105 | |||
106 | /** |
||
107 | * Created timestamp. |
||
108 | * |
||
109 | * @var UTCDateTime |
||
110 | */ |
||
111 | protected $created; |
||
112 | |||
113 | /** |
||
114 | * Point to antother node (Means this node is reference to $reference). |
||
115 | * |
||
116 | * @var ObjectId |
||
117 | */ |
||
118 | protected $reference; |
||
119 | |||
120 | /** |
||
121 | * Raw attributes before any processing or modifications. |
||
122 | * |
||
123 | * @var array |
||
124 | */ |
||
125 | protected $raw_attributes; |
||
126 | |||
127 | /** |
||
128 | * Readonly flag. |
||
129 | * |
||
130 | * @var bool |
||
131 | */ |
||
132 | protected $readonly = false; |
||
133 | |||
134 | /** |
||
135 | * App attributes. |
||
136 | * |
||
137 | * @var array |
||
138 | */ |
||
139 | protected $app = []; |
||
140 | |||
141 | /** |
||
142 | * Filesystem. |
||
143 | * |
||
144 | * @var Filesystem |
||
145 | */ |
||
146 | protected $_fs; |
||
147 | |||
148 | /** |
||
149 | * Database. |
||
150 | * |
||
151 | * @var Database |
||
152 | */ |
||
153 | protected $_db; |
||
154 | |||
155 | /** |
||
156 | * User. |
||
157 | * |
||
158 | * @var User |
||
159 | */ |
||
160 | protected $_user; |
||
161 | |||
162 | /** |
||
163 | * Logger. |
||
164 | * |
||
165 | * @var LoggerInterface |
||
166 | */ |
||
167 | protected $_logger; |
||
168 | |||
169 | /** |
||
170 | * Server. |
||
171 | * |
||
172 | * @var Server |
||
173 | */ |
||
174 | protected $_server; |
||
175 | |||
176 | /** |
||
177 | * Hook. |
||
178 | * |
||
179 | * @var Hook |
||
180 | */ |
||
181 | protected $_hook; |
||
182 | |||
183 | /** |
||
184 | * Acl. |
||
185 | * |
||
186 | * @var Acl |
||
187 | */ |
||
188 | protected $_acl; |
||
189 | |||
190 | /** |
||
191 | * Mount. |
||
192 | * |
||
193 | * @var ObjectId |
||
194 | */ |
||
195 | protected $storage_reference; |
||
196 | |||
197 | /** |
||
198 | * Storage attributes. |
||
199 | * |
||
200 | * @var array |
||
201 | */ |
||
202 | protected $storage; |
||
203 | |||
204 | /** |
||
205 | * Acl. |
||
206 | * |
||
207 | * @var array |
||
208 | */ |
||
209 | protected $acl = []; |
||
210 | |||
211 | /** |
||
212 | * Mount. |
||
213 | * |
||
214 | * @var array |
||
215 | */ |
||
216 | protected $mount = []; |
||
217 | |||
218 | /** |
||
219 | * Parent collection. |
||
220 | * |
||
221 | * @var Collection |
||
222 | */ |
||
223 | protected $_parent; |
||
224 | |||
225 | /** |
||
226 | * Convert to filename. |
||
227 | * |
||
228 | * @return string |
||
229 | */ |
||
230 | public function __toString() |
||
231 | { |
||
232 | return $this->name; |
||
233 | } |
||
234 | |||
235 | /** |
||
236 | * Get owner. |
||
237 | */ |
||
238 | public function getOwner(): ObjectId |
||
239 | { |
||
240 | return $this->owner; |
||
241 | } |
||
242 | |||
243 | /** |
||
244 | * Set filesystem. |
||
245 | */ |
||
246 | public function setFilesystem(Filesystem $fs): NodeInterface |
||
247 | { |
||
248 | $this->_fs = $fs; |
||
249 | $this->_user = $fs->getUser(); |
||
250 | |||
251 | return $this; |
||
252 | } |
||
253 | |||
254 | /** |
||
255 | * Get filesystem. |
||
256 | */ |
||
257 | public function getFilesystem(): Filesystem |
||
258 | { |
||
259 | return $this->_fs; |
||
260 | } |
||
261 | |||
262 | /** |
||
263 | * Check if $node is a sub node of any parent nodes of this node. |
||
264 | */ |
||
265 | public function isSubNode(NodeInterface $node): bool |
||
266 | { |
||
267 | if ($node->getId() == $this->_id) { |
||
268 | return true; |
||
269 | } |
||
270 | |||
271 | foreach ($node->getParents() as $node) { |
||
272 | if ($node->getId() == $this->_id) { |
||
273 | return true; |
||
274 | } |
||
275 | } |
||
276 | |||
277 | if ($this->isRoot()) { |
||
278 | return true; |
||
279 | } |
||
280 | |||
281 | return false; |
||
282 | } |
||
283 | |||
284 | /** |
||
285 | * Move node. |
||
286 | */ |
||
287 | public function setParent(Collection $parent, int $conflict = NodeInterface::CONFLICT_NOACTION): NodeInterface |
||
288 | { |
||
289 | if ($this->parent === $parent->getId()) { |
||
290 | throw new Exception\Conflict( |
||
291 | 'source node '.$this->name.' is already in the requested parent folder', |
||
292 | Exception\Conflict::ALREADY_THERE |
||
293 | ); |
||
294 | } |
||
295 | if ($this->isSubNode($parent)) { |
||
296 | throw new Exception\Conflict( |
||
297 | 'node called '.$this->name.' can not be moved into itself', |
||
298 | Exception\Conflict::CANT_BE_CHILD_OF_ITSELF |
||
299 | ); |
||
300 | } |
||
301 | if (!$this->_acl->isAllowed($this, 'w') && !$this->isReference()) { |
||
302 | throw new ForbiddenException( |
||
303 | 'not allowed to move node '.$this->name, |
||
304 | ForbiddenException::NOT_ALLOWED_TO_MOVE |
||
305 | ); |
||
306 | } |
||
307 | |||
308 | $exists = $parent->childExists($this->name); |
||
309 | if (true === $exists && NodeInterface::CONFLICT_NOACTION === $conflict) { |
||
310 | throw new Exception\Conflict( |
||
311 | 'a node called '.$this->name.' does already exists in this collection', |
||
312 | Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS |
||
313 | ); |
||
314 | } |
||
315 | if ($this->isShared() && $this instanceof Collection && $parent->isShared()) { |
||
316 | throw new Exception\Conflict( |
||
317 | 'a shared folder can not be a child of a shared folder too', |
||
318 | Exception\Conflict::SHARED_NODE_CANT_BE_CHILD_OF_SHARE |
||
319 | ); |
||
320 | } |
||
321 | if ($parent->isDeleted()) { |
||
322 | throw new Exception\Conflict( |
||
323 | 'cannot move node into a deleted collction', |
||
324 | Exception\Conflict::DELETED_PARENT |
||
325 | ); |
||
326 | } |
||
327 | |||
328 | if (true === $exists && NodeInterface::CONFLICT_RENAME === $conflict) { |
||
329 | $this->setName($this->getDuplicateName()); |
||
330 | $this->raw_attributes['name'] = $this->name; |
||
331 | } |
||
332 | |||
333 | if ($this instanceof Collection) { |
||
334 | $this->getChildrenRecursive($this->getRealId(), $shares); |
||
335 | |||
336 | if (!empty($shares) && $parent->isShared()) { |
||
337 | throw new Exception\Conflict( |
||
338 | 'folder contains a shared folder', |
||
339 | Exception\Conflict::NODE_CONTAINS_SHARED_NODE |
||
340 | ); |
||
341 | } |
||
342 | } |
||
343 | |||
344 | if (($parent->isSpecial() && $this->shared != $parent->getShareId()) |
||
345 | || (!$parent->isSpecial() && $this->isShareMember()) |
||
346 | || ($parent->getMount() != $this->getParent()->getMount())) { |
||
347 | $new = $this->copyTo($parent, $conflict); |
||
348 | $this->delete(); |
||
349 | |||
350 | return $new; |
||
351 | } |
||
352 | |||
353 | if (true === $exists && NodeInterface::CONFLICT_MERGE === $conflict) { |
||
354 | $new = $this->copyTo($parent, $conflict); |
||
355 | $this->delete(true); |
||
356 | |||
357 | return $new; |
||
358 | } |
||
359 | |||
360 | $this->storage = $this->_parent->getStorage()->move($this, $parent); |
||
361 | $this->parent = $parent->getRealId(); |
||
362 | $this->owner = $this->_user->getId(); |
||
363 | |||
364 | $this->save(['parent', 'shared', 'owner', 'storage']); |
||
365 | |||
366 | return $this; |
||
367 | } |
||
368 | |||
369 | /** |
||
370 | * Set node acl. |
||
371 | */ |
||
372 | public function setAcl(array $acl): NodeInterface |
||
373 | { |
||
374 | if (!$this->_acl->isAllowed($this, 'm')) { |
||
375 | throw new ForbiddenException( |
||
376 | 'not allowed to update acl', |
||
377 | ForbiddenException::NOT_ALLOWED_TO_MANAGE |
||
378 | ); |
||
379 | } |
||
380 | |||
381 | if (!$this->isShareMember()) { |
||
382 | throw new Exception\Conflict('node acl may only be set on share member nodes', Exception\Conflict::NOT_SHARED); |
||
383 | } |
||
384 | |||
385 | $this->_acl->validateAcl($this->_server, $acl); |
||
386 | $this->acl = $acl; |
||
387 | $this->save(['acl']); |
||
388 | |||
389 | return $this; |
||
390 | } |
||
391 | |||
392 | /** |
||
393 | * Get ACL. |
||
394 | */ |
||
395 | public function getAcl(): array |
||
396 | { |
||
397 | if ($this->isReference()) { |
||
398 | $acl = $this->_fs->findRawNode($this->getShareId())['acl']; |
||
399 | } else { |
||
400 | $acl = $this->acl; |
||
401 | } |
||
402 | |||
403 | return $this->_acl->resolveAclTable($this->_server, $acl); |
||
404 | } |
||
405 | |||
406 | /** |
||
407 | * Get share id. |
||
408 | */ |
||
409 | public function getShareId(bool $reference = false): ?ObjectId |
||
410 | { |
||
411 | if ($this->isReference() && true === $reference) { |
||
412 | return $this->_id; |
||
413 | } |
||
414 | if ($this->isShareMember() && true === $reference) { |
||
415 | return $this->shared; |
||
0 ignored issues
–
show
|
|||
416 | } |
||
417 | if ($this->isShared() && $this->isReference()) { |
||
418 | return $this->reference; |
||
419 | } |
||
420 | if ($this->isShared()) { |
||
421 | return $this->_id; |
||
422 | } |
||
423 | if ($this->isShareMember()) { |
||
424 | return $this->shared; |
||
0 ignored issues
–
show
The return type of
return $this->shared; (boolean ) is incompatible with the return type declared by the interface Balloon\Filesystem\Node\NodeInterface::getShareId of type MongoDB\BSON\ObjectId .
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design. Let’s take a look at an example: class Author {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
abstract class Post {
public function getAuthor() {
return 'Johannes';
}
}
class BlogPost extends Post {
public function getAuthor() {
return new Author('Johannes');
}
}
class ForumPost extends Post { /* ... */ }
function my_function(Post $post) {
echo strtoupper($post->getAuthor());
}
Our function
Loading history...
|
|||
425 | } |
||
426 | |||
427 | return null; |
||
428 | } |
||
429 | |||
430 | /** |
||
431 | * Get share node. |
||
432 | */ |
||
433 | public function getShareNode(): ?Collection |
||
434 | { |
||
435 | if ($this->isShare()) { |
||
436 | return $this; |
||
437 | } |
||
438 | |||
439 | if ($this->isSpecial()) { |
||
440 | return $this->_fs->findNodeById($this->getShareId(true)); |
||
441 | } |
||
442 | |||
443 | return null; |
||
444 | } |
||
445 | |||
446 | /** |
||
447 | * Is node marked as readonly? |
||
448 | */ |
||
449 | public function isReadonly(): bool |
||
450 | { |
||
451 | return $this->readonly; |
||
452 | } |
||
453 | |||
454 | /** |
||
455 | * Request is from node owner? |
||
456 | */ |
||
457 | public function isOwnerRequest(): bool |
||
458 | { |
||
459 | return null !== $this->_user && $this->owner == $this->_user->getId(); |
||
460 | } |
||
461 | |||
462 | /** |
||
463 | * Check if node is kind of special. |
||
464 | */ |
||
465 | public function isSpecial(): bool |
||
466 | { |
||
467 | if ($this->isShared()) { |
||
468 | return true; |
||
469 | } |
||
470 | if ($this->isReference()) { |
||
471 | return true; |
||
472 | } |
||
473 | if ($this->isShareMember()) { |
||
474 | return true; |
||
475 | } |
||
476 | |||
477 | return false; |
||
478 | } |
||
479 | |||
480 | /** |
||
481 | * Check if node is a sub node of a share. |
||
482 | */ |
||
483 | public function isShareMember(): bool |
||
484 | { |
||
485 | return $this->shared instanceof ObjectId && !$this->isReference(); |
||
486 | } |
||
487 | |||
488 | /** |
||
489 | * Check if node is a sub node of an external storage mount. |
||
490 | */ |
||
491 | public function isMountMember(): bool |
||
492 | { |
||
493 | return $this->storage_reference instanceof ObjectId; |
||
494 | } |
||
495 | |||
496 | /** |
||
497 | * Is share. |
||
498 | */ |
||
499 | public function isShare(): bool |
||
500 | { |
||
501 | return true === $this->shared && !$this->isReference(); |
||
502 | } |
||
503 | |||
504 | /** |
||
505 | * Is share (Reference or master share). |
||
506 | */ |
||
507 | public function isShared(): bool |
||
508 | { |
||
509 | if (true === $this->shared) { |
||
510 | return true; |
||
511 | } |
||
512 | |||
513 | return false; |
||
514 | } |
||
515 | |||
516 | /** |
||
517 | * Set the name. |
||
518 | */ |
||
519 | public function setName($name): bool |
||
520 | { |
||
521 | $name = $this->checkName($name); |
||
522 | |||
523 | try { |
||
524 | $child = $this->getParent()->getChild($name); |
||
525 | if ($child->getId() != $this->_id) { |
||
526 | throw new Exception\Conflict( |
||
527 | 'a node called '.$name.' does already exists in this collection', |
||
528 | Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS |
||
529 | ); |
||
530 | } |
||
531 | } catch (Exception\NotFound $e) { |
||
532 | //child does not exists, we can safely rename |
||
533 | } |
||
534 | |||
535 | $this->storage = $this->_parent->getStorage()->rename($this, $name); |
||
536 | $this->name = $name; |
||
537 | |||
538 | if ($this instanceof File) { |
||
539 | $this->mime = MimeType::getType($this->name); |
||
540 | } |
||
541 | |||
542 | return $this->save(['name', 'storage', 'mime']); |
||
543 | } |
||
544 | |||
545 | /** |
||
546 | * Check name. |
||
547 | */ |
||
548 | public function checkName(string $name): string |
||
549 | { |
||
550 | if (preg_match('/([\\\<\>\:\"\/\|\*\?])|(^$)|(^\.$)|(^\..$)/', $name)) { |
||
551 | throw new Exception\InvalidArgument('name contains invalid characters'); |
||
552 | } |
||
553 | if (strlen($name) > self::MAX_NAME_LENGTH) { |
||
554 | throw new Exception\InvalidArgument('name is longer than '.self::MAX_NAME_LENGTH.' characters'); |
||
555 | } |
||
556 | |||
557 | if (!Normalizer::isNormalized($name)) { |
||
558 | $name = Normalizer::normalize($name); |
||
559 | } |
||
560 | |||
561 | return $name; |
||
562 | } |
||
563 | |||
564 | /** |
||
565 | * Get the name. |
||
566 | */ |
||
567 | public function getName(): string |
||
568 | { |
||
569 | return $this->name; |
||
570 | } |
||
571 | |||
572 | /** |
||
573 | * Get mount node. |
||
574 | */ |
||
575 | public function getMount(): ?ObjectId |
||
576 | { |
||
577 | return count($this->mount) > 0 ? $this->_id : $this->storage_reference; |
||
578 | } |
||
579 | |||
580 | /** |
||
581 | * Undelete. |
||
582 | */ |
||
583 | public function undelete(int $conflict = NodeInterface::CONFLICT_NOACTION, ?string $recursion = null, bool $recursion_first = true): bool |
||
584 | { |
||
585 | if (!$this->_acl->isAllowed($this, 'w') && !$this->isReference()) { |
||
586 | throw new ForbiddenException( |
||
587 | 'not allowed to restore node '.$this->name, |
||
588 | ForbiddenException::NOT_ALLOWED_TO_UNDELETE |
||
589 | ); |
||
590 | } |
||
591 | if (!$this->isDeleted()) { |
||
592 | throw new Exception\Conflict( |
||
593 | 'node is not deleted, skip restore', |
||
594 | Exception\Conflict::NOT_DELETED |
||
595 | ); |
||
596 | } |
||
597 | |||
598 | $parent = $this->getParent(); |
||
599 | if ($parent->isDeleted()) { |
||
600 | throw new Exception\Conflict( |
||
601 | 'could not restore node '.$this->name.' into a deleted parent', |
||
602 | Exception\Conflict::DELETED_PARENT |
||
603 | ); |
||
604 | } |
||
605 | |||
606 | if ($parent->childExists($this->name)) { |
||
607 | if (NodeInterface::CONFLICT_MERGE === $conflict) { |
||
608 | $this->copyTo($parent, $conflict); |
||
609 | $this->delete(true); |
||
610 | } elseif (NodeInterface::CONFLICT_RENAME === $conflict) { |
||
611 | $this->setName($this->getDuplicateName()); |
||
612 | $this->raw_attributes['name'] = $this->name; |
||
613 | } else { |
||
614 | throw new Exception\Conflict( |
||
615 | 'a node called '.$this->name.' does already exists in this collection', |
||
616 | Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS |
||
617 | ); |
||
618 | } |
||
619 | } |
||
620 | |||
621 | if (null === $recursion) { |
||
622 | $recursion_first = true; |
||
623 | $recursion = uniqid(); |
||
624 | } else { |
||
625 | $recursion_first = false; |
||
626 | } |
||
627 | |||
628 | $this->storage = $this->_parent->getStorage()->undelete($this); |
||
629 | $this->deleted = false; |
||
630 | |||
631 | $this->save([ |
||
632 | 'storage', |
||
633 | 'name', |
||
634 | 'deleted', |
||
635 | ], [], $recursion, $recursion_first); |
||
636 | |||
637 | if ($this instanceof File || $this->isReference() || $this->isMounted() || $this->isFiltered()) { |
||
638 | return true; |
||
639 | } |
||
640 | |||
641 | return $this->doRecursiveAction(function ($node) use ($conflict, $recursion) { |
||
642 | $node->undelete($conflict, $recursion, false); |
||
643 | }, NodeInterface::DELETED_ONLY); |
||
644 | } |
||
645 | |||
646 | /** |
||
647 | * Is node deleted? |
||
648 | */ |
||
649 | public function isDeleted(): bool |
||
650 | { |
||
651 | return $this->deleted instanceof UTCDateTime; |
||
652 | } |
||
653 | |||
654 | /** |
||
655 | * Get last modified timestamp. |
||
656 | */ |
||
657 | public function getLastModified(): int |
||
658 | { |
||
659 | if ($this->changed instanceof UTCDateTime) { |
||
660 | return (int) $this->changed->toDateTime()->format('U'); |
||
661 | } |
||
662 | |||
663 | return 0; |
||
664 | } |
||
665 | |||
666 | /** |
||
667 | * Get unique id. |
||
668 | * |
||
669 | * @return ObjectId |
||
670 | */ |
||
671 | 1 | public function getId(): ?ObjectId |
|
672 | { |
||
673 | 1 | return $this->_id; |
|
674 | } |
||
675 | |||
676 | /** |
||
677 | * Get parent. |
||
678 | */ |
||
679 | public function getParent(): ?Collection |
||
680 | { |
||
681 | return $this->_parent; |
||
682 | } |
||
683 | |||
684 | /** |
||
685 | * Get parents. |
||
686 | */ |
||
687 | public function getParents(?NodeInterface $node = null, array $parents = []): array |
||
688 | { |
||
689 | if (null === $node) { |
||
690 | $node = $this; |
||
691 | } |
||
692 | |||
693 | if ($node->isInRoot()) { |
||
694 | return $parents; |
||
695 | } |
||
696 | $parent = $node->getParent(); |
||
697 | $parents[] = $parent; |
||
698 | |||
699 | return $node->getParents($parent, $parents); |
||
700 | } |
||
701 | |||
702 | /** |
||
703 | * Get as zip. |
||
704 | */ |
||
705 | public function getZip(): void |
||
706 | { |
||
707 | $archive = new ZipStream($this->name.'.zip'); |
||
708 | $this->zip($archive, false); |
||
709 | $archive->finish(); |
||
710 | } |
||
711 | |||
712 | /** |
||
713 | * Create zip. |
||
714 | */ |
||
715 | public function zip(ZipStream $archive, bool $self = true, ?NodeInterface $parent = null, string $path = '', int $depth = 0): bool |
||
716 | { |
||
717 | if (null === $parent) { |
||
718 | $parent = $this; |
||
719 | } |
||
720 | |||
721 | if ($parent instanceof Collection) { |
||
722 | $children = $parent->getChildNodes(); |
||
723 | |||
724 | if (true === $self && 0 === $depth) { |
||
725 | $path = $parent->getName().DIRECTORY_SEPARATOR; |
||
726 | } elseif (0 === $depth) { |
||
727 | $path = ''; |
||
728 | } elseif (0 !== $depth) { |
||
729 | $path .= DIRECTORY_SEPARATOR.$parent->getName().DIRECTORY_SEPARATOR; |
||
730 | } |
||
731 | |||
732 | foreach ($children as $child) { |
||
733 | $name = $path.$child->getName(); |
||
734 | |||
735 | if ($child instanceof Collection) { |
||
736 | $this->zip($archive, $self, $child, $name, ++$depth); |
||
737 | } elseif ($child instanceof File) { |
||
738 | try { |
||
739 | $resource = $child->get(); |
||
740 | if ($resource !== null) { |
||
741 | $archive->addFileFromStream($name, $resource); |
||
742 | } |
||
743 | } catch (\Exception $e) { |
||
744 | $this->_logger->error('failed add file ['.$child->getId().'] to zip stream', [ |
||
745 | 'category' => get_class($this), |
||
746 | 'exception' => $e, |
||
747 | ]); |
||
748 | } |
||
749 | } |
||
750 | } |
||
751 | } elseif ($parent instanceof File) { |
||
752 | $resource = $parent->get(); |
||
753 | if ($resource !== null) { |
||
754 | $archive->addFileFromStream($parent->getName(), $resource); |
||
755 | } |
||
756 | } |
||
757 | |||
758 | return true; |
||
759 | } |
||
760 | |||
761 | /** |
||
762 | * Get mime type. |
||
763 | */ |
||
764 | 1 | public function getContentType(): string |
|
765 | { |
||
766 | 1 | return $this->mime; |
|
767 | } |
||
768 | |||
769 | /** |
||
770 | * Is reference. |
||
771 | */ |
||
772 | public function isReference(): bool |
||
773 | { |
||
774 | return $this->reference instanceof ObjectId; |
||
775 | } |
||
776 | |||
777 | /** |
||
778 | * Set app attributes. |
||
779 | */ |
||
780 | public function setAppAttributes(string $namespace, array $attributes): NodeInterface |
||
781 | { |
||
782 | $this->app[$namespace] = $attributes; |
||
783 | $this->save('app.'.$namespace); |
||
784 | |||
785 | return $this; |
||
786 | } |
||
787 | |||
788 | /** |
||
789 | * Set app attribute. |
||
790 | */ |
||
791 | public function setAppAttribute(string $namespace, string $attribute, $value): NodeInterface |
||
792 | { |
||
793 | if (!isset($this->app[$namespace])) { |
||
794 | $this->app[$namespace] = []; |
||
795 | } |
||
796 | |||
797 | $this->app[$namespace][$attribute] = $value; |
||
798 | $this->save('app.'.$namespace); |
||
799 | |||
800 | return $this; |
||
801 | } |
||
802 | |||
803 | /** |
||
804 | * Remove app attribute. |
||
805 | */ |
||
806 | public function unsetAppAttributes(string $namespace): NodeInterface |
||
807 | { |
||
808 | if (isset($this->app[$namespace])) { |
||
809 | unset($this->app[$namespace]); |
||
810 | $this->save('app.'.$namespace); |
||
811 | } |
||
812 | |||
813 | return $this; |
||
814 | } |
||
815 | |||
816 | /** |
||
817 | * Remove app attribute. |
||
818 | */ |
||
819 | public function unsetAppAttribute(string $namespace, string $attribute): NodeInterface |
||
820 | { |
||
821 | if (isset($this->app[$namespace][$attribute])) { |
||
822 | unset($this->app[$namespace][$attribute]); |
||
823 | $this->save('app'.$namespace); |
||
824 | } |
||
825 | |||
826 | return $this; |
||
827 | } |
||
828 | |||
829 | /** |
||
830 | * Get app attribute. |
||
831 | */ |
||
832 | public function getAppAttribute(string $namespace, string $attribute) |
||
833 | { |
||
834 | if (isset($this->app[$namespace][$attribute])) { |
||
835 | return $this->app[$namespace][$attribute]; |
||
836 | } |
||
837 | |||
838 | return null; |
||
839 | } |
||
840 | |||
841 | /** |
||
842 | * Get app attributes. |
||
843 | */ |
||
844 | public function getAppAttributes(string $namespace): array |
||
845 | { |
||
846 | if (isset($this->app[$namespace])) { |
||
847 | return $this->app[$namespace]; |
||
848 | } |
||
849 | |||
850 | return []; |
||
851 | } |
||
852 | |||
853 | /** |
||
854 | * Set meta attributes. |
||
855 | */ |
||
856 | public function setMetaAttributes(array $attributes): NodeInterface |
||
857 | { |
||
858 | $attributes = $this->validateMetaAttributes($attributes); |
||
859 | foreach ($attributes as $attribute => $value) { |
||
860 | if (empty($value) && isset($this->meta[$attribute])) { |
||
861 | unset($this->meta[$attribute]); |
||
862 | } elseif (!empty($value)) { |
||
863 | $this->meta[$attribute] = $value; |
||
864 | } |
||
865 | } |
||
866 | |||
867 | $this->save('meta'); |
||
868 | |||
869 | return $this; |
||
870 | } |
||
871 | |||
872 | /** |
||
873 | * Get meta attributes as array. |
||
874 | */ |
||
875 | public function getMetaAttributes(array $attributes = []): array |
||
876 | { |
||
877 | if (empty($attributes)) { |
||
878 | return $this->meta; |
||
879 | } |
||
880 | if (is_array($attributes)) { |
||
881 | return array_intersect_key($this->meta, array_flip($attributes)); |
||
882 | } |
||
883 | } |
||
884 | |||
885 | /** |
||
886 | * Mark node as readonly. |
||
887 | */ |
||
888 | public function setReadonly(bool $readonly = true): bool |
||
889 | { |
||
890 | $this->readonly = $readonly; |
||
891 | $this->storage = $this->_parent->getStorage()->readonly($this, $readonly); |
||
892 | |||
893 | return $this->save(['readonly', 'storage']); |
||
894 | } |
||
895 | |||
896 | /** |
||
897 | * Mark node as self-destroyable. |
||
898 | */ |
||
899 | public function setDestroyable(?UTCDateTime $ts): bool |
||
900 | { |
||
901 | $this->destroy = $ts; |
||
902 | |||
903 | if (null === $ts) { |
||
904 | return $this->save([], 'destroy'); |
||
905 | } |
||
906 | |||
907 | return $this->save('destroy'); |
||
908 | } |
||
909 | |||
910 | /** |
||
911 | * Get original raw attributes before any processing. |
||
912 | */ |
||
913 | public function getRawAttributes(): array |
||
914 | { |
||
915 | return $this->raw_attributes; |
||
916 | } |
||
917 | |||
918 | /** |
||
919 | * Check if node is in root. |
||
920 | */ |
||
921 | public function isInRoot(): bool |
||
922 | { |
||
923 | return null === $this->parent; |
||
924 | } |
||
925 | |||
926 | /** |
||
927 | * Check if node is an instance of the actual root collection. |
||
928 | */ |
||
929 | public function isRoot(): bool |
||
930 | { |
||
931 | return null === $this->_id && ($this instanceof Collection); |
||
932 | } |
||
933 | |||
934 | /** |
||
935 | * Resolve node path. |
||
936 | */ |
||
937 | public function getPath(): string |
||
938 | { |
||
939 | $path = ''; |
||
940 | foreach (array_reverse($this->getParents()) as $parent) { |
||
941 | $path .= DIRECTORY_SEPARATOR.$parent->getName(); |
||
942 | } |
||
943 | |||
944 | $path .= DIRECTORY_SEPARATOR.$this->getName(); |
||
945 | |||
946 | return $path; |
||
947 | } |
||
948 | |||
949 | /** |
||
950 | * Save node attributes. |
||
951 | * |
||
952 | * @param array|string $attributes |
||
953 | * @param array|string $remove |
||
954 | * @param string $recursion |
||
955 | */ |
||
956 | public function save($attributes = [], $remove = [], ?string $recursion = null, bool $recursion_first = true): bool |
||
957 | { |
||
958 | if (!$this->_acl->isAllowed($this, 'w') && !$this->isReference()) { |
||
959 | throw new ForbiddenException( |
||
960 | 'not allowed to modify node '.$this->name, |
||
961 | ForbiddenException::NOT_ALLOWED_TO_MODIFY |
||
962 | ); |
||
963 | } |
||
964 | |||
965 | if ($this instanceof Collection && $this->isRoot()) { |
||
966 | return false; |
||
967 | } |
||
968 | |||
969 | $remove = (array) $remove; |
||
970 | $attributes = (array) $attributes; |
||
971 | $this->_hook->run( |
||
972 | 'preSaveNodeAttributes', |
||
973 | [$this, &$attributes, &$remove, &$recursion, &$recursion_first] |
||
974 | ); |
||
975 | |||
976 | try { |
||
977 | $set = []; |
||
978 | $values = $this->getAttributes(); |
||
979 | foreach ($attributes as $attr) { |
||
980 | $set[$attr] = $this->getArrayValue($values, $attr); |
||
981 | } |
||
982 | |||
983 | $update = []; |
||
984 | if (!empty($set)) { |
||
985 | $update['$set'] = $set; |
||
986 | } |
||
987 | |||
988 | if (!empty($remove)) { |
||
989 | $remove = array_fill_keys($remove, 1); |
||
990 | $update['$unset'] = $remove; |
||
991 | } |
||
992 | |||
993 | if (empty($update)) { |
||
994 | return false; |
||
995 | } |
||
996 | $result = $this->_db->storage->updateOne([ |
||
997 | '_id' => $this->_id, |
||
998 | ], $update); |
||
999 | |||
1000 | $this->_hook->run( |
||
1001 | 'postSaveNodeAttributes', |
||
1002 | [$this, $attributes, $remove, $recursion, $recursion_first] |
||
1003 | ); |
||
1004 | |||
1005 | $this->_logger->info('modified node attributes of ['.$this->_id.']', [ |
||
1006 | 'category' => get_class($this), |
||
1007 | 'params' => $update, |
||
1008 | ]); |
||
1009 | |||
1010 | return true; |
||
1011 | } catch (\Exception $e) { |
||
1012 | $this->_logger->error('failed modify node attributes of ['.$this->_id.']', [ |
||
1013 | 'category' => get_class($this), |
||
1014 | 'exception' => $e, |
||
1015 | ]); |
||
1016 | |||
1017 | throw $e; |
||
1018 | } |
||
1019 | } |
||
1020 | |||
1021 | /** |
||
1022 | * Get array value via string path. |
||
1023 | */ |
||
1024 | protected function getArrayValue(Iterable $array, string $path, string $separator = '.') |
||
1025 | { |
||
1026 | if (isset($array[$path])) { |
||
1027 | return $array[$path]; |
||
1028 | } |
||
1029 | $keys = explode($separator, $path); |
||
1030 | |||
1031 | foreach ($keys as $key) { |
||
1032 | if (!array_key_exists($key, $array)) { |
||
1033 | return; |
||
1034 | } |
||
1035 | |||
1036 | $array = $array[$key]; |
||
1037 | } |
||
1038 | |||
1039 | return $array; |
||
1040 | } |
||
1041 | |||
1042 | /** |
||
1043 | * Validate meta attributes. |
||
1044 | */ |
||
1045 | protected function validateMetaAttributes(array $attributes): array |
||
1046 | { |
||
1047 | foreach ($attributes as $attribute => $value) { |
||
1048 | $const = __CLASS__.'::META_'.strtoupper($attribute); |
||
1049 | if (!defined($const)) { |
||
1050 | throw new Exception('meta attribute '.$attribute.' is not valid'); |
||
1051 | } |
||
1052 | |||
1053 | if ($attribute === NodeInterface::META_TAGS && !empty($value) && (!is_array($value) || array_filter($value, 'is_string') != $value)) { |
||
1054 | throw new Exception('tags meta attribute must be an array of strings'); |
||
1055 | } |
||
1056 | |||
1057 | if ($attribute !== NodeInterface::META_TAGS && !is_string($value)) { |
||
1058 | throw new Exception($attribute.' meta attribute must be a string'); |
||
1059 | } |
||
1060 | } |
||
1061 | |||
1062 | return $attributes; |
||
1063 | } |
||
1064 | |||
1065 | /** |
||
1066 | * Duplicate name with a uniqid within name. |
||
1067 | */ |
||
1068 | protected function getDuplicateName(?string $name = null, ?string $class = null): string |
||
1069 | { |
||
1070 | if (null === $name) { |
||
1071 | $name = $this->name; |
||
1072 | } |
||
1073 | |||
1074 | if (null === $class) { |
||
1075 | $class = get_class($this); |
||
1076 | } |
||
1077 | |||
1078 | if ($class === Collection::class) { |
||
1079 | return $name.' ('.substr(uniqid('', true), -4).')'; |
||
1080 | } |
||
1081 | |||
1082 | $ext = substr(strrchr($name, '.'), 1); |
||
1083 | if (false === $ext) { |
||
1084 | return $name.' ('.substr(uniqid('', true), -4).')'; |
||
1085 | } |
||
1086 | |||
1087 | $name = substr($name, 0, -(strlen($ext) + 1)); |
||
1088 | |||
1089 | return $name.' ('.substr(uniqid('', true), -4).')'.'.'.$ext; |
||
1090 | } |
||
1091 | |||
1092 | /** |
||
1093 | * Completly remove node. |
||
1094 | */ |
||
1095 | abstract protected function _forceDelete(): bool; |
||
1096 | } |
||
1097 |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.