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