[FEATURE] Allow Storages outside the webroot
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Resource / AbstractFile.php
1 <?php
2 namespace TYPO3\CMS\Core\Resource;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2011-2013 Ingmar Schlecht <ingmar@typo3.org>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the text file GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29
30 use TYPO3\CMS\Core\Utility\PathUtility;
31
32 /**
33 * Abstract file representation in the file abstraction layer.
34 *
35 * @author Ingmar Schlecht <ingmar@typo3.org>
36 */
37 abstract class AbstractFile implements FileInterface {
38
39 /**
40 * Various file properties
41 *
42 * Note that all properties, which only the persisted (indexed) files have are stored in this
43 * overall properties array only. The only properties which really exist as object properties of
44 * the file object are the storage, the identifier, the fileName and the indexing status.
45 *
46 * @var array
47 */
48 protected $properties;
49
50 /**
51 * The storage this file is located in
52 *
53 * @var ResourceStorage
54 */
55 protected $storage = NULL;
56
57 /**
58 * The identifier of this file to identify it on the storage.
59 * On some drivers, this is the path to the file, but drivers could also just
60 * provide any other unique identifier for this file on the specific storage.
61 *
62 * @var string
63 */
64 protected $identifier;
65
66 /**
67 * The file name of this file
68 *
69 * @var string
70 */
71 protected $name;
72
73 /**
74 * If set to true, this file is regarded as being deleted.
75 *
76 * @var boolean
77 */
78 protected $deleted = FALSE;
79
80 /**
81 * any other file
82 */
83 const FILETYPE_UNKNOWN = 0;
84
85 /**
86 * Any kind of text
87 * @see http://www.iana.org/assignments/media-types/text
88 */
89 const FILETYPE_TEXT = 1;
90
91 /**
92 * Any kind of image
93 * @see http://www.iana.org/assignments/media-types/image
94 */
95 const FILETYPE_IMAGE = 2;
96
97 /**
98 * Any kind of audio file
99 * @see http://www.iana.org/assignments/media-types/audio
100 */
101 const FILETYPE_AUDIO = 3;
102
103 /**
104 * Any kind of video
105 * @see http://www.iana.org/assignments/media-types/video
106 */
107 const FILETYPE_VIDEO = 4;
108
109 /**
110 * Any kind of application
111 * @see http://www.iana.org/assignments/media-types/application
112 */
113 const FILETYPE_APPLICATION = 5;
114
115 /**
116 * Any kind of software, often known as "application"
117 * @deprecated since 6.1, will be removed in 6.3. Use rather FILETYPE_APPLICATION which matches the Iana standard.
118 */
119 const FILETYPE_SOFTWARE = 5;
120
121 /******************
122 * VARIOUS FILE PROPERTY GETTERS
123 ******************/
124 /**
125 * Returns true if the given property key exists for this file.
126 *
127 * @param string $key
128 * @return boolean
129 */
130 public function hasProperty($key) {
131 return array_key_exists($key, $this->properties);
132 }
133
134 /**
135 * Returns a property value
136 *
137 * @param string $key
138 * @return mixed Property value
139 */
140 public function getProperty($key) {
141 if ($this->hasProperty($key)) {
142 return $this->properties[$key];
143 } else {
144 return NULL;
145 }
146 }
147
148 /**
149 * Returns the properties of this object.
150 *
151 * @return array
152 */
153 public function getProperties() {
154 return $this->properties;
155 }
156
157 /**
158 * Returns the identifier of this file
159 *
160 * @return string
161 */
162 public function getIdentifier() {
163 return $this->identifier;
164 }
165
166 /**
167 * Get hashed identifier
168 *
169 * @return string
170 */
171 public function getHashedIdentifier() {
172 return $this->properties['identifier_hash'];
173 }
174
175 /**
176 * Returns the name of this file
177 *
178 * @return string
179 */
180 public function getName() {
181 // Do not check if file has been deleted because we might need the
182 // name for undeleting it.
183 return $this->name;
184 }
185
186 /**
187 * Returns the basename (the name without extension) of this file.
188 *
189 * @return string
190 */
191 public function getNameWithoutExtension() {
192 return PathUtility::pathinfo($this->getName(), PATHINFO_FILENAME);
193 }
194
195 /**
196 * Returns the size of this file
197 *
198 * @throws \RuntimeException
199 * @return integer
200 */
201 public function getSize() {
202 if ($this->deleted) {
203 throw new \RuntimeException('File has been deleted.', 1329821480);
204 }
205 return $this->properties['size'] ?: array_pop($this->getStorage()->getFileInfoByIdentifier($this->getIdentifier(), array('size')));
206 }
207
208 /**
209 * Returns the uid of this file
210 *
211 * @return integer
212 */
213 public function getUid() {
214 return $this->getProperty('uid');
215 }
216
217 /**
218 * Returns the Sha1 of this file
219 *
220 * @throws \RuntimeException
221 * @return string
222 */
223 public function getSha1() {
224 if ($this->deleted) {
225 throw new \RuntimeException('File has been deleted.', 1329821481);
226 }
227 return $this->getStorage()->hashFile($this, 'sha1');
228 }
229
230 /**
231 * Returns the creation time of the file as Unix timestamp
232 *
233 * @throws \RuntimeException
234 * @return integer
235 */
236 public function getCreationTime() {
237 if ($this->deleted) {
238 throw new \RuntimeException('File has been deleted.', 1329821487);
239 }
240 return $this->getProperty('creation_date');
241 }
242
243 /**
244 * Returns the date (as UNIX timestamp) the file was last modified.
245 *
246 * @throws \RuntimeException
247 * @return integer
248 */
249 public function getModificationTime() {
250 if ($this->deleted) {
251 throw new \RuntimeException('File has been deleted.', 1329821488);
252 }
253 return $this->getProperty('modification_date');
254 }
255
256 /**
257 * Get the extension of this file in a lower-case variant
258 *
259 * @return string The file extension
260 */
261 public function getExtension() {
262 $pathinfo = PathUtility::pathinfo($this->getName());
263
264 $extension = strtolower($pathinfo['extension']);
265
266 return $extension;
267 }
268
269 /**
270 * Get the MIME type of this file
271 *
272 * @return array file information
273 */
274 public function getMimeType() {
275 return $this->properties['mimetype'] ?: array_pop($this->getStorage()->getFileInfoByIdentifier($this->getIdentifier(), array('mimetype')));
276 }
277
278 /**
279 * Returns the fileType of this file
280 * basically there are only five main "file types"
281 * "audio"
282 * "image"
283 * "software"
284 * "text"
285 * "video"
286 * "other"
287 * see the constants in this class
288 *
289 * @return integer $fileType
290 */
291 public function getType() {
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 $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 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 if ($this->deleted) {
349 throw new \RuntimeException('File has been deleted.', 1329821478);
350 }
351 $this->getStorage()->setFileContents($this, $contents);
352 return $this;
353 }
354
355 /****************************************
356 * STORAGE AND MANAGEMENT RELATED METHDOS
357 ****************************************/
358
359 /**
360 * Get the storage this file is located in
361 *
362 * @return ResourceStorage
363 * @throws \RuntimeException
364 */
365 public function getStorage() {
366 if ($this->storage === NULL) {
367 throw new \RuntimeException('You\'re using fileObjects without a storage.', 1381570091);
368 }
369 return $this->storage;
370 }
371
372 /**
373 * Checks if this file exists. This should normally always return TRUE;
374 * it might only return FALSE when this object has been created from an
375 * index record without checking for.
376 *
377 * @return boolean TRUE if this file physically exists
378 */
379 public function exists() {
380 if ($this->deleted) {
381 return FALSE;
382 }
383 return $this->storage->hasFile($this->getIdentifier());
384 }
385
386 /**
387 * Sets the storage this file is located in. This is only meant for
388 * \TYPO3\CMS\Core\Resource-internal usage; don't use it to move files.
389 *
390 * @internal Should only be used by other parts of the File API (e.g. drivers after moving a file)
391 * @param ResourceStorage $storage
392 * @return File
393 */
394 public function setStorage(ResourceStorage $storage) {
395 $this->storage = $storage;
396 $this->properties['storage'] = $storage->getUid();
397 return $this;
398 }
399
400 /**
401 * Set the identifier of this file
402 *
403 * @internal Should only be used by other parts of the File API (e.g. drivers after moving a file)
404 * @param string $identifier
405 * @return File
406 */
407 public function setIdentifier($identifier) {
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 if (is_array($this->properties) && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($this->properties['storage'])) {
420 $combinedIdentifier = $this->properties['storage'] . ':' . $this->getIdentifier();
421 } else {
422 $combinedIdentifier = $this->getStorage()->getUid() . ':' . $this->getIdentifier();
423 }
424 return $combinedIdentifier;
425 }
426
427 /**
428 * Deletes this file from its storage. This also means that this object becomes useless.
429 *
430 * @return boolean TRUE if deletion succeeded
431 */
432 public function delete() {
433 // The storage will mark this file as deleted
434 return $this->getStorage()->deleteFile($this);
435 }
436
437 /**
438 * Marks this file as deleted. This should only be used inside the
439 * File Abstraction Layer, as it is a low-level API method.
440 *
441 * @return void
442 */
443 public function setDeleted() {
444 $this->deleted = TRUE;
445 }
446
447 /**
448 * Returns TRUE if this file has been deleted
449 *
450 * @return boolean
451 */
452 public function isDeleted() {
453 return $this->deleted;
454 }
455
456 /**
457 * Renames this file.
458 *
459 * @param string $newName The new file name
460 *
461 * @throws \RuntimeException
462 * @return File
463 */
464 public function rename($newName) {
465 if ($this->deleted) {
466 throw new \RuntimeException('File has been deleted.', 1329821482);
467 }
468 return $this->getStorage()->renameFile($this, $newName);
469 }
470
471 /**
472 * Copies this file into a target folder
473 *
474 * @param Folder $targetFolder Folder to copy file into.
475 * @param string $targetFileName an optional destination fileName
476 * @param string $conflictMode overrideExistingFile", "renameNewFile", "cancel
477 *
478 * @throws \RuntimeException
479 * @return File The new (copied) file.
480 */
481 public function copyTo(Folder $targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
482 if ($this->deleted) {
483 throw new \RuntimeException('File has been deleted.', 1329821483);
484 }
485 return $targetFolder->getStorage()->copyFile($this, $targetFolder, $targetFileName, $conflictMode);
486 }
487
488 /**
489 * Moves the file into the target folder
490 *
491 * @param Folder $targetFolder Folder to move file into.
492 * @param string $targetFileName an optional destination fileName
493 * @param string $conflictMode overrideExistingFile", "renameNewFile", "cancel
494 *
495 * @throws \RuntimeException
496 * @return File This file object, with updated properties.
497 */
498 public function moveTo(Folder $targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
499 if ($this->deleted) {
500 throw new \RuntimeException('File has been deleted.', 1329821484);
501 }
502 return $targetFolder->getStorage()->moveFile($this, $targetFolder, $targetFileName, $conflictMode);
503 }
504
505 /*****************
506 * SPECIAL METHODS
507 *****************/
508 /**
509 * Returns a publicly accessible URL for this file
510 *
511 * WARNING: Access to the file may be restricted by further means, e.g. some
512 * web-based authentication. You have to take care of this yourself.
513 *
514 * @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)
515 *
516 * @return null|string
517 */
518 public function getPublicUrl($relativeToCurrentScript = FALSE) {
519 if ($this->deleted) {
520 return NULL;
521 } else {
522 return $this->getStorage()->getPublicUrl($this, $relativeToCurrentScript);
523 }
524 }
525
526 /**
527 * Returns a path to a local version of this file to process it locally (e.g. with some system tool).
528 * If the file is normally located on a remote storages, this creates a local copy.
529 * If the file is already on the local system, this only makes a new copy if $writable is set to TRUE.
530 *
531 * @param boolean $writable Set this to FALSE if you only want to do read operations on the file.
532 *
533 * @throws \RuntimeException
534 * @return string
535 */
536 public function getForLocalProcessing($writable = TRUE) {
537 if ($this->deleted) {
538 throw new \RuntimeException('File has been deleted.', 1329821486);
539 }
540 return $this->getStorage()->getFileForLocalProcessing($this, $writable);
541 }
542
543 /***********************
544 * INDEX RELATED METHODS
545 ***********************/
546 /**
547 * Updates properties of this object.
548 * This method is used to reconstitute settings from the
549 * database into this object after being intantiated.
550 *
551 * @param array $properties
552 */
553 abstract public function updateProperties(array $properties);
554
555 /**
556 * Returns the parent folder.
557 *
558 * @return FolderInterface
559 */
560 public function getParentFolder() {
561 return $this->getStorage()->getFolder($this->getStorage()->getFolderIdentifierFromFileIdentifier($this->getIdentifier()));
562 }
563 }