Completed
Branch master (3adcdb)
by Raffael
08:09 queued 04:17
created

File::restore()   C

Complexity

Conditions 9
Paths 62

Size

Total Lines 93
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 93
rs 5.0669
c 0
b 0
f 0
cc 9
eloc 57
nc 62
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * balloon
7
 *
8
 * @copyright   Copryright (c) 2012-2018 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\Filesystem\Storage;
19
use Balloon\Hook;
20
use Balloon\Mime;
21
use Balloon\Server\User;
22
use MongoDB\BSON\UTCDateTime;
23
use Psr\Log\LoggerInterface;
24
use Sabre\DAV\IFile;
25
26
class File extends AbstractNode implements IFile
27
{
28
    /**
29
     * History types.
30
     */
31
    const HISTORY_CREATE = 0;
32
    const HISTORY_EDIT = 1;
33
    const HISTORY_RESTORE = 2;
34
    const HISTORY_DELETE = 3;
35
    const HISTORY_UNDELETE = 4;
36
37
    /**
38
     * Empty content hash (NULL).
39
     */
40
    const EMPTY_CONTENT = 'd41d8cd98f00b204e9800998ecf8427e';
41
42
    /**
43
     * Temporary file patterns.
44
     *
45
     * @param array
46
     **/
47
    protected $temp_files = [
48
        '/^\._(.*)$/',     // OS/X resource forks
49
        '/^.DS_Store$/',   // OS/X custom folder settings
50
        '/^desktop.ini$/', // Windows custom folder settings
51
        '/^Thumbs.db$/',   // Windows thumbnail cache
52
        '/^.(.*).swpx$/',  // ViM temporary files
53
        '/^.(.*).swx$/',   // ViM temporary files
54
        '/^.(.*).swp$/',   // ViM temporary files
55
        '/^\.dat(.*)$/',   // Smultron seems to create these
56
        '/^~lock.(.*)#$/', // Windows 7 lockfiles
57
    ];
58
59
    /**
60
     * MD5 Hash of the content.
61
     *
62
     * @var string
63
     */
64
    protected $hash;
65
66
    /**
67
     * File version.
68
     *
69
     * @var int
70
     */
71
    protected $version = 0;
72
73
    /**
74
     * File size.
75
     *
76
     * @var int
77
     */
78
    protected $size = 0;
79
80
    /**
81
     * History.
82
     *
83
     * @var array
84
     */
85
    protected $history = [];
86
87
    /**
88
     * Storage.
89
     *
90
     * @var Storage
91
     */
92
    protected $_storage;
93
94
    /**
95
     * Storage attributes.
96
     *
97
     * @var mixed
98
     */
99
    protected $storage;
100
101
    /**
102
     * Initialize file node.
103
     *
104
     * @param array      $attributes
105
     * @param Filesystem $fs
106
     */
107
    public function __construct(array $attributes, Filesystem $fs, LoggerInterface $logger, Hook $hook, Acl $acl, Storage $storage)
108
    {
109
        $this->_fs = $fs;
110
        $this->_server = $fs->getServer();
111
        $this->_db = $fs->getDatabase();
112
        $this->_user = $fs->getUser();
113
        $this->_logger = $logger;
114
        $this->_hook = $hook;
115
        $this->_storage = $storage;
116
        $this->_acl = $acl;
117
118
        foreach ($attributes as $attr => $value) {
119
            $this->{$attr} = $value;
120
        }
121
122
        $this->raw_attributes = $attributes;
123
    }
124
125
    /**
126
     * Read content and return ressource.
127
     *
128
     * @return resource
129
     */
130
    public function get()
131
    {
132
        try {
133
            if (null === $this->storage) {
134
                return null;
135
            }
136
137
            return $this->_storage->getFile($this, $this->storage);
138
        } catch (\Exception $e) {
139
            throw new Exception\NotFound(
140
                'content not found',
141
                Exception\NotFound::CONTENTS_NOT_FOUND
142
            );
143
        }
144
    }
145
146
    /**
147
     * Copy node.
148
     *
149
     * @param Collection $parent
150
     * @param int        $conflict
151
     * @param string     $recursion
152
     * @param bool       $recursion_first
153
     *
154
     * @return NodeInterface
155
     */
156
    public function copyTo(Collection $parent, int $conflict = NodeInterface::CONFLICT_NOACTION, ?string $recursion = null, bool $recursion_first = true): NodeInterface
157
    {
158
        $this->_hook->run(
159
            'preCopyFile',
160
            [$this, $parent, &$conflict, &$recursion, &$recursion_first]
161
        );
162
163
        if (NodeInterface::CONFLICT_RENAME === $conflict && $parent->childExists($this->name)) {
164
            $name = $this->getDuplicateName();
165
        } else {
166
            $name = $this->name;
167
        }
168
169
        if (NodeInterface::CONFLICT_MERGE === $conflict && $parent->childExists($this->name)) {
170
            $result = $parent->getChild($this->name);
171
            $result->put($this->get());
172
        } else {
173
            $result = $parent->addFile($name, $this->get(), [
174
                'created' => $this->created,
175
                'changed' => $this->changed,
176
                'deleted' => $this->deleted,
177
                'meta' => $this->meta,
178
            ], NodeInterface::CONFLICT_NOACTION, true);
179
        }
180
181
        $this->_hook->run(
182
            'postCopyFile',
183
            [$this, $parent, $result, $conflict, $recursion, $recursion_first]
184
        );
185
186
        return $result;
187
    }
188
189
    /**
190
     * Get history.
191
     *
192
     * @return array
193
     */
194
    public function getHistory(): array
195
    {
196
        return $this->history;
197
    }
198
199
    /**
200
     * Restore content to some older version.
201
     *
202
     * @param int $version
203
     *
204
     * @return bool
205
     */
206
    public function restore(int $version): bool
207
    {
208
        if (!$this->_acl->isAllowed($this, 'w')) {
209
            throw new ForbiddenException(
210
                'not allowed to restore node '.$this->name,
211
                ForbiddenException::NOT_ALLOWED_TO_RESTORE
212
            );
213
        }
214
215
        $this->_hook->run('preRestoreFile', [$this, &$version]);
216
217
        if ($this->readonly) {
218
            throw new Exception\Conflict(
219
                'node is marked as readonly, it is not possible to change any content',
220
                Exception\Conflict::READONLY
221
            );
222
        }
223
224
        if ($this->version === $version) {
225
            throw new Exception('file is already version '.$version);
226
        }
227
228
        $v = array_search($version, array_column($this->history, 'version'), true);
229
        if (null === $v) {
230
            throw new Exception('failed restore file to version '.$version.', version was not found');
231
        }
232
233
        $file = $this->history[$v]['storage'];
234
        /*$exists = [];
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
235
236
        if (null !== $file) {
237
            try {
238
                $exists = $this->_storage->getFileMeta($this, $this->history[$v]['storage']);
239
            } catch (\Exception $e) {
240
                throw new Exception('could not restore to version '.$version.', version content does not exists');
241
            }
242
        }*/
243
244
        $current = $this->version;
0 ignored issues
show
Unused Code introduced by
$current is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
245
        $new = $this->increaseVersion();
246
247
        $this->history[] = [
248
            'version' => $new,
249
            'changed' => $this->changed,
250
            'user' => $this->owner,
251
            'type' => self::HISTORY_RESTORE,
252
            'hash' => $this->history[$v]['hash'],
253
            'origin' => $this->history[$v]['version'],
254
            'storage' => $this->history[$v]['storage'],
255
            'storage_adapter' => $this->history[$v]['storage_adapter'],
256
            'size' => $this->history[$v]['size'],
257
            'mime' => isset($this->history[$v]['mime']) ? $this->history[$v]['mime'] : null,
258
        ];
259
260
        try {
261
            $this->deleted = false;
262
            $this->version = $new;
263
            $this->storage = $this->history[$v]['storage'];
264
            $this->storage_adapter = $this->history[$v]['storage_adapter'];
265
266
            $this->hash = null === $file ? self::EMPTY_CONTENT : $this->history[$v]['hash'];
267
            $this->mime = isset($this->history[$v]['mime']) ? $this->history[$v]['mime'] : null;
268
            $this->size = $this->history[$v]['size'];
269
            $this->changed = $this->history[$v]['changed'];
270
271
            $this->save([
272
                'deleted',
273
                'version',
274
                'storage',
275
                'storage_adapter',
276
                'hash',
277
                'mime',
278
                'size',
279
                'history',
280
                'changed',
281
            ]);
282
283
            $this->_hook->run('postRestoreFile', [$this, &$version]);
284
285
            $this->_logger->info('restored file ['.$this->_id.'] to version ['.$version.']', [
286
                'category' => get_class($this),
287
            ]);
288
        } catch (\Exception $e) {
289
            $this->_logger->error('failed restore file ['.$this->_id.'] to version ['.$version.']', [
290
                'category' => get_class($this),
291
                'exception' => $e,
292
            ]);
293
294
            throw $e;
295
        }
296
297
        return true;
298
    }
299
300
    /**
301
     * Delete node.
302
     *
303
     * Actually the node will not be deleted (Just set a delete flag), set $force=true to
304
     * delete finally
305
     *
306
     * @param bool   $force
307
     * @param string $recursion
308
     * @param bool   $recursion_first
309
     *
310
     * @return bool
311
     */
312
    public function delete(bool $force = false, ?string $recursion = null, bool $recursion_first = true): bool
313
    {
314
        if (!$this->_acl->isAllowed($this, 'w')) {
315
            throw new ForbiddenException(
316
                'not allowed to delete node '.$this->name,
317
                ForbiddenException::NOT_ALLOWED_TO_DELETE
318
            );
319
        }
320
321
        $this->_hook->run('preDeleteFile', [$this, &$force, &$recursion, &$recursion_first]);
322
323
        if ($this->readonly && null !== $this->_user) {
324
            throw new Exception\Conflict(
325
                'node is marked as readonly, it is not possible to delete it',
326
                Exception\Conflict::READONLY
327
            );
328
        }
329
330
        if (true === $force || $this->isTemporaryFile()) {
331
            $result = $this->_forceDelete();
332
            $this->_hook->run('postDeleteFile', [$this, $force, $recursion, $recursion_first]);
333
334
            return $result;
335
        }
336
337
        $ts = new UTCDateTime();
338
        $this->deleted = $ts;
339
        $this->increaseVersion();
340
341
        $this->history[] = [
342
            'version' => $this->version,
343
            'changed' => $ts,
344
            'user' => ($this->_user === null) ? null : $this->_user->getId(),
345
            'type' => self::HISTORY_DELETE,
346
            'storage' => $this->storage,
347
            'storage_adapter' => $this->storage_adapter,
348
            'size' => $this->size,
349
            'hash' => $this->hash,
350
        ];
351
352
        $result = $this->save([
353
            'version',
354
            'deleted',
355
            'history',
356
        ], [], $recursion, $recursion_first);
357
358
        $this->_hook->run('postDeleteFile', [$this, $force, $recursion, $recursion_first]);
359
360
        return $result;
361
    }
362
363
    /**
364
     * Check if file is temporary.
365
     *
366
     * @return bool
367
     **/
368
    public function isTemporaryFile(): bool
369
    {
370
        foreach ($this->temp_files as $pattern) {
371
            if (preg_match($pattern, $this->name)) {
372
                return true;
373
            }
374
        }
375
376
        return false;
377
    }
378
379
    /**
380
     * Delete version.
381
     *
382
     * @param int $version
383
     *
384
     * @return bool
385
     */
386
    public function deleteVersion(int $version): bool
387
    {
388
        $key = array_search($version, array_column($this->history, 'version'), true);
389
390
        if (false === $key) {
391
            throw new Exception('version '.$version.' does not exists');
392
        }
393
394
        try {
395
            if ($this->history[$key]['storage'] !== null) {
396
                $this->_storage->deleteFile($this, $this->history[$key]['storage']);
397
            }
398
399
            array_splice($this->history, $key, 1);
400
401
            $this->_logger->debug('removed version ['.$version.'] from file ['.$this->_id.']', [
402
                'category' => get_class($this),
403
            ]);
404
405
            return $this->save('history');
406
        } catch (\Exception $e) {
407
            $this->_logger->error('failed remove version ['.$version.'] from file ['.$this->_id.']', [
408
                'category' => get_class($this),
409
                'exception' => $e,
410
            ]);
411
412
            throw $e;
413
        }
414
    }
415
416
    /**
417
     * Cleanup history.
418
     *
419
     * @return bool
420
     */
421
    public function cleanHistory(): bool
422
    {
423
        foreach ($this->history as $node) {
424
            $this->deleteVersion($node['version']);
425
        }
426
427
        return true;
428
    }
429
430
    /**
431
     * Get Attributes.
432
     *
433
     * @return array
434
     */
435
    public function getAttributes(): array
436
    {
437
        return [
438
            '_id' => $this->_id,
439
            'name' => $this->name,
440
            'hash' => $this->hash,
441
            'directory' => false,
442
            'size' => $this->size,
443
            'version' => $this->version,
444
            'parent' => $this->parent,
445
            'acl' => $this->acl,
446
            'app' => $this->app,
447
            'meta' => $this->meta,
448
            'mime' => $this->mime,
449
            'owner' => $this->owner,
450
            'history' => $this->history,
451
            'shared' => $this->shared,
452
            'deleted' => $this->deleted,
453
            'changed' => $this->changed,
454
            'created' => $this->created,
455
            'destroy' => $this->destroy,
456
            'readonly' => $this->readonly,
457
            'storage_adapter' => $this->storage_adapter,
458
            'storage' => $this->storage,
459
        ];
460
    }
461
462
    /**
463
     * Get filename extension.
464
     *
465
     * @return string
466
     */
467
    public function getExtension(): string
468
    {
469
        $ext = strrchr($this->name, '.');
470
        if (false === $ext) {
471
            throw new Exception('file does not have an extension');
472
        }
473
474
        return substr($ext, 1);
475
    }
476
477
    /**
478
     * Get file size.
479
     *
480
     * @return int
481
     */
482
    public function getSize(): int
483
    {
484
        return $this->size;
485
    }
486
487
    /**
488
     * Get md5 sum of the file content,
489
     * actually the hash value comes from the database.
490
     *
491
     * @return string
492
     */
493
    public function getETag(): string
494
    {
495
        return "'".$this->hash."'";
496
    }
497
498
    /**
499
     * Get hash.
500
     *
501
     * @return string
502
     */
503
    public function getHash(): ?string
504
    {
505
        return $this->hash;
506
    }
507
508
    /**
509
     * Get version.
510
     *
511
     * @return int
512
     */
513
    public function getVersion(): int
514
    {
515
        return $this->version;
516
    }
517
518
    /**
519
     * Change content.
520
     *
521
     * @param resource|string $file
522
     * @param bool            $new
523
     * @param array           $attributes
524
     *
525
     * @return int
526
     */
527
    public function put($file, bool $new = false, array $attributes = []): int
528
    {
529
        $this->_logger->debug('add contents for file ['.$this->_id.']', [
530
            'category' => get_class($this),
531
        ]);
532
533
        $this->validatePutRequest($file, $new, $attributes);
534
        $file = $this->createTemporaryFile($file, $stream);
535
        $new_hash = $this->verifyFile($file, $new);
536
537
        if ($this->hash === $new_hash) {
538
            $this->_logger->info('stop PUT execution, content checksums are equal for file ['.$this->_id.']', [
539
                'category' => get_class($this),
540
            ]);
541
542
            //Remove tmp file
543
            if (null !== $file) {
544
                unlink($file);
545
                fclose($stream);
546
            }
547
548
            return $this->version;
549
        }
550
551
        $this->hash = $new_hash;
552
        $max = (int) (string) $this->_fs->getServer()->getMaxFileVersion();
553
        if (count($this->history) >= $max) {
554
            $del = key($this->history);
555
            $this->_logger->debug('history limit ['.$max.'] reached, remove oldest version ['.$del.'] from file ['.$this->_id.']', [
556
                'category' => get_class($this),
557
            ]);
558
559
            $this->deleteVersion($this->history[$del]['version']);
560
        }
561
562
        //Write new content
563
        if ($this->size > 0) {
564
            $this->storage = $this->_storage->storeFile($this, $stream, $this->storage_adapter);
565
        } else {
566
            $this->storage = null;
567
        }
568
569
        //Update current version
570
        $this->increaseVersion();
571
572
        //Get meta attributes
573
        if (isset($attributes['mime'])) {
574
            $this->mime = $attributes['mime'];
575
        } elseif (null !== $file) {
576
            $this->mime = (new Mime())->getMime($file, $this->name);
577
        }
578
579
        //Remove tmp file
580
        if (null !== $file) {
581
            unlink($file);
582
            fclose($stream);
583
        }
584
585
        $this->_logger->debug('set mime ['.$this->mime.'] for content, file=['.$this->_id.']', [
586
            'category' => get_class($this),
587
        ]);
588
589
        $this->addVersion($attributes)
590
             ->postPutFile($file, $new, $attributes);
591
592
        return $this->version;
593
    }
594
595
    /**
596
     * Completly remove file.
597
     *
598
     * @return bool
599
     */
600
    protected function _forceDelete(): bool
601
    {
602
        try {
603
            $this->cleanHistory();
604
            $this->_db->storage->deleteOne([
605
                '_id' => $this->_id,
606
            ]);
607
608
            $this->_logger->info('removed file node ['.$this->_id.']', [
609
                'category' => get_class($this),
610
            ]);
611
        } catch (\Exception $e) {
612
            $this->_logger->error('failed delete file node ['.$this->_id.']', [
613
                'category' => get_class($this),
614
                'exception' => $e,
615
            ]);
616
617
            throw $e;
618
        }
619
620
        return true;
621
    }
622
623
    /**
624
     * Increase version.
625
     *
626
     * @return int
627
     */
628
    protected function increaseVersion(): int
629
    {
630
        ++$this->version;
631
632
        return $this->version;
633
    }
634
635
    /**
636
     * Create uuidv4.
637
     *
638
     * @param string $data
639
     *
640
     * @return string
641
     */
642
    protected function guidv4(string $data): string
643
    {
644
        assert(16 === strlen($data));
645
646
        $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100
647
        $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10
648
649
        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
650
    }
651
652
    /**
653
     * Change content.
654
     *
655
     * @param resource|string $file
656
     * @param bool            $new
657
     * @param array           $attributes
658
     *
659
     * @return bool
660
     */
661
    protected function validatePutRequest($file, bool $new = false, array $attributes = []): bool
662
    {
663
        if (!$this->_acl->isAllowed($this, 'w')) {
664
            throw new ForbiddenException(
665
                'not allowed to modify node',
666
                ForbiddenException::NOT_ALLOWED_TO_MODIFY
667
            );
668
        }
669
670
        $this->_hook->run('prePutFile', [$this, &$file, &$new, &$attributes]);
671
672
        if ($this->readonly) {
673
            throw new Exception\Conflict(
674
                'node is marked as readonly, it is not possible to change any content',
675
                Exception\Conflict::READONLY
676
            );
677
        }
678
679
        if ($this->isShareMember() && false === $new && 'w' === $this->_acl->getAclPrivilege($this->getShareNode())) {
0 ignored issues
show
Bug introduced by
It seems like $this->getShareNode() can be null; however, getAclPrivilege() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
680
            throw new ForbiddenException(
681
                'not allowed to overwrite node',
682
                ForbiddenException::NOT_ALLOWED_TO_OVERWRITE
683
            );
684
        }
685
686
        return true;
687
    }
688
689
    /**
690
     * Verify content to be added.
691
     *
692
     * @param string $path
693
     * @param bool   $new
694
     *
695
     * @return bool
696
     */
697
    protected function verifyFile(?string $path, bool $new = false): string
698
    {
699
        if (null === $path) {
700
            $this->size = 0;
701
            $new_hash = self::EMPTY_CONTENT;
702
        } else {
703
            $size = filesize($path);
704
            $this->size = $size;
705
            $new_hash = md5_file($path);
706
707
            if (!$this->_user->checkQuota($size)) {
708
                $this->_logger->warning('could not execute PUT, user quota is full', [
709
                    'category' => get_class($this),
710
                ]);
711
712
                if (true === $new) {
713
                    $this->_forceDelete();
714
                }
715
716
                throw new Exception\InsufficientStorage(
717
                    'user quota is full',
718
                    Exception\InsufficientStorage::USER_QUOTA_FULL
719
                );
720
            }
721
        }
722
723
        return $new_hash;
724
    }
725
726
    /**
727
     * Create temporary file.
728
     *
729
     * @param resource|string $file
730
     * @param resource        $stream
731
     *
732
     * @return string
733
     */
734
    protected function createTemporaryFile($file, &$stream): ?string
735
    {
736
        if (is_string($file)) {
737
            if (!is_readable($file)) {
738
                throw new Exception('file does not exists or is not readable');
739
            }
740
741
            $stream = fopen($file, 'r');
742
        } elseif (is_resource($file)) {
743
            $tmp = $this->_fs->getServer()->getTempDir().DIRECTORY_SEPARATOR.'upload'.DIRECTORY_SEPARATOR.$this->_user->getId();
744
            if (!file_exists($tmp)) {
745
                mkdir($tmp, 0700, true);
746
            }
747
748
            $tmp_file = $tmp.DIRECTORY_SEPARATOR.$this->guidv4(openssl_random_pseudo_bytes(16));
749
            $stream = fopen($tmp_file, 'w+');
750
            $size = stream_copy_to_stream($file, $stream, ((int) $this->_fs->getServer()->getMaxFileSize() + 1));
751
            rewind($stream);
752
            fclose($file);
753
754
            if ($size > (int) $this->_fs->getServer()->getMaxFileSize()) {
755
                unlink($tmp_file);
756
757
                throw new Exception\InsufficientStorage(
758
                    'file size exceeded limit',
759
                    Exception\InsufficientStorage::FILE_SIZE_LIMIT
760
                );
761
            }
762
763
            $file = $tmp_file;
764
        } else {
765
            $file = null;
766
        }
767
768
        return $file;
769
    }
770
771
    /**
772
     * Add new version.
773
     *
774
     * @param array $attributes
775
     *
776
     * @return File
777
     */
778
    protected function addVersion(array $attributes = []): self
779
    {
780
        if (1 !== $this->version) {
781
            if (isset($attributes['changed'])) {
782
                if (!($attributes['changed'] instanceof UTCDateTime)) {
0 ignored issues
show
Bug introduced by
The class MongoDB\BSON\UTCDateTime does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
783
                    throw new Exception\InvalidArgument('attribute changed must be an instance of UTCDateTime');
784
                }
785
786
                $this->changed = $attributes['changed'];
787
            } else {
788
                $this->changed = new UTCDateTime();
789
            }
790
791
            $this->_logger->debug('added new history version ['.$this->version.'] for file ['.$this->_id.']', [
792
                'category' => get_class($this),
793
            ]);
794
795
            $this->history[] = [
796
                'version' => $this->version,
797
                'changed' => $this->changed,
798
                'user' => $this->_user->getId(),
799
                'type' => self::HISTORY_EDIT,
800
                'storage' => $this->storage,
801
                'storage_adapter' => $this->storage_adapter,
802
                'size' => $this->size,
803
                'mime' => $this->mime,
804
                'hash' => $this->hash,
805
            ];
806
        } else {
807
            $this->_logger->debug('added first file version [1] for file ['.$this->_id.']', [
808
                'category' => get_class($this),
809
            ]);
810
811
            $this->history[0] = [
812
                'version' => 1,
813
                'changed' => isset($attributes['changed']) ? $attributes['changed'] : new UTCDateTime(),
814
                'user' => $this->owner,
815
                'type' => self::HISTORY_CREATE,
816
                'storage' => $this->storage,
817
                'storage_adapter' => $this->storage_adapter,
818
                'size' => $this->size,
819
                'mime' => $this->mime,
820
                'hash' => $this->hash,
821
            ];
822
        }
823
824
        return $this;
825
    }
826
827
    /**
828
     * Finalize put request.
829
     *
830
     * @param resource|string $file
831
     * @param bool            $new
832
     * @param array           $attributes
833
     *
834
     * @return File
835
     */
836
    protected function postPutFile($file, bool $new, array $attributes): self
837
    {
838
        try {
839
            $this->save([
840
                'size',
841
                'changed',
842
                'mime',
843
                'hash',
844
                'version',
845
                'history',
846
                'storage',
847
                'storage_adapter',
848
            ]);
849
850
            $this->_logger->debug('modifed file metadata ['.$this->_id.']', [
851
                'category' => get_class($this),
852
            ]);
853
854
            $this->_hook->run('postPutFile', [$this, $file, $new, $attributes]);
855
856
            return $this;
857
        } catch (\Exception $e) {
858
            $this->_logger->error('failed modify file metadata ['.$this->_id.']', [
859
                'category' => get_class($this),
860
                'exception' => $e,
861
            ]);
862
863
            throw $e;
864
        }
865
    }
866
}
867