[BUGFIX] Check if folder is within the filemount
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Resource / Folder.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 * A folder that groups files in a storage. This may be a folder on the local
21 * disk, a bucket in Amazon S3 or a user or a tag in Flickr.
22 *
23 * This object is not persisted in TYPO3 locally, but created on the fly by
24 * storage drivers for the folders they "offer".
25 *
26 * Some folders serve as a physical container for files (e.g. folders on the
27 * local disk, S3 buckets or Flickr users). Other folders just group files by a
28 * certain criterion, e.g. a tag.
29 * The way this is implemented depends on the storage driver.
30 */
31 class Folder implements FolderInterface
32 {
33 /**
34 * The storage this folder belongs to.
35 *
36 * @var ResourceStorage
37 */
38 protected $storage;
39
40 /**
41 * The identifier of this folder to identify it on the storage.
42 * On some drivers, this is the path to the folder, but drivers could also just
43 * provide any other unique identifier for this folder on the specific storage.
44 *
45 * @var string
46 */
47 protected $identifier;
48
49 /**
50 * The name of this folder
51 *
52 * @var string
53 */
54 protected $name;
55
56 /**
57 * The filters this folder should use for a filelist.
58 *
59 * @var callable[]
60 */
61 protected $fileAndFolderNameFilters = array();
62
63 /**
64 * Modes for filter usage in getFiles()/getFolders()
65 */
66 const FILTER_MODE_NO_FILTERS = 0;
67 // Merge local filters into storage's filters
68 const FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS = 1;
69 // Only use the filters provided by the storage
70 const FILTER_MODE_USE_STORAGE_FILTERS = 2;
71 // Only use the filters provided by the current class
72 const FILTER_MODE_USE_OWN_FILTERS = 3;
73
74 /**
75 * Initialization of the folder
76 *
77 * @param ResourceStorage $storage
78 * @param $identifier
79 * @param $name
80 */
81 public function __construct(ResourceStorage $storage, $identifier, $name)
82 {
83 $this->storage = $storage;
84 $this->identifier = $identifier;
85 $this->name = $name;
86 }
87
88 /**
89 * Returns the name of this folder.
90 *
91 * @return string
92 */
93 public function getName()
94 {
95 return $this->name;
96 }
97
98 /**
99 * Returns the full path of this folder, from the root.
100 *
101 * @param string $rootId ID of the root folder, NULL to auto-detect
102 *
103 * @return string
104 */
105 public function getReadablePath($rootId = null)
106 {
107 if ($rootId === null) {
108 // Find first matching filemount and use that as root
109 foreach ($this->storage->getFileMounts() as $fileMount) {
110 if ($this->storage->isWithinFolder($fileMount['folder'], $this)) {
111 $rootId = $fileMount['folder']->getIdentifier();
112 break;
113 }
114 }
115 if ($rootId === null) {
116 $rootId = $this->storage->getRootLevelFolder()->getIdentifier();
117 }
118 }
119 $readablePath = '/';
120 if ($this->identifier !== $rootId) {
121 try {
122 $readablePath = $this->getParentFolder()->getReadablePath($rootId);
123 } catch (Exception\InsufficientFolderAccessPermissionsException $e) {
124 // May no access to parent folder (e.g. because of mount point)
125 $readablePath = '/';
126 }
127 }
128 return $readablePath . ($this->name ? $this->name . '/' : '');
129 }
130
131 /**
132 * Sets a new name of the folder
133 * currently this does not trigger the "renaming process"
134 * as the name is more seen as a label
135 *
136 * @param string $name The new name
137 * @return void
138 */
139 public function setName($name)
140 {
141 $this->name = $name;
142 }
143
144 /**
145 * Returns the storage this folder belongs to.
146 *
147 * @return ResourceStorage
148 */
149 public function getStorage()
150 {
151 return $this->storage;
152 }
153
154 /**
155 * Returns the path of this folder inside the storage. It depends on the
156 * type of storage whether this is a real path or just some unique identifier.
157 *
158 * @return string
159 */
160 public function getIdentifier()
161 {
162 return $this->identifier;
163 }
164
165 /**
166 * Get hashed identifier
167 *
168 * @return string
169 */
170 public function getHashedIdentifier()
171 {
172 return $this->storage->hashFileIdentifier($this->identifier);
173 }
174
175 /**
176 * Returns a combined identifier of this folder, i.e. the storage UID and
177 * the folder identifier separated by a colon ":".
178 *
179 * @return string Combined storage and folder identifier, e.g. StorageUID:folder/path/
180 */
181 public function getCombinedIdentifier()
182 {
183 return $this->getStorage()->getUid() . ':' . $this->getIdentifier();
184 }
185
186 /**
187 * Returns a publicly accessible URL for this folder
188 *
189 * WARNING: Access to the folder may be restricted by further means, e.g. some
190 * web-based authentication. You have to take care of this yourself.
191 *
192 * @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)
193 * @return string
194 */
195 public function getPublicUrl($relativeToCurrentScript = false)
196 {
197 return $this->getStorage()->getPublicUrl($this, $relativeToCurrentScript);
198 }
199
200 /**
201 * Returns a list of files in this folder, optionally filtered. There are several filter modes available, see the
202 * FILTER_MODE_* constants for more information.
203 *
204 * For performance reasons the returned items can also be limited to a given range
205 *
206 * @param int $start The item to start at
207 * @param int $numberOfItems The number of items to return
208 * @param int $filterMode The filter mode to use for the filelist.
209 * @param bool $recursive
210 * @param string $sort Property name used to sort the items.
211 * Among them may be: '' (empty, no sorting), name,
212 * fileext, size, tstamp and rw.
213 * If a driver does not support the given property, it
214 * should fall back to "name".
215 * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
216 * @return \TYPO3\CMS\Core\Resource\File[]
217 */
218 public function getFiles($start = 0, $numberOfItems = 0, $filterMode = self::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, $recursive = false, $sort = '', $sortRev = false)
219 {
220 // Fallback for compatibility with the old method signature variable $useFilters that was used instead of $filterMode
221 if ($filterMode === false) {
222 $useFilters = false;
223 $backedUpFilters = array();
224 } else {
225 list($backedUpFilters, $useFilters) = $this->prepareFiltersInStorage($filterMode);
226 }
227
228 $fileObjects = $this->storage->getFilesInFolder($this, $start, $numberOfItems, $useFilters, $recursive, $sort, $sortRev);
229
230 $this->restoreBackedUpFiltersInStorage($backedUpFilters);
231
232 return $fileObjects;
233 }
234
235 /**
236 * Returns amount of all files within this folder, optionally filtered by
237 * the given pattern
238 *
239 * @param array $filterMethods
240 * @param bool $recursive
241 * @return int
242 * @throws Exception\InsufficientFolderAccessPermissionsException
243 */
244 public function getFileCount(array $filterMethods = array(), $recursive = false)
245 {
246 return $this->storage->countFilesInFolder($this, true, $recursive);
247 }
248
249 /**
250 * Returns the object for a subfolder of the current folder, if it exists.
251 *
252 * @param string $name Name of the subfolder
253 * @return Folder
254 * @throws \InvalidArgumentException
255 */
256 public function getSubfolder($name)
257 {
258 if (!$this->storage->hasFolderInFolder($name, $this)) {
259 throw new \InvalidArgumentException('Folder "' . $name . '" does not exist in "' . $this->identifier . '"', 1329836110);
260 }
261 return $this->storage->getFolderInFolder($name, $this);
262 }
263
264 /**
265 * Returns a list of subfolders
266 *
267 * @param int $start The item to start at
268 * @param int $numberOfItems The number of items to return
269 * @param int $filterMode The filter mode to use for the filelist.
270 * @param bool $recursive
271 * @return Folder[]
272 */
273 public function getSubfolders($start = 0, $numberOfItems = 0, $filterMode = self::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, $recursive = false)
274 {
275 list($backedUpFilters, $useFilters) = $this->prepareFiltersInStorage($filterMode);
276 $folderObjects = $this->storage->getFoldersInFolder($this, $start, $numberOfItems, $useFilters, $recursive);
277 $this->restoreBackedUpFiltersInStorage($backedUpFilters);
278 return $folderObjects;
279 }
280
281 /**
282 * Adds a file from the local server disk. If the file already exists and
283 * overwriting is disabled,
284 *
285 * @param string $localFilePath
286 * @param string $fileName
287 * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
288 * @return File The file object
289 */
290 public function addFile($localFilePath, $fileName = null, $conflictMode = DuplicationBehavior::CANCEL)
291 {
292 $fileName = $fileName ? $fileName : PathUtility::basename($localFilePath);
293 return $this->storage->addFile($localFilePath, $this, $fileName, $conflictMode);
294 }
295
296 /**
297 * Adds an uploaded file into the Storage.
298 *
299 * @param array $uploadedFileData contains information about the uploaded file given by $_FILES['file1']
300 * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
301 * @return File The file object
302 */
303 public function addUploadedFile(array $uploadedFileData, $conflictMode = DuplicationBehavior::CANCEL)
304 {
305 return $this->storage->addUploadedFile($uploadedFileData, $this, $uploadedFileData['name'], $conflictMode);
306 }
307
308 /**
309 * Renames this folder.
310 *
311 * @param string $newName
312 * @return Folder
313 */
314 public function rename($newName)
315 {
316 return $this->storage->renameFolder($this, $newName);
317 }
318
319 /**
320 * Deletes this folder from its storage. This also means that this object becomes useless.
321 *
322 * @param bool $deleteRecursively
323 * @return bool TRUE if deletion succeeded
324 */
325 public function delete($deleteRecursively = true)
326 {
327 return $this->storage->deleteFolder($this, $deleteRecursively);
328 }
329
330 /**
331 * Creates a new blank file
332 *
333 * @param string $fileName
334 * @return File The new file object
335 */
336 public function createFile($fileName)
337 {
338 return $this->storage->createFile($fileName, $this);
339 }
340
341 /**
342 * Creates a new folder
343 *
344 * @param string $folderName
345 * @return Folder The new folder object
346 */
347 public function createFolder($folderName)
348 {
349 return $this->storage->createFolder($folderName, $this);
350 }
351
352 /**
353 * Copies folder to a target folder
354 *
355 * @param Folder $targetFolder Target folder to copy to.
356 * @param string $targetFolderName an optional destination fileName
357 * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
358 * @return Folder New (copied) folder object.
359 */
360 public function copyTo(Folder $targetFolder, $targetFolderName = null, $conflictMode = DuplicationBehavior::RENAME)
361 {
362 return $targetFolder->getStorage()->copyFolder($this, $targetFolder, $targetFolderName, $conflictMode);
363 }
364
365 /**
366 * Moves folder to a target folder
367 *
368 * @param Folder $targetFolder Target folder to move to.
369 * @param string $targetFolderName an optional destination fileName
370 * @param string $conflictMode a value of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
371 * @return Folder New (copied) folder object.
372 */
373 public function moveTo(Folder $targetFolder, $targetFolderName = null, $conflictMode = DuplicationBehavior::RENAME)
374 {
375 return $targetFolder->getStorage()->moveFolder($this, $targetFolder, $targetFolderName, $conflictMode);
376 }
377
378 /**
379 * Checks if a file exists in this folder
380 *
381 * @param string $name
382 * @return bool
383 */
384 public function hasFile($name)
385 {
386 return $this->storage->hasFileInFolder($name, $this);
387 }
388
389 /**
390 * Checks if a folder exists in this folder.
391 *
392 * @param string $name
393 * @return bool
394 */
395 public function hasFolder($name)
396 {
397 return $this->storage->hasFolderInFolder($name, $this);
398 }
399
400 /**
401 * Check if a file operation (= action) is allowed on this folder
402 *
403 * @param string $action Action that can be read, write or delete
404 * @return bool
405 */
406 public function checkActionPermission($action)
407 {
408 try {
409 return $this->getStorage()->checkFolderActionPermission($action, $this);
410 } catch (Exception\ResourcePermissionsUnavailableException $e) {
411 return false;
412 }
413 }
414
415 /**
416 * Updates the properties of this folder, e.g. after re-indexing or moving it.
417 *
418 * NOTE: This method should not be called from outside the File Abstraction Layer (FAL)!
419 *
420 * @param array $properties
421 * @return void
422 * @internal
423 */
424 public function updateProperties(array $properties)
425 {
426 // Setting identifier and name to update values
427 if (isset($properties['identifier'])) {
428 $this->identifier = $properties['identifier'];
429 }
430 if (isset($properties['name'])) {
431 $this->name = $properties['name'];
432 }
433 }
434
435 /**
436 * Prepares the filters in this folder's storage according to a set filter mode.
437 *
438 * @param int $filterMode The filter mode to use; one of the FILTER_MODE_* constants
439 * @return array The backed up filters as an array (NULL if filters were not backed up) and whether to use filters or not (bool)
440 */
441 protected function prepareFiltersInStorage($filterMode)
442 {
443 $backedUpFilters = null;
444 $useFilters = true;
445
446 switch ($filterMode) {
447 case self::FILTER_MODE_USE_OWN_FILTERS:
448 $backedUpFilters = $this->storage->getFileAndFolderNameFilters();
449 $this->storage->setFileAndFolderNameFilters($this->fileAndFolderNameFilters);
450
451 break;
452
453 case self::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS:
454 if (!empty($this->fileAndFolderNameFilters)) {
455 $backedUpFilters = $this->storage->getFileAndFolderNameFilters();
456 foreach ($this->fileAndFolderNameFilters as $filter) {
457 $this->storage->addFileAndFolderNameFilter($filter);
458 }
459 }
460
461 break;
462
463 case self::FILTER_MODE_USE_STORAGE_FILTERS:
464 // nothing to do here
465
466 break;
467
468 case self::FILTER_MODE_NO_FILTERS:
469 $useFilters = false;
470
471 break;
472 }
473 return array($backedUpFilters, $useFilters);
474 }
475
476 /**
477 * Restores the filters of a storage.
478 *
479 * @param array $backedUpFilters The filters to restore; might be NULL if no filters have been backed up, in
480 * which case this method does nothing.
481 * @see prepareFiltersInStorage()
482 */
483 protected function restoreBackedUpFiltersInStorage($backedUpFilters)
484 {
485 if ($backedUpFilters !== null) {
486 $this->storage->setFileAndFolderNameFilters($backedUpFilters);
487 }
488 }
489
490 /**
491 * Sets the filters to use when listing files. These are only used if the filter mode is one of
492 * FILTER_MODE_USE_OWN_FILTERS and FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS
493 *
494 * @param array $filters
495 */
496 public function setFileAndFolderNameFilters(array $filters)
497 {
498 $this->fileAndFolderNameFilters = $filters;
499 }
500
501 /**
502 * Returns the role of this folder (if any). See FolderInterface::ROLE_* constants for possible values.
503 *
504 * @return int
505 */
506 public function getRole()
507 {
508 return $this->storage->getRole($this);
509 }
510
511 /**
512 * Returns the parent folder.
513 *
514 * In non-hierarchical storages, that always is the root folder.
515 *
516 * The parent folder of the root folder is the root folder.
517 *
518 * @return Folder
519 */
520 public function getParentFolder()
521 {
522 return $this->getStorage()->getFolder($this->getStorage()->getFolderIdentifierFromFileIdentifier($this->getIdentifier()));
523 }
524
525 /**
526 * Returns the modification time of the file as Unix timestamp
527 *
528 * @return int
529 */
530 public function getModificationTime()
531 {
532 return $this->storage->getFolderInfo($this)['mtime'];
533 }
534
535 /**
536 * Returns the creation time of the file as Unix timestamp
537 *
538 * @return int
539 */
540 public function getCreationTime()
541 {
542 return $this->storage->getFolderInfo($this)['ctime'];
543 }
544 }