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