a06657d1a5b33760b2a7df406c2083c3aa94dd1f
[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
186 */
187 public function getSize()
188 {
189 if ($this->deleted) {
190 throw new \RuntimeException('File has been deleted.', 1329821480);
191 }
192 return $this->properties['size'] ?: array_pop($this->getStorage()->getFileInfoByIdentifier($this->getIdentifier(), array('size')));
193 }
194
195 /**
196 * Returns the uid of this file
197 *
198 * @return int
199 */
200 public function getUid()
201 {
202 return $this->getProperty('uid');
203 }
204
205 /**
206 * Returns the Sha1 of this file
207 *
208 * @throws \RuntimeException
209 * @return string
210 */
211 public function getSha1()
212 {
213 if ($this->deleted) {
214 throw new \RuntimeException('File has been deleted.', 1329821481);
215 }
216 return $this->getStorage()->hashFile($this, 'sha1');
217 }
218
219 /**
220 * Returns the creation time of the file as Unix timestamp
221 *
222 * @throws \RuntimeException
223 * @return int
224 */
225 public function getCreationTime()
226 {
227 if ($this->deleted) {
228 throw new \RuntimeException('File has been deleted.', 1329821487);
229 }
230 return $this->getProperty('creation_date');
231 }
232
233 /**
234 * Returns the date (as UNIX timestamp) the file was last modified.
235 *
236 * @throws \RuntimeException
237 * @return int
238 */
239 public function getModificationTime()
240 {
241 if ($this->deleted) {
242 throw new \RuntimeException('File has been deleted.', 1329821488);
243 }
244 return $this->getProperty('modification_date');
245 }
246
247 /**
248 * Get the extension of this file in a lower-case variant
249 *
250 * @return string The file extension
251 */
252 public function getExtension()
253 {
254 $pathinfo = PathUtility::pathinfo($this->getName());
255
256 $extension = strtolower($pathinfo['extension']);
257
258 return $extension;
259 }
260
261 /**
262 * Get the MIME type of this file
263 *
264 * @return string mime type
265 */
266 public function getMimeType()
267 {
268 return $this->properties['mime_type'] ?: array_pop($this->getStorage()->getFileInfoByIdentifier($this->getIdentifier(), array('mimetype')));
269 }
270
271 /**
272 * Returns the fileType of this file
273 * basically there are only five main "file types"
274 * "audio"
275 * "image"
276 * "software"
277 * "text"
278 * "video"
279 * "other"
280 * see the constants in this class
281 *
282 * @return int $fileType
283 */
284 public function getType()
285 {
286 // this basically extracts the mimetype and guess the filetype based
287 // on the first part of the mimetype works for 99% of all cases, and
288 // we don't need to make an SQL statement like EXT:media does currently
289 if (!$this->properties['type']) {
290 $mimeType = $this->getMimeType();
291 list($fileType) = explode('/', $mimeType);
292 switch (strtolower($fileType)) {
293 case 'text':
294 $this->properties['type'] = self::FILETYPE_TEXT;
295 break;
296 case 'image':
297 $this->properties['type'] = self::FILETYPE_IMAGE;
298 break;
299 case 'audio':
300 $this->properties['type'] = self::FILETYPE_AUDIO;
301 break;
302 case 'video':
303 $this->properties['type'] = self::FILETYPE_VIDEO;
304 break;
305 case 'application':
306
307 case 'software':
308 $this->properties['type'] = self::FILETYPE_APPLICATION;
309 break;
310 default:
311 $this->properties['type'] = self::FILETYPE_UNKNOWN;
312 }
313 }
314 return (int)$this->properties['type'];
315 }
316
317 /******************
318 * CONTENTS RELATED
319 ******************/
320 /**
321 * Get the contents of this file
322 *
323 * @throws \RuntimeException
324 * @return string File contents
325 */
326 public function getContents()
327 {
328 if ($this->deleted) {
329 throw new \RuntimeException('File has been deleted.', 1329821479);
330 }
331 return $this->getStorage()->getFileContents($this);
332 }
333
334 /**
335 * Replace the current file contents with the given string
336 *
337 * @param string $contents The contents to write to the file.
338 *
339 * @throws \RuntimeException
340 * @return File The file object (allows chaining).
341 */
342 public function setContents($contents)
343 {
344 if ($this->deleted) {
345 throw new \RuntimeException('File has been deleted.', 1329821478);
346 }
347 $this->getStorage()->setFileContents($this, $contents);
348 return $this;
349 }
350
351 /****************************************
352 * STORAGE AND MANAGEMENT RELATED METHDOS
353 ****************************************/
354
355 /**
356 * Get the storage this file is located in
357 *
358 * @return ResourceStorage
359 * @throws \RuntimeException
360 */
361 public function getStorage()
362 {
363 if ($this->storage === null) {
364 throw new \RuntimeException('You\'re using fileObjects without a storage.', 1381570091);
365 }
366 return $this->storage;
367 }
368
369 /**
370 * Checks if this file exists. This should normally always return TRUE;
371 * it might only return FALSE when this object has been created from an
372 * index record without checking for.
373 *
374 * @return bool TRUE if this file physically exists
375 */
376 public function exists()
377 {
378 if ($this->deleted) {
379 return false;
380 }
381 return $this->storage->hasFile($this->getIdentifier());
382 }
383
384 /**
385 * Sets the storage this file is located in. This is only meant for
386 * \TYPO3\CMS\Core\Resource-internal usage; don't use it to move files.
387 *
388 * @internal Should only be used by other parts of the File API (e.g. drivers after moving a file)
389 * @param ResourceStorage $storage
390 * @return File
391 */
392 public function setStorage(ResourceStorage $storage)
393 {
394 $this->storage = $storage;
395 $this->properties['storage'] = $storage->getUid();
396 return $this;
397 }
398
399 /**
400 * Set the identifier of this file
401 *
402 * @internal Should only be used by other parts of the File API (e.g. drivers after moving a file)
403 * @param string $identifier
404 * @return File
405 */
406 public function setIdentifier($identifier)
407 {
408 $this->identifier = $identifier;
409 return $this;
410 }
411
412 /**
413 * Returns a combined identifier of this file, i.e. the storage UID and the
414 * folder identifier separated by a colon ":".
415 *
416 * @return string Combined storage and file identifier, e.g. StorageUID:path/and/fileName.png
417 */
418 public function getCombinedIdentifier()
419 {
420 if (is_array($this->properties) && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($this->properties['storage'])) {
421 $combinedIdentifier = $this->properties['storage'] . ':' . $this->getIdentifier();
422 } else {
423 $combinedIdentifier = $this->getStorage()->getUid() . ':' . $this->getIdentifier();
424 }
425 return $combinedIdentifier;
426 }
427
428 /**
429 * Deletes this file from its storage. This also means that this object becomes useless.
430 *
431 * @return bool TRUE if deletion succeeded
432 */
433 public function delete()
434 {
435 // The storage will mark this file as deleted
436 return $this->getStorage()->deleteFile($this);
437 }
438
439 /**
440 * Marks this file as deleted. This should only be used inside the
441 * File Abstraction Layer, as it is a low-level API method.
442 *
443 * @return void
444 */
445 public function setDeleted()
446 {
447 $this->deleted = true;
448 }
449
450 /**
451 * Returns TRUE if this file has been deleted
452 *
453 * @return bool
454 */
455 public function isDeleted()
456 {
457 return $this->deleted;
458 }
459
460 /**
461 * Renames this file.
462 *
463 * @param string $newName The new file name
464 *
465 * @throws \RuntimeException
466 * @return File
467 */
468 public function rename($newName)
469 {
470 if ($this->deleted) {
471 throw new \RuntimeException('File has been deleted.', 1329821482);
472 }
473 return $this->getStorage()->renameFile($this, $newName);
474 }
475
476 /**
477 * Copies this file into a target folder
478 *
479 * @param Folder $targetFolder Folder to copy file into.
480 * @param string $targetFileName an optional destination fileName
481 * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
482 *
483 * @throws \RuntimeException
484 * @return File The new (copied) file.
485 */
486 public function copyTo(Folder $targetFolder, $targetFileName = null, $conflictMode = DuplicationBehavior::RENAME)
487 {
488 if ($this->deleted) {
489 throw new \RuntimeException('File has been deleted.', 1329821483);
490 }
491 return $targetFolder->getStorage()->copyFile($this, $targetFolder, $targetFileName, $conflictMode);
492 }
493
494 /**
495 * Moves the file into the target folder
496 *
497 * @param Folder $targetFolder Folder to move file into.
498 * @param string $targetFileName an optional destination fileName
499 * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
500 *
501 * @throws \RuntimeException
502 * @return File This file object, with updated properties.
503 */
504 public function moveTo(Folder $targetFolder, $targetFileName = null, $conflictMode = DuplicationBehavior::RENAME)
505 {
506 if ($this->deleted) {
507 throw new \RuntimeException('File has been deleted.', 1329821484);
508 }
509 return $targetFolder->getStorage()->moveFile($this, $targetFolder, $targetFileName, $conflictMode);
510 }
511
512 /*****************
513 * SPECIAL METHODS
514 *****************/
515 /**
516 * Returns a publicly accessible URL for this file
517 *
518 * WARNING: Access to the file may be restricted by further means, e.g. some
519 * web-based authentication. You have to take care of this yourself.
520 *
521 * @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)
522 * @return NULL|string NULL if file is deleted, the generated URL otherwise
523 */
524 public function getPublicUrl($relativeToCurrentScript = false)
525 {
526 if ($this->deleted) {
527 return null;
528 } else {
529 return $this->getStorage()->getPublicUrl($this, $relativeToCurrentScript);
530 }
531 }
532
533 /**
534 * Returns a path to a local version of this file to process it locally (e.g. with some system tool).
535 * If the file is normally located on a remote storages, this creates a local copy.
536 * If the file is already on the local system, this only makes a new copy if $writable is set to TRUE.
537 *
538 * @param bool $writable Set this to FALSE if you only want to do read operations on the file.
539 *
540 * @throws \RuntimeException
541 * @return string
542 */
543 public function getForLocalProcessing($writable = true)
544 {
545 if ($this->deleted) {
546 throw new \RuntimeException('File has been deleted.', 1329821486);
547 }
548 return $this->getStorage()->getFileForLocalProcessing($this, $writable);
549 }
550
551 /***********************
552 * INDEX RELATED METHODS
553 ***********************/
554 /**
555 * Updates properties of this object.
556 * This method is used to reconstitute settings from the
557 * database into this object after being intantiated.
558 *
559 * @param array $properties
560 */
561 abstract public function updateProperties(array $properties);
562
563 /**
564 * Returns the parent folder.
565 *
566 * @return FolderInterface
567 */
568 public function getParentFolder()
569 {
570 return $this->getStorage()->getFolder($this->getStorage()->getFolderIdentifierFromFileIdentifier($this->getIdentifier()));
571 }
572 }