[CLEANUP] Alwas put null at the last position
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Resource / AbstractFile.php
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\MathUtility;
18 use TYPO3\CMS\Core\Utility\PathUtility;
19
20 /**
21 * Abstract file representation in the file abstraction layer.
22 */
23 abstract class AbstractFile implements FileInterface
24 {
25 /**
26 * Various file properties
27 *
28 * Note that all properties, which only the persisted (indexed) files have are stored in this
29 * overall properties array only. The only properties which really exist as object properties of
30 * the file object are the storage, the identifier, the fileName and the indexing status.
31 *
32 * @var array
33 */
34 protected $properties;
35
36 /**
37 * The storage this file is located in
38 *
39 * @var ResourceStorage
40 */
41 protected $storage = null;
42
43 /**
44 * The identifier of this file to identify it on the storage.
45 * On some drivers, this is the path to the file, but drivers could also just
46 * provide any other unique identifier for this file on the specific storage.
47 *
48 * @var string
49 */
50 protected $identifier;
51
52 /**
53 * The file name of this file
54 *
55 * @var string
56 */
57 protected $name;
58
59 /**
60 * If set to true, this file is regarded as being deleted.
61 *
62 * @var bool
63 */
64 protected $deleted = false;
65
66 /**
67 * any other file
68 */
69 const FILETYPE_UNKNOWN = 0;
70
71 /**
72 * Any kind of text
73 * @see http://www.iana.org/assignments/media-types/text
74 */
75 const FILETYPE_TEXT = 1;
76
77 /**
78 * Any kind of image
79 * @see http://www.iana.org/assignments/media-types/image
80 */
81 const FILETYPE_IMAGE = 2;
82
83 /**
84 * Any kind of audio file
85 * @see http://www.iana.org/assignments/media-types/audio
86 */
87 const FILETYPE_AUDIO = 3;
88
89 /**
90 * Any kind of video
91 * @see http://www.iana.org/assignments/media-types/video
92 */
93 const FILETYPE_VIDEO = 4;
94
95 /**
96 * Any kind of application
97 * @see http://www.iana.org/assignments/media-types/application
98 */
99 const FILETYPE_APPLICATION = 5;
100
101 /******************
102 * VARIOUS FILE PROPERTY GETTERS
103 ******************/
104 /**
105 * Returns true if the given property key exists for this file.
106 *
107 * @param string $key
108 * @return bool
109 */
110 public function hasProperty($key)
111 {
112 return array_key_exists($key, $this->properties);
113 }
114
115 /**
116 * Returns a property value
117 *
118 * @param string $key
119 * @return mixed Property value
120 */
121 public function getProperty($key)
122 {
123 if ($this->hasProperty($key)) {
124 return $this->properties[$key];
125 }
126 return null;
127 }
128
129 /**
130 * Returns the properties of this object.
131 *
132 * @return array
133 */
134 public function getProperties()
135 {
136 return $this->properties;
137 }
138
139 /**
140 * Returns the identifier of this file
141 *
142 * @return string
143 */
144 public function getIdentifier()
145 {
146 return $this->identifier;
147 }
148
149 /**
150 * Get hashed identifier
151 *
152 * @return string
153 */
154 public function getHashedIdentifier()
155 {
156 return $this->properties['identifier_hash'];
157 }
158
159 /**
160 * Returns the name of this file
161 *
162 * @return string
163 */
164 public function getName()
165 {
166 // Do not check if file has been deleted because we might need the
167 // name for undeleting it.
168 return $this->name;
169 }
170
171 /**
172 * Returns the basename (the name without extension) of this file.
173 *
174 * @return string
175 */
176 public function getNameWithoutExtension()
177 {
178 return PathUtility::pathinfo($this->getName(), PATHINFO_FILENAME);
179 }
180
181 /**
182 * Returns the size of this file
183 *
184 * @throws \RuntimeException
185 * @return int|null Returns null if size is not available for the file
186 */
187 public function getSize()
188 {
189 if ($this->deleted) {
190 throw new \RuntimeException('File has been deleted.', 1329821480);
191 }
192 if (empty($this->properties['size'])) {
193 $size = array_pop($this->getStorage()->getFileInfoByIdentifier($this->getIdentifier(), ['size']));
194 } else {
195 $size = $this->properties['size'];
196 }
197 return $size ? (int)$size : null;
198 }
199
200 /**
201 * Returns the uid of this file
202 *
203 * @return int
204 */
205 public function getUid()
206 {
207 return (int)$this->getProperty('uid');
208 }
209
210 /**
211 * Returns the Sha1 of this file
212 *
213 * @throws \RuntimeException
214 * @return string
215 */
216 public function getSha1()
217 {
218 if ($this->deleted) {
219 throw new \RuntimeException('File has been deleted.', 1329821481);
220 }
221 return $this->getStorage()->hashFile($this, 'sha1');
222 }
223
224 /**
225 * Returns the creation time of the file as Unix timestamp
226 *
227 * @throws \RuntimeException
228 * @return int
229 */
230 public function getCreationTime()
231 {
232 if ($this->deleted) {
233 throw new \RuntimeException('File has been deleted.', 1329821487);
234 }
235 return (int)$this->getProperty('creation_date');
236 }
237
238 /**
239 * Returns the date (as UNIX timestamp) the file was last modified.
240 *
241 * @throws \RuntimeException
242 * @return int
243 */
244 public function getModificationTime()
245 {
246 if ($this->deleted) {
247 throw new \RuntimeException('File has been deleted.', 1329821488);
248 }
249 return (int)$this->getProperty('modification_date');
250 }
251
252 /**
253 * Get the extension of this file in a lower-case variant
254 *
255 * @return string The file extension
256 */
257 public function getExtension()
258 {
259 $pathinfo = PathUtility::pathinfo($this->getName());
260
261 $extension = strtolower($pathinfo['extension']);
262
263 return $extension;
264 }
265
266 /**
267 * Get the MIME type of this file
268 *
269 * @return string mime type
270 */
271 public function getMimeType()
272 {
273 return $this->properties['mime_type'] ?: array_pop($this->getStorage()->getFileInfoByIdentifier($this->getIdentifier(), ['mimetype']));
274 }
275
276 /**
277 * Returns the fileType of this file
278 * basically there are only five main "file types"
279 * "audio"
280 * "image"
281 * "software"
282 * "text"
283 * "video"
284 * "other"
285 * see the constants in this class
286 *
287 * @return int $fileType
288 */
289 public function getType()
290 {
291 // this basically extracts the mimetype and guess the filetype based
292 // on the first part of the mimetype works for 99% of all cases, and
293 // we don't need to make an SQL statement like EXT:media does currently
294 if (!$this->properties['type']) {
295 $mimeType = $this->getMimeType();
296 list($fileType) = explode('/', $mimeType);
297 switch (strtolower($fileType)) {
298 case 'text':
299 $this->properties['type'] = self::FILETYPE_TEXT;
300 break;
301 case 'image':
302 $this->properties['type'] = self::FILETYPE_IMAGE;
303 break;
304 case 'audio':
305 $this->properties['type'] = self::FILETYPE_AUDIO;
306 break;
307 case 'video':
308 $this->properties['type'] = self::FILETYPE_VIDEO;
309 break;
310 case 'application':
311
312 case 'software':
313 $this->properties['type'] = self::FILETYPE_APPLICATION;
314 break;
315 default:
316 $this->properties['type'] = self::FILETYPE_UNKNOWN;
317 }
318 }
319 return (int)$this->properties['type'];
320 }
321
322 /******************
323 * CONTENTS RELATED
324 ******************/
325 /**
326 * Get the contents of this file
327 *
328 * @throws \RuntimeException
329 * @return string File contents
330 */
331 public function getContents()
332 {
333 if ($this->deleted) {
334 throw new \RuntimeException('File has been deleted.', 1329821479);
335 }
336 return $this->getStorage()->getFileContents($this);
337 }
338
339 /**
340 * Replace the current file contents with the given string
341 *
342 * @param string $contents The contents to write to the file.
343 *
344 * @throws \RuntimeException
345 * @return File The file object (allows chaining).
346 */
347 public function setContents($contents)
348 {
349 if ($this->deleted) {
350 throw new \RuntimeException('File has been deleted.', 1329821478);
351 }
352 $this->getStorage()->setFileContents($this, $contents);
353 return $this;
354 }
355
356 /****************************************
357 * STORAGE AND MANAGEMENT RELATED METHDOS
358 ****************************************/
359
360 /**
361 * Get the storage this file is located in
362 *
363 * @return ResourceStorage
364 * @throws \RuntimeException
365 */
366 public function getStorage()
367 {
368 if ($this->storage === null) {
369 throw new \RuntimeException('You\'re using fileObjects without a storage.', 1381570091);
370 }
371 return $this->storage;
372 }
373
374 /**
375 * Checks if this file exists. This should normally always return TRUE;
376 * it might only return FALSE when this object has been created from an
377 * index record without checking for.
378 *
379 * @return bool TRUE if this file physically exists
380 */
381 public function exists()
382 {
383 if ($this->deleted) {
384 return false;
385 }
386 return $this->storage->hasFile($this->getIdentifier());
387 }
388
389 /**
390 * Sets the storage this file is located in. This is only meant for
391 * \TYPO3\CMS\Core\Resource-internal usage; don't use it to move files.
392 *
393 * @internal Should only be used by other parts of the File API (e.g. drivers after moving a file)
394 * @param ResourceStorage $storage
395 * @return File
396 */
397 public function setStorage(ResourceStorage $storage)
398 {
399 $this->storage = $storage;
400 $this->properties['storage'] = $storage->getUid();
401 return $this;
402 }
403
404 /**
405 * Set the identifier of this file
406 *
407 * @internal Should only be used by other parts of the File API (e.g. drivers after moving a file)
408 * @param string $identifier
409 * @return File
410 */
411 public function setIdentifier($identifier)
412 {
413 $this->identifier = $identifier;
414 return $this;
415 }
416
417 /**
418 * Returns a combined identifier of this file, i.e. the storage UID and the
419 * folder identifier separated by a colon ":".
420 *
421 * @return string Combined storage and file identifier, e.g. StorageUID:path/and/fileName.png
422 */
423 public function getCombinedIdentifier()
424 {
425 if (is_array($this->properties) && MathUtility::canBeInterpretedAsInteger($this->properties['storage'])) {
426 $combinedIdentifier = $this->properties['storage'] . ':' . $this->getIdentifier();
427 } else {
428 $combinedIdentifier = $this->getStorage()->getUid() . ':' . $this->getIdentifier();
429 }
430 return $combinedIdentifier;
431 }
432
433 /**
434 * Deletes this file from its storage. This also means that this object becomes useless.
435 *
436 * @return bool TRUE if deletion succeeded
437 */
438 public function delete()
439 {
440 // The storage will mark this file as deleted
441 $wasDeleted = $this->getStorage()->deleteFile($this);
442
443 // Unset all properties when deleting the file, as they will be stale anyway
444 // This needs to happen AFTER the storage deleted the file, because the storage
445 // emits a signal, which passes the file object to the slots, which may need
446 // all file properties of the deleted file.
447 $this->properties = [];
448
449 return $wasDeleted;
450 }
451
452 /**
453 * Marks this file as deleted. This should only be used inside the
454 * File Abstraction Layer, as it is a low-level API method.
455 */
456 public function setDeleted()
457 {
458 $this->deleted = true;
459 }
460
461 /**
462 * Returns TRUE if this file has been deleted
463 *
464 * @return bool
465 */
466 public function isDeleted()
467 {
468 return $this->deleted;
469 }
470
471 /**
472 * Renames this file.
473 *
474 * @param string $newName The new file name
475 *
476 * @param string $conflictMode
477 * @return FileInterface
478 */
479 public function rename($newName, $conflictMode = DuplicationBehavior::RENAME)
480 {
481 if ($this->deleted) {
482 throw new \RuntimeException('File has been deleted.', 1329821482);
483 }
484 return $this->getStorage()->renameFile($this, $newName, $conflictMode);
485 }
486
487 /**
488 * Copies this file into a target folder
489 *
490 * @param Folder $targetFolder Folder to copy file into.
491 * @param string $targetFileName an optional destination fileName
492 * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
493 *
494 * @throws \RuntimeException
495 * @return File The new (copied) file.
496 */
497 public function copyTo(Folder $targetFolder, $targetFileName = null, $conflictMode = DuplicationBehavior::RENAME)
498 {
499 if ($this->deleted) {
500 throw new \RuntimeException('File has been deleted.', 1329821483);
501 }
502 return $targetFolder->getStorage()->copyFile($this, $targetFolder, $targetFileName, $conflictMode);
503 }
504
505 /**
506 * Moves the file into the target folder
507 *
508 * @param Folder $targetFolder Folder to move file into.
509 * @param string $targetFileName an optional destination fileName
510 * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
511 *
512 * @throws \RuntimeException
513 * @return File This file object, with updated properties.
514 */
515 public function moveTo(Folder $targetFolder, $targetFileName = null, $conflictMode = DuplicationBehavior::RENAME)
516 {
517 if ($this->deleted) {
518 throw new \RuntimeException('File has been deleted.', 1329821484);
519 }
520 return $targetFolder->getStorage()->moveFile($this, $targetFolder, $targetFileName, $conflictMode);
521 }
522
523 /*****************
524 * SPECIAL METHODS
525 *****************/
526 /**
527 * Returns a publicly accessible URL for this file
528 *
529 * WARNING: Access to the file may be restricted by further means, e.g. some
530 * web-based authentication. You have to take care of this yourself.
531 *
532 * @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)
533 * @return string|null NULL if file is deleted, the generated URL otherwise
534 */
535 public function getPublicUrl($relativeToCurrentScript = false)
536 {
537 if ($this->deleted) {
538 return null;
539 }
540 return $this->getStorage()->getPublicUrl($this, $relativeToCurrentScript);
541 }
542
543 /**
544 * Returns a path to a local version of this file to process it locally (e.g. with some system tool).
545 * If the file is normally located on a remote storages, this creates a local copy.
546 * If the file is already on the local system, this only makes a new copy if $writable is set to TRUE.
547 *
548 * @param bool $writable Set this to FALSE if you only want to do read operations on the file.
549 *
550 * @throws \RuntimeException
551 * @return string
552 */
553 public function getForLocalProcessing($writable = true)
554 {
555 if ($this->deleted) {
556 throw new \RuntimeException('File has been deleted.', 1329821486);
557 }
558 return $this->getStorage()->getFileForLocalProcessing($this, $writable);
559 }
560
561 /***********************
562 * INDEX RELATED METHODS
563 ***********************/
564 /**
565 * Updates properties of this object.
566 * This method is used to reconstitute settings from the
567 * database into this object after being intantiated.
568 *
569 * @param array $properties
570 */
571 abstract public function updateProperties(array $properties);
572
573 /**
574 * Returns the parent folder.
575 *
576 * @return FolderInterface
577 */
578 public function getParentFolder()
579 {
580 return $this->getStorage()->getFolder($this->getStorage()->getFolderIdentifierFromFileIdentifier($this->getIdentifier()));
581 }
582 }