[BUGFIX] Ensure correct return type for AbstractFile getters
[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\PathUtility;
18
19 /**
20 * Abstract file representation in the file abstraction layer.
21 */
22 abstract class AbstractFile implements FileInterface
23 {
24 /**
25 * Various file properties
26 *
27 * Note that all properties, which only the persisted (indexed) files have are stored in this
28 * overall properties array only. The only properties which really exist as object properties of
29 * the file object are the storage, the identifier, the fileName and the indexing status.
30 *
31 * @var array
32 */
33 protected $properties;
34
35 /**
36 * The storage this file is located in
37 *
38 * @var ResourceStorage
39 */
40 protected $storage = null;
41
42 /**
43 * The identifier of this file to identify it on the storage.
44 * On some drivers, this is the path to the file, but drivers could also just
45 * provide any other unique identifier for this file on the specific storage.
46 *
47 * @var string
48 */
49 protected $identifier;
50
51 /**
52 * The file name of this file
53 *
54 * @var string
55 */
56 protected $name;
57
58 /**
59 * If set to true, this file is regarded as being deleted.
60 *
61 * @var bool
62 */
63 protected $deleted = false;
64
65 /**
66 * any other file
67 */
68 const FILETYPE_UNKNOWN = 0;
69
70 /**
71 * Any kind of text
72 * @see http://www.iana.org/assignments/media-types/text
73 */
74 const FILETYPE_TEXT = 1;
75
76 /**
77 * Any kind of image
78 * @see http://www.iana.org/assignments/media-types/image
79 */
80 const FILETYPE_IMAGE = 2;
81
82 /**
83 * Any kind of audio file
84 * @see http://www.iana.org/assignments/media-types/audio
85 */
86 const FILETYPE_AUDIO = 3;
87
88 /**
89 * Any kind of video
90 * @see http://www.iana.org/assignments/media-types/video
91 */
92 const FILETYPE_VIDEO = 4;
93
94 /**
95 * Any kind of application
96 * @see http://www.iana.org/assignments/media-types/application
97 */
98 const FILETYPE_APPLICATION = 5;
99
100 /******************
101 * VARIOUS FILE PROPERTY GETTERS
102 ******************/
103 /**
104 * Returns true if the given property key exists for this file.
105 *
106 * @param string $key
107 * @return bool
108 */
109 public function hasProperty($key)
110 {
111 return array_key_exists($key, $this->properties);
112 }
113
114 /**
115 * Returns a property value
116 *
117 * @param string $key
118 * @return mixed Property value
119 */
120 public function getProperty($key)
121 {
122 if ($this->hasProperty($key)) {
123 return $this->properties[$key];
124 } else {
125 return null;
126 }
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(), array('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(), array('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) && \TYPO3\CMS\Core\Utility\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 return $this->getStorage()->deleteFile($this);
442 }
443
444 /**
445 * Marks this file as deleted. This should only be used inside the
446 * File Abstraction Layer, as it is a low-level API method.
447 *
448 * @return void
449 */
450 public function setDeleted()
451 {
452 $this->deleted = true;
453 }
454
455 /**
456 * Returns TRUE if this file has been deleted
457 *
458 * @return bool
459 */
460 public function isDeleted()
461 {
462 return $this->deleted;
463 }
464
465 /**
466 * Renames this file.
467 *
468 * @param string $newName The new file name
469 *
470 * @throws \RuntimeException
471 * @return File
472 */
473 public function rename($newName)
474 {
475 if ($this->deleted) {
476 throw new \RuntimeException('File has been deleted.', 1329821482);
477 }
478 return $this->getStorage()->renameFile($this, $newName);
479 }
480
481 /**
482 * Copies this file into a target folder
483 *
484 * @param Folder $targetFolder Folder to copy file into.
485 * @param string $targetFileName an optional destination fileName
486 * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
487 *
488 * @throws \RuntimeException
489 * @return File The new (copied) file.
490 */
491 public function copyTo(Folder $targetFolder, $targetFileName = null, $conflictMode = DuplicationBehavior::RENAME)
492 {
493 if ($this->deleted) {
494 throw new \RuntimeException('File has been deleted.', 1329821483);
495 }
496 return $targetFolder->getStorage()->copyFile($this, $targetFolder, $targetFileName, $conflictMode);
497 }
498
499 /**
500 * Moves the file into the target folder
501 *
502 * @param Folder $targetFolder Folder to move file into.
503 * @param string $targetFileName an optional destination fileName
504 * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
505 *
506 * @throws \RuntimeException
507 * @return File This file object, with updated properties.
508 */
509 public function moveTo(Folder $targetFolder, $targetFileName = null, $conflictMode = DuplicationBehavior::RENAME)
510 {
511 if ($this->deleted) {
512 throw new \RuntimeException('File has been deleted.', 1329821484);
513 }
514 return $targetFolder->getStorage()->moveFile($this, $targetFolder, $targetFileName, $conflictMode);
515 }
516
517 /*****************
518 * SPECIAL METHODS
519 *****************/
520 /**
521 * Returns a publicly accessible URL for this file
522 *
523 * WARNING: Access to the file may be restricted by further means, e.g. some
524 * web-based authentication. You have to take care of this yourself.
525 *
526 * @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)
527 * @return NULL|string NULL if file is deleted, the generated URL otherwise
528 */
529 public function getPublicUrl($relativeToCurrentScript = false)
530 {
531 if ($this->deleted) {
532 return null;
533 } else {
534 return $this->getStorage()->getPublicUrl($this, $relativeToCurrentScript);
535 }
536 }
537
538 /**
539 * Returns a path to a local version of this file to process it locally (e.g. with some system tool).
540 * If the file is normally located on a remote storages, this creates a local copy.
541 * If the file is already on the local system, this only makes a new copy if $writable is set to TRUE.
542 *
543 * @param bool $writable Set this to FALSE if you only want to do read operations on the file.
544 *
545 * @throws \RuntimeException
546 * @return string
547 */
548 public function getForLocalProcessing($writable = true)
549 {
550 if ($this->deleted) {
551 throw new \RuntimeException('File has been deleted.', 1329821486);
552 }
553 return $this->getStorage()->getFileForLocalProcessing($this, $writable);
554 }
555
556 /***********************
557 * INDEX RELATED METHODS
558 ***********************/
559 /**
560 * Updates properties of this object.
561 * This method is used to reconstitute settings from the
562 * database into this object after being intantiated.
563 *
564 * @param array $properties
565 */
566 abstract public function updateProperties(array $properties);
567
568 /**
569 * Returns the parent folder.
570 *
571 * @return FolderInterface
572 */
573 public function getParentFolder()
574 {
575 return $this->getStorage()->getFolder($this->getStorage()->getFolderIdentifierFromFileIdentifier($this->getIdentifier()));
576 }
577 }