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