Completed
Push — master ( 989ca3...ab5d30 )
by
unknown
39:06 queued 17:14
created

AbstractFile::copyTo()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 3
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
namespace TYPO3\CMS\Core\Resource;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use TYPO3\CMS\Core\Utility\GeneralUtility;
18
use TYPO3\CMS\Core\Utility\MathUtility;
19
use TYPO3\CMS\Core\Utility\PathUtility;
20
21
/**
22
 * Abstract file representation in the file abstraction layer.
23
 */
24
abstract class AbstractFile implements FileInterface
25
{
26
    /**
27
     * Various file properties
28
     *
29
     * Note that all properties, which only the persisted (indexed) files have are stored in this
30
     * overall properties array only. The only properties which really exist as object properties of
31
     * the file object are the storage, the identifier, the fileName and the indexing status.
32
     *
33
     * @var array
34
     */
35
    protected $properties;
36
37
    /**
38
     * The storage this file is located in
39
     *
40
     * @var ResourceStorage
41
     */
42
    protected $storage;
43
44
    /**
45
     * The identifier of this file to identify it on the storage.
46
     * On some drivers, this is the path to the file, but drivers could also just
47
     * provide any other unique identifier for this file on the specific storage.
48
     *
49
     * @var string
50
     */
51
    protected $identifier;
52
53
    /**
54
     * The file name of this file
55
     *
56
     * @var string
57
     */
58
    protected $name;
59
60
    /**
61
     * If set to true, this file is regarded as being deleted.
62
     *
63
     * @var bool
64
     */
65
    protected $deleted = false;
66
67
    /**
68
     * any other file
69
     */
70
    const FILETYPE_UNKNOWN = 0;
71
72
    /**
73
     * Any kind of text
74
     * @see http://www.iana.org/assignments/media-types/text
75
     */
76
    const FILETYPE_TEXT = 1;
77
78
    /**
79
     * Any kind of image
80
     * @see http://www.iana.org/assignments/media-types/image
81
     */
82
    const FILETYPE_IMAGE = 2;
83
84
    /**
85
     * Any kind of audio file
86
     * @see http://www.iana.org/assignments/media-types/audio
87
     */
88
    const FILETYPE_AUDIO = 3;
89
90
    /**
91
     * Any kind of video
92
     * @see http://www.iana.org/assignments/media-types/video
93
     */
94
    const FILETYPE_VIDEO = 4;
95
96
    /**
97
     * Any kind of application
98
     * @see http://www.iana.org/assignments/media-types/application
99
     */
100
    const FILETYPE_APPLICATION = 5;
101
102
    /******************
103
     * VARIOUS FILE PROPERTY GETTERS
104
     ******************/
105
    /**
106
     * Returns true if the given property key exists for this file.
107
     *
108
     * @param string $key
109
     * @return bool
110
     */
111
    public function hasProperty($key)
112
    {
113
        return array_key_exists($key, $this->properties);
114
    }
115
116
    /**
117
     * Returns a property value
118
     *
119
     * @param string $key
120
     * @return mixed Property value
121
     */
122
    public function getProperty($key)
123
    {
124
        if ($this->hasProperty($key)) {
125
            return $this->properties[$key];
126
        }
127
        return null;
128
    }
129
130
    /**
131
     * Returns the properties of this object.
132
     *
133
     * @return array
134
     */
135
    public function getProperties()
136
    {
137
        return $this->properties;
138
    }
139
140
    /**
141
     * Returns the identifier of this file
142
     *
143
     * @return string
144
     */
145
    public function getIdentifier()
146
    {
147
        return $this->identifier;
148
    }
149
150
    /**
151
     * Get hashed identifier
152
     *
153
     * @return string
154
     */
155
    public function getHashedIdentifier()
156
    {
157
        return $this->properties['identifier_hash'];
158
    }
159
160
    /**
161
     * Returns the name of this file
162
     *
163
     * @return string
164
     */
165
    public function getName()
166
    {
167
        // Do not check if file has been deleted because we might need the
168
        // name for undeleting it.
169
        return $this->name;
170
    }
171
172
    /**
173
     * Returns the basename (the name without extension) of this file.
174
     *
175
     * @return string
176
     */
177
    public function getNameWithoutExtension()
178
    {
179
        return PathUtility::pathinfo($this->getName(), PATHINFO_FILENAME);
0 ignored issues
show
Bug Best Practice introduced by
The expression return TYPO3\CMS\Core\Ut...urce\PATHINFO_FILENAME) also could return the type array which is incompatible with the documented return type string.
Loading history...
180
    }
181
182
    /**
183
     * Returns the size of this file
184
     *
185
     * @throws \RuntimeException
186
     * @return int|null Returns null if size is not available for the file
187
     */
188
    public function getSize()
189
    {
190
        if ($this->deleted) {
191
            throw new \RuntimeException('File has been deleted.', 1329821480);
192
        }
193
        if (empty($this->properties['size'])) {
194
            $size = array_pop($this->getStorage()->getFileInfoByIdentifier($this->getIdentifier(), ['size']));
0 ignored issues
show
Bug introduced by
$this->getStorage()->get...ifier(), array('size')) cannot be passed to array_pop() as the parameter $array expects a reference. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

194
            $size = array_pop(/** @scrutinizer ignore-type */ $this->getStorage()->getFileInfoByIdentifier($this->getIdentifier(), ['size']));
Loading history...
195
        } else {
196
            $size = $this->properties['size'];
197
        }
198
        return $size ? (int)$size : null;
199
    }
200
201
    /**
202
     * Returns the uid of this file
203
     *
204
     * @return int
205
     */
206
    public function getUid()
207
    {
208
        return (int)$this->getProperty('uid');
209
    }
210
211
    /**
212
     * Returns the Sha1 of this file
213
     *
214
     * @throws \RuntimeException
215
     * @return string
216
     */
217
    public function getSha1()
218
    {
219
        if ($this->deleted) {
220
            throw new \RuntimeException('File has been deleted.', 1329821481);
221
        }
222
        return $this->getStorage()->hashFile($this, 'sha1');
223
    }
224
225
    /**
226
     * Returns the creation time of the file as Unix timestamp
227
     *
228
     * @throws \RuntimeException
229
     * @return int
230
     */
231
    public function getCreationTime()
232
    {
233
        if ($this->deleted) {
234
            throw new \RuntimeException('File has been deleted.', 1329821487);
235
        }
236
        return (int)$this->getProperty('creation_date');
237
    }
238
239
    /**
240
     * Returns the date (as UNIX timestamp) the file was last modified.
241
     *
242
     * @throws \RuntimeException
243
     * @return int
244
     */
245
    public function getModificationTime()
246
    {
247
        if ($this->deleted) {
248
            throw new \RuntimeException('File has been deleted.', 1329821488);
249
        }
250
        return (int)$this->getProperty('modification_date');
251
    }
252
253
    /**
254
     * Get the extension of this file in a lower-case variant
255
     *
256
     * @return string The file extension
257
     */
258
    public function getExtension()
259
    {
260
        $pathinfo = PathUtility::pathinfo($this->getName());
261
262
        $extension = strtolower($pathinfo['extension'] ?? '');
263
264
        return $extension;
265
    }
266
267
    /**
268
     * Get the MIME type of this file
269
     *
270
     * @return string mime type
271
     */
272
    public function getMimeType()
273
    {
274
        return $this->properties['mime_type'] ?: array_pop($this->getStorage()->getFileInfoByIdentifier($this->getIdentifier(), ['mimetype']));
0 ignored issues
show
Bug introduced by
$this->getStorage()->get...r(), array('mimetype')) cannot be passed to array_pop() as the parameter $array expects a reference. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

274
        return $this->properties['mime_type'] ?: array_pop(/** @scrutinizer ignore-type */ $this->getStorage()->getFileInfoByIdentifier($this->getIdentifier(), ['mimetype']));
Loading history...
275
    }
276
277
    /**
278
     * Returns the fileType of this file
279
     * basically there are only five main "file types"
280
     * "audio"
281
     * "image"
282
     * "software"
283
     * "text"
284
     * "video"
285
     * "other"
286
     * see the constants in this class
287
     *
288
     * @return int $fileType
289
     */
290
    public function getType()
291
    {
292
        // this basically extracts the mimetype and guess the filetype based
293
        // on the first part of the mimetype works for 99% of all cases, and
294
        // we don't need to make an SQL statement like EXT:media does currently
295
        if (!$this->properties['type']) {
296
            $mimeType = $this->getMimeType();
297
            [$fileType] = explode('/', $mimeType);
298
            switch (strtolower($fileType)) {
299
                case 'text':
300
                    $this->properties['type'] = self::FILETYPE_TEXT;
301
                    break;
302
                case 'image':
303
                    $this->properties['type'] = self::FILETYPE_IMAGE;
304
                    break;
305
                case 'audio':
306
                    $this->properties['type'] = self::FILETYPE_AUDIO;
307
                    break;
308
                case 'video':
309
                    $this->properties['type'] = self::FILETYPE_VIDEO;
310
                    break;
311
                case 'application':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
312
313
                case 'software':
314
                    $this->properties['type'] = self::FILETYPE_APPLICATION;
315
                    break;
316
                default:
317
                    $this->properties['type'] = self::FILETYPE_UNKNOWN;
318
            }
319
        }
320
        return (int)$this->properties['type'];
321
    }
322
323
    /**
324
     * Useful to find out if this file can be previewed or resized as image.
325
     * @return bool true if File has an image-extension according to $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
326
     */
327
    public function isImage(): bool
328
    {
329
        return GeneralUtility::inList(strtolower($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] ?? ''), $this->getExtension());
330
    }
331
332
    /**
333
     * Useful to find out if this file has a file extension based on any of the registered media extensions
334
     * @return bool true if File is a media-extension according to $GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext']
335
     */
336
    public function isMediaFile(): bool
337
    {
338
        return GeneralUtility::inList(strtolower($GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext'] ?? ''), $this->getExtension());
339
    }
340
341
    /**
342
     * Useful to find out if this file can be edited.
343
     *
344
     * @return bool true if File is a text-based file extension according to $GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext']
345
     */
346
    public function isTextFile(): bool
347
    {
348
        return GeneralUtility::inList(strtolower($GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'] ?? ''), $this->getExtension());
349
    }
350
    /******************
351
     * CONTENTS RELATED
352
     ******************/
353
    /**
354
     * Get the contents of this file
355
     *
356
     * @throws \RuntimeException
357
     * @return string File contents
358
     */
359
    public function getContents()
360
    {
361
        if ($this->deleted) {
362
            throw new \RuntimeException('File has been deleted.', 1329821479);
363
        }
364
        return $this->getStorage()->getFileContents($this);
365
    }
366
367
    /**
368
     * Replace the current file contents with the given string
369
     *
370
     * @param string $contents The contents to write to the file.
371
     *
372
     * @throws \RuntimeException
373
     * @return File The file object (allows chaining).
374
     */
375
    public function setContents($contents)
376
    {
377
        if ($this->deleted) {
378
            throw new \RuntimeException('File has been deleted.', 1329821478);
379
        }
380
        $this->getStorage()->setFileContents($this, $contents);
381
        return $this;
382
    }
383
384
    /****************************************
385
     * STORAGE AND MANAGEMENT RELATED METHODS
386
     ****************************************/
387
388
    /**
389
     * Get the storage this file is located in
390
     *
391
     * @return ResourceStorage
392
     * @throws \RuntimeException
393
     */
394
    public function getStorage()
395
    {
396
        if ($this->storage === null) {
397
            throw new \RuntimeException('You\'re using fileObjects without a storage.', 1381570091);
398
        }
399
        return $this->storage;
400
    }
401
402
    /**
403
     * Checks if this file exists. This should normally always return TRUE;
404
     * it might only return FALSE when this object has been created from an
405
     * index record without checking for.
406
     *
407
     * @return bool TRUE if this file physically exists
408
     */
409
    public function exists()
410
    {
411
        if ($this->deleted) {
412
            return false;
413
        }
414
        return $this->storage->hasFile($this->getIdentifier());
415
    }
416
417
    /**
418
     * Sets the storage this file is located in. This is only meant for
419
     * \TYPO3\CMS\Core\Resource-internal usage; don't use it to move files.
420
     *
421
     * @internal Should only be used by other parts of the File API (e.g. drivers after moving a file)
422
     * @param ResourceStorage $storage
423
     * @return File
424
     */
425
    public function setStorage(ResourceStorage $storage)
426
    {
427
        $this->storage = $storage;
428
        $this->properties['storage'] = $storage->getUid();
429
        return $this;
430
    }
431
432
    /**
433
     * Set the identifier of this file
434
     *
435
     * @internal Should only be used by other parts of the File API (e.g. drivers after moving a file)
436
     * @param string $identifier
437
     * @return File
438
     */
439
    public function setIdentifier($identifier)
440
    {
441
        $this->identifier = $identifier;
442
        return $this;
443
    }
444
445
    /**
446
     * Returns a combined identifier of this file, i.e. the storage UID and the
447
     * folder identifier separated by a colon ":".
448
     *
449
     * @return string Combined storage and file identifier, e.g. StorageUID:path/and/fileName.png
450
     */
451
    public function getCombinedIdentifier()
452
    {
453
        if (!empty($this->properties['storage']) && MathUtility::canBeInterpretedAsInteger($this->properties['storage'])) {
454
            $combinedIdentifier = $this->properties['storage'] . ':' . $this->getIdentifier();
455
        } else {
456
            $combinedIdentifier = $this->getStorage()->getUid() . ':' . $this->getIdentifier();
457
        }
458
        return $combinedIdentifier;
459
    }
460
461
    /**
462
     * Deletes this file from its storage. This also means that this object becomes useless.
463
     *
464
     * @return bool TRUE if deletion succeeded
465
     */
466
    public function delete()
467
    {
468
        // The storage will mark this file as deleted
469
        $wasDeleted = $this->getStorage()->deleteFile($this);
470
471
        // Unset all properties when deleting the file, as they will be stale anyway
472
        // This needs to happen AFTER the storage deleted the file, because the storage
473
        // emits a signal, which passes the file object to the slots, which may need
474
        // all file properties of the deleted file.
475
        $this->properties = [];
476
477
        return $wasDeleted;
478
    }
479
480
    /**
481
     * Marks this file as deleted. This should only be used inside the
482
     * File Abstraction Layer, as it is a low-level API method.
483
     */
484
    public function setDeleted()
485
    {
486
        $this->deleted = true;
487
    }
488
489
    /**
490
     * Returns TRUE if this file has been deleted
491
     *
492
     * @return bool
493
     */
494
    public function isDeleted()
495
    {
496
        return $this->deleted;
497
    }
498
499
    /**
500
     * Renames this file.
501
     *
502
     * @param string $newName The new file name
503
     *
504
     * @param string $conflictMode
505
     * @return FileInterface
506
     */
507
    public function rename($newName, $conflictMode = DuplicationBehavior::RENAME)
508
    {
509
        if ($this->deleted) {
510
            throw new \RuntimeException('File has been deleted.', 1329821482);
511
        }
512
        return $this->getStorage()->renameFile($this, $newName, $conflictMode);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getStorage...newName, $conflictMode) also could return the type TYPO3\CMS\Core\Resource\ProcessedFile which is incompatible with the return type mandated by TYPO3\CMS\Core\Resource\FileInterface::rename() of TYPO3\CMS\Core\Resource\File.
Loading history...
513
    }
514
515
    /**
516
     * Copies this file into a target folder
517
     *
518
     * @param Folder $targetFolder Folder to copy file into.
519
     * @param string $targetFileName an optional destination fileName
520
     * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
521
     *
522
     * @throws \RuntimeException
523
     * @return File The new (copied) file.
524
     */
525
    public function copyTo(Folder $targetFolder, $targetFileName = null, $conflictMode = DuplicationBehavior::RENAME)
526
    {
527
        if ($this->deleted) {
528
            throw new \RuntimeException('File has been deleted.', 1329821483);
529
        }
530
        return $targetFolder->getStorage()->copyFile($this, $targetFolder, $targetFileName, $conflictMode);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $targetFolder->ge...ileName, $conflictMode) also could return the type TYPO3\CMS\Core\Resource\ProcessedFile which is incompatible with the documented return type TYPO3\CMS\Core\Resource\File.
Loading history...
531
    }
532
533
    /**
534
     * Moves the file into the target folder
535
     *
536
     * @param Folder $targetFolder Folder to move file into.
537
     * @param string $targetFileName an optional destination fileName
538
     * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
539
     *
540
     * @throws \RuntimeException
541
     * @return File This file object, with updated properties.
542
     */
543
    public function moveTo(Folder $targetFolder, $targetFileName = null, $conflictMode = DuplicationBehavior::RENAME)
544
    {
545
        if ($this->deleted) {
546
            throw new \RuntimeException('File has been deleted.', 1329821484);
547
        }
548
        return $targetFolder->getStorage()->moveFile($this, $targetFolder, $targetFileName, $conflictMode);
549
    }
550
551
    /*****************
552
     * SPECIAL METHODS
553
     *****************/
554
    /**
555
     * Returns a publicly accessible URL for this file
556
     *
557
     * WARNING: Access to the file may be restricted by further means, e.g. some
558
     * web-based authentication. You have to take care of this yourself.
559
     *
560
     * @param bool $relativeToCurrentScript Determines whether the URL returned should be relative to the current script, in case it is relative at all (only for the LocalDriver)
561
     * @return string|null NULL if file is deleted, the generated URL otherwise
562
     */
563
    public function getPublicUrl($relativeToCurrentScript = false)
564
    {
565
        if ($this->deleted) {
566
            return null;
567
        }
568
        return $this->getStorage()->getPublicUrl($this, $relativeToCurrentScript);
569
    }
570
571
    /**
572
     * Returns a path to a local version of this file to process it locally (e.g. with some system tool).
573
     * If the file is normally located on a remote storages, this creates a local copy.
574
     * If the file is already on the local system, this only makes a new copy if $writable is set to TRUE.
575
     *
576
     * @param bool $writable Set this to FALSE if you only want to do read operations on the file.
577
     *
578
     * @throws \RuntimeException
579
     * @return string
580
     */
581
    public function getForLocalProcessing($writable = true)
582
    {
583
        if ($this->deleted) {
584
            throw new \RuntimeException('File has been deleted.', 1329821486);
585
        }
586
        return $this->getStorage()->getFileForLocalProcessing($this, $writable);
587
    }
588
589
    /***********************
590
     * INDEX RELATED METHODS
591
     ***********************/
592
    /**
593
     * Updates properties of this object.
594
     * This method is used to reconstitute settings from the
595
     * database into this object after being instantiated.
596
     *
597
     * @param array $properties
598
     */
599
    abstract public function updateProperties(array $properties);
600
601
    /**
602
     * Returns the parent folder.
603
     *
604
     * @return FolderInterface
605
     */
606
    public function getParentFolder()
607
    {
608
        return $this->getStorage()->getFolder($this->getStorage()->getFolderIdentifierFromFileIdentifier($this->getIdentifier()));
609
    }
610
}
611