[BUGFIX] Clean-up responsibilities of FAL classes
[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 textfile 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
142 if ($this->hasProperty($key)) {
143 return $this->properties[$key];
144 } else {
145 return NULL;
146 }
147 }
148
149 /**
150 * Returns the properties of this object.
151 *
152 * @return array
153 */
154 public function getProperties() {
155 return $this->properties;
156 }
157
158 /**
159 * Returns the identifier of this file
160 *
161 * @return string
162 */
163 public function getIdentifier() {
164 return $this->identifier;
165 }
166
167 /**
168 * Returns the name of this file
169 *
170 * @return string
171 */
172 public function getName() {
173 // Do not check if file has been deleted because we might need the
174 // name for undeleting it.
175 return $this->name;
176 }
177
178 /**
179 * Returns the basename (the name without extension) of this file.
180 *
181 * @return string
182 */
183 public function getNameWithoutExtension() {
184 return PathUtility::pathinfo($this->getName(), PATHINFO_FILENAME);
185 }
186
187 /**
188 * Returns the size of this file
189 *
190 * @throws \RuntimeException
191 * @return integer
192 */
193 public function getSize() {
194 if ($this->deleted) {
195 throw new \RuntimeException('File has been deleted.', 1329821480);
196 }
197 return $this->properties['size'];
198 }
199
200 /**
201 * Returns the uid of this file
202 *
203 * @return integer
204 */
205 public function getUid() {
206 return $this->getProperty('uid');
207 }
208
209 /**
210 * Returns the Sha1 of this file
211 *
212 * @throws \RuntimeException
213 * @return string
214 */
215 public function getSha1() {
216 if ($this->deleted) {
217 throw new \RuntimeException('File has been deleted.', 1329821481);
218 }
219 return $this->getStorage()->hashFile($this, 'sha1');
220 }
221
222 /**
223 * Returns the creation time of the file as Unix timestamp
224 *
225 * @throws \RuntimeException
226 * @return integer
227 */
228 public function getCreationTime() {
229 if ($this->deleted) {
230 throw new \RuntimeException('File has been deleted.', 1329821487);
231 }
232 return $this->getProperty('creation_date');
233 }
234
235 /**
236 * Returns the date (as UNIX timestamp) the file was last modified.
237 *
238 * @throws \RuntimeException
239 * @return integer
240 */
241 public function getModificationTime() {
242 if ($this->deleted) {
243 throw new \RuntimeException('File has been deleted.', 1329821488);
244 }
245 return $this->getProperty('modification_date');
246 }
247
248 /**
249 * Get the extension of this file in a lower-case variant
250 *
251 * @return string The file extension
252 */
253 public function getExtension() {
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 array file information
265 */
266 public function getMimeType() {
267 // TODO this will be slow - use the cached version if possible
268 $stat = $this->getStorage()->getFileInfo($this);
269 return $stat['mimetype'];
270 }
271
272 /**
273 * Returns the fileType of this file
274 * basically there are only five main "file types"
275 * "audio"
276 * "image"
277 * "software"
278 * "text"
279 * "video"
280 * "other"
281 * see the constants in this class
282 *
283 * @return integer $fileType
284 */
285 public function getType() {
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 $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 if ($this->deleted) {
328 throw new \RuntimeException('File has been deleted.', 1329821479);
329 }
330 return $this->getStorage()->getFileContents($this);
331 }
332
333 /**
334 * Replace the current file contents with the given string
335 *
336 * @param string $contents The contents to write to the file.
337 *
338 * @throws \RuntimeException
339 * @return File The file object (allows chaining).
340 */
341 public function setContents($contents) {
342 if ($this->deleted) {
343 throw new \RuntimeException('File has been deleted.', 1329821478);
344 }
345 $this->getStorage()->setFileContents($this, $contents);
346 return $this;
347 }
348
349 /****************************************
350 * STORAGE AND MANAGEMENT RELATED METHDOS
351 ****************************************/
352 /**
353 * Get the storage this file is located in
354 *
355 * @return ResourceStorage
356 */
357 public function getStorage() {
358 if ($this->storage === NULL) {
359 throw new \RuntimeException('You\'re using fileObjects without a storage.', 1381570091);
360 }
361 return $this->storage;
362 }
363
364 /**
365 * Checks if this file exists. This should normally always return TRUE;
366 * it might only return FALSE when this object has been created from an
367 * index record without checking for.
368 *
369 * @return boolean TRUE if this file physically exists
370 */
371 public function exists() {
372 if ($this->deleted) {
373 return FALSE;
374 }
375 return $this->storage->hasFile($this->getIdentifier());
376 }
377
378 /**
379 * Sets the storage this file is located in. This is only meant for
380 * \TYPO3\CMS\Core\Resource-internal usage; don't use it to move files.
381 *
382 * @internal Should only be used by other parts of the File API (e.g. drivers after moving a file)
383 * @param ResourceStorage $storage
384 * @return File
385 */
386 public function setStorage(ResourceStorage $storage) {
387 $this->storage = $storage;
388 $this->properties['storage'] = $storage->getUid();
389 return $this;
390 }
391
392 /**
393 * Set the identifier of this file
394 *
395 * @internal Should only be used by other parts of the File API (e.g. drivers after moving a file)
396 * @param string $identifier
397 * @return File
398 */
399 public function setIdentifier($identifier) {
400 $this->identifier = $identifier;
401 return $this;
402 }
403
404 /**
405 * Returns a combined identifier of this file, i.e. the storage UID and the
406 * folder identifier separated by a colon ":".
407 *
408 * @return string Combined storage and file identifier, e.g. StorageUID:path/and/fileName.png
409 */
410 public function getCombinedIdentifier() {
411 if (is_array($this->properties) && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($this->properties['storage'])) {
412 $combinedIdentifier = $this->properties['storage'] . ':' . $this->getIdentifier();
413 } else {
414 $combinedIdentifier = $this->getStorage()->getUid() . ':' . $this->getIdentifier();
415 }
416 return $combinedIdentifier;
417 }
418
419 /**
420 * Deletes this file from its storage. This also means that this object becomes useless.
421 *
422 * @return boolean TRUE if deletion succeeded
423 */
424 public function delete() {
425 // The storage will mark this file as deleted
426 return $this->getStorage()->deleteFile($this);
427 }
428
429 /**
430 * Marks this file as deleted. This should only be used inside the
431 * File Abstraction Layer, as it is a low-level API method.
432 *
433 * @return void
434 */
435 public function setDeleted() {
436 $this->deleted = TRUE;
437 }
438
439 /**
440 * Returns TRUE if this file has been deleted
441 *
442 * @return boolean
443 */
444 public function isDeleted() {
445 return $this->deleted;
446 }
447
448 /**
449 * Renames this file.
450 *
451 * @param string $newName The new file name
452 *
453 * @throws \RuntimeException
454 * @return File
455 */
456 public function rename($newName) {
457 if ($this->deleted) {
458 throw new \RuntimeException('File has been deleted.', 1329821482);
459 }
460 return $this->getStorage()->renameFile($this, $newName);
461 }
462
463 /**
464 * Copies this file into a target folder
465 *
466 * @param Folder $targetFolder Folder to copy file into.
467 * @param string $targetFileName an optional destination fileName
468 * @param string $conflictMode overrideExistingFile", "renameNewFile", "cancel
469 *
470 * @throws \RuntimeException
471 * @return File The new (copied) file.
472 */
473 public function copyTo(Folder $targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
474 if ($this->deleted) {
475 throw new \RuntimeException('File has been deleted.', 1329821483);
476 }
477 return $targetFolder->getStorage()->copyFile($this, $targetFolder, $targetFileName, $conflictMode);
478 }
479
480 /**
481 * Moves the file into the target folder
482 *
483 * @param Folder $targetFolder Folder to move file into.
484 * @param string $targetFileName an optional destination fileName
485 * @param string $conflictMode overrideExistingFile", "renameNewFile", "cancel
486 *
487 * @throws \RuntimeException
488 * @return File This file object, with updated properties.
489 */
490 public function moveTo(Folder $targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
491 if ($this->deleted) {
492 throw new \RuntimeException('File has been deleted.', 1329821484);
493 }
494 return $targetFolder->getStorage()->moveFile($this, $targetFolder, $targetFileName, $conflictMode);
495 }
496
497 /*****************
498 * SPECIAL METHODS
499 *****************/
500 /**
501 * Returns a publicly accessible URL for this file
502 *
503 * WARNING: Access to the file may be restricted by further means, e.g. some
504 * web-based authentication. You have to take care of this yourself.
505 *
506 * @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)
507 *
508 * @return null|string
509 */
510 public function getPublicUrl($relativeToCurrentScript = FALSE) {
511 if ($this->deleted) {
512 return NULL;
513 } else {
514 return $this->getStorage()->getPublicUrl($this, $relativeToCurrentScript);
515 }
516 }
517
518 /**
519 * Returns a path to a local version of this file to process it locally (e.g. with some system tool).
520 * If the file is normally located on a remote storages, this creates a local copy.
521 * If the file is already on the local system, this only makes a new copy if $writable is set to TRUE.
522 *
523 * @param boolean $writable Set this to FALSE if you only want to do read operations on the file.
524 *
525 * @throws \RuntimeException
526 * @return string
527 */
528 public function getForLocalProcessing($writable = TRUE) {
529 if ($this->deleted) {
530 throw new \RuntimeException('File has been deleted.', 1329821486);
531 }
532 return $this->getStorage()->getFileForLocalProcessing($this, $writable);
533 }
534
535 /***********************
536 * INDEX RELATED METHODS
537 ***********************/
538 /**
539 * Updates properties of this object.
540 * This method is used to reconstitute settings from the
541 * database into this object after being intantiated.
542 *
543 * @param array $properties
544 */
545 abstract public function updateProperties(array $properties);
546
547 }