0cbc5d14e0671de8173039771216de3994d3f428
[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 return $this->getStorage()->deleteFile($this);
443 }
444
445 /**
446 * Marks this file as deleted. This should only be used inside the
447 * File Abstraction Layer, as it is a low-level API method.
448 */
449 public function setDeleted()
450 {
451 // Unset all properties when deleting the file, as they will be stale anyway
452 $this->properties = null;
453 $this->deleted = true;
454 }
455
456 /**
457 * Returns TRUE if this file has been deleted
458 *
459 * @return bool
460 */
461 public function isDeleted()
462 {
463 return $this->deleted;
464 }
465
466 /**
467 * Renames this file.
468 *
469 * @param string $newName The new file name
470 *
471 * @param string $conflictMode
472 * @return FileInterface
473 */
474 public function rename($newName, $conflictMode = DuplicationBehavior::RENAME)
475 {
476 if ($this->deleted) {
477 throw new \RuntimeException('File has been deleted.', 1329821482);
478 }
479 return $this->getStorage()->renameFile($this, $newName, $conflictMode);
480 }
481
482 /**
483 * Copies this file into a target folder
484 *
485 * @param Folder $targetFolder Folder to copy file into.
486 * @param string $targetFileName an optional destination fileName
487 * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
488 *
489 * @throws \RuntimeException
490 * @return File The new (copied) file.
491 */
492 public function copyTo(Folder $targetFolder, $targetFileName = null, $conflictMode = DuplicationBehavior::RENAME)
493 {
494 if ($this->deleted) {
495 throw new \RuntimeException('File has been deleted.', 1329821483);
496 }
497 return $targetFolder->getStorage()->copyFile($this, $targetFolder, $targetFileName, $conflictMode);
498 }
499
500 /**
501 * Moves the file into the target folder
502 *
503 * @param Folder $targetFolder Folder to move file into.
504 * @param string $targetFileName an optional destination fileName
505 * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
506 *
507 * @throws \RuntimeException
508 * @return File This file object, with updated properties.
509 */
510 public function moveTo(Folder $targetFolder, $targetFileName = null, $conflictMode = DuplicationBehavior::RENAME)
511 {
512 if ($this->deleted) {
513 throw new \RuntimeException('File has been deleted.', 1329821484);
514 }
515 return $targetFolder->getStorage()->moveFile($this, $targetFolder, $targetFileName, $conflictMode);
516 }
517
518 /*****************
519 * SPECIAL METHODS
520 *****************/
521 /**
522 * Returns a publicly accessible URL for this file
523 *
524 * WARNING: Access to the file may be restricted by further means, e.g. some
525 * web-based authentication. You have to take care of this yourself.
526 *
527 * @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)
528 * @return NULL|string NULL if file is deleted, the generated URL otherwise
529 */
530 public function getPublicUrl($relativeToCurrentScript = false)
531 {
532 if ($this->deleted) {
533 return null;
534 } else {
535 return $this->getStorage()->getPublicUrl($this, $relativeToCurrentScript);
536 }
537 }
538
539 /**
540 * Returns a path to a local version of this file to process it locally (e.g. with some system tool).
541 * If the file is normally located on a remote storages, this creates a local copy.
542 * If the file is already on the local system, this only makes a new copy if $writable is set to TRUE.
543 *
544 * @param bool $writable Set this to FALSE if you only want to do read operations on the file.
545 *
546 * @throws \RuntimeException
547 * @return string
548 */
549 public function getForLocalProcessing($writable = true)
550 {
551 if ($this->deleted) {
552 throw new \RuntimeException('File has been deleted.', 1329821486);
553 }
554 return $this->getStorage()->getFileForLocalProcessing($this, $writable);
555 }
556
557 /***********************
558 * INDEX RELATED METHODS
559 ***********************/
560 /**
561 * Updates properties of this object.
562 * This method is used to reconstitute settings from the
563 * database into this object after being intantiated.
564 *
565 * @param array $properties
566 */
567 abstract public function updateProperties(array $properties);
568
569 /**
570 * Returns the parent folder.
571 *
572 * @return FolderInterface
573 */
574 public function getParentFolder()
575 {
576 return $this->getStorage()->getFolder($this->getStorage()->getFolderIdentifierFromFileIdentifier($this->getIdentifier()));
577 }
578 }