[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\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 * @author Andreas Wolf <andreas.wolf@ikt-werk.de>
32 * @author Ingmar Schlecht <ingmar@typo3.org>
33 */
34 class Folder implements FolderInterface {
35
36 /**
37 * The storage this folder belongs to.
38 *
39 * @var ResourceStorage
40 */
41 protected $storage;
42
43 /**
44 * The identifier of this folder to identify it on the storage.
45 * On some drivers, this is the path to the folder, but drivers could also just
46 * provide any other unique identifier for this folder on the specific storage.
47 *
48 * @var string
49 */
50 protected $identifier;
51
52 /**
53 * The name of this folder
54 *
55 * @var string
56 */
57 protected $name;
58
59 /**
60 * The filters this folder should use for a file list.
61 *
62 * @var callable[]
63 */
64 protected $fileAndFolderNameFilters = array();
65
66 /**
67 * Modes for filter usage in getFiles()/getFolders()
68 */
69 const FILTER_MODE_NO_FILTERS = 0;
70 // Merge local filters into storage's filters
71 const FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS = 1;
72 // Only use the filters provided by the storage
73 const FILTER_MODE_USE_STORAGE_FILTERS = 2;
74 // Only use the filters provided by the current class
75 const FILTER_MODE_USE_OWN_FILTERS = 3;
76
77 /**
78 * Initialization of the folder
79 *
80 * @param ResourceStorage $storage
81 * @param $identifier
82 * @param $name
83 */
84 public function __construct(ResourceStorage $storage, $identifier, $name) {
85 $this->storage = $storage;
86 $this->identifier = rtrim($identifier, '/') . '/';
87 $this->name = $name;
88 }
89
90 /**
91 * Returns the name of this folder.
92 *
93 * @return string
94 */
95 public function getName() {
96 return $this->name;
97 }
98
99 /**
100 * Returns the full path of this folder, from the root.
101 *
102 * @param string $rootId ID of the root folder, NULL to auto-detect
103 *
104 * @return string
105 */
106 public function getReadablePath($rootId = NULL) {
107 $oldPermissionFlag = $this->getStorage()->getEvaluatePermissions();
108 $this->getStorage()->setEvaluatePermissions(FALSE);
109 if ($rootId === NULL) {
110 $rootId = $this->storage->getRootLevelFolder(FALSE)->getIdentifier();
111 }
112 $readablePath = '';
113 if ($this->identifier !== $rootId) {
114 $readablePath = $this->getParentFolder()->getReadablePath($rootId);
115 }
116 $this->getStorage()->setEvaluatePermissions($oldPermissionFlag);
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 boolean $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 integer $start The item to start at
190 * @param integer $numberOfItems The number of items to return
191 * @param integer $filterMode The filter mode to use for the file list.
192 * @param boolean $recursive
193 * @return \TYPO3\CMS\Core\Resource\File[]
194 */
195 public function getFiles($start = 0, $numberOfItems = 0, $filterMode = self::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, $recursive = FALSE) {
196 // Fallback for compatibility with the old method signature variable $useFilters that was used instead of $filterMode
197 if ($filterMode === FALSE) {
198 $useFilters = FALSE;
199 $backedUpFilters = array();
200 } else {
201 list($backedUpFilters, $useFilters) = $this->prepareFiltersInStorage($filterMode);
202 }
203
204 $fileObjects = $this->storage->getFilesInFolder($this, $start, $numberOfItems, $useFilters, $recursive);
205
206 $this->restoreBackedUpFiltersInStorage($backedUpFilters);
207
208 return $fileObjects;
209 }
210
211 /**
212 * Returns amount of all files within this folder, optionally filtered by
213 * the given pattern
214 *
215 * @param array $filterMethods
216 * @param bool $recursive
217 * @return int
218 * @throws Exception\InsufficientFolderAccessPermissionsException
219 */
220 public function getFileCount(array $filterMethods = array(), $recursive = FALSE) {
221 return count($this->storage->getFileIdentifiersInFolder($this->identifier, TRUE, $recursive));
222 }
223
224 /**
225 * Returns the object for a subfolder of the current folder, if it exists.
226 *
227 * @param string $name Name of the subfolder
228 *
229 * @throws \InvalidArgumentException
230 * @return Folder
231 */
232 public function getSubfolder($name) {
233 if (!$this->storage->hasFolderInFolder($name, $this)) {
234 throw new \InvalidArgumentException('Folder "' . $name . '" does not exist in "' . $this->identifier . '"', 1329836110);
235 }
236 /** @var $factory ResourceFactory */
237 $factory = ResourceFactory::getInstance();
238 $folderObject = $factory->createFolderObject($this->storage, $this->identifier . $name . '/', $name);
239 return $folderObject;
240 }
241
242 /**
243 * Returns a list of subfolders
244 *
245 * @param integer $start The item to start at
246 * @param integer $numberOfItems The number of items to return
247 * @param integer $filterMode The filter mode to use for the file list.
248 * @return Folder[]
249 */
250 public function getSubfolders($start = 0, $numberOfItems = 0, $filterMode = self::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS) {
251 list($backedUpFilters, $useFilters) = $this->prepareFiltersInStorage($filterMode);
252 $folderObjects = $this->storage->getFoldersInFolder($this, $start, $numberOfItems, $useFilters);
253 $this->restoreBackedUpFiltersInStorage($backedUpFilters);
254 return $folderObjects;
255 }
256
257 /**
258 * Adds a file from the local server disk. If the file already exists and
259 * overwriting is disabled,
260 *
261 * @param string $localFilePath
262 * @param string $fileName
263 * @param string $conflictMode possible value are 'cancel', 'replace', 'changeName'
264 * @return File The file object
265 */
266 public function addFile($localFilePath, $fileName = NULL, $conflictMode = 'cancel') {
267 $fileName = $fileName ? $fileName : PathUtility::basename($localFilePath);
268 return $this->storage->addFile($localFilePath, $this, $fileName, $conflictMode);
269 }
270
271 /**
272 * Adds an uploaded file into the Storage.
273 *
274 * @param array $uploadedFileData contains information about the uploaded file given by $_FILES['file1']
275 * @param string $conflictMode possible value are 'cancel', 'replace'
276 * @return File The file object
277 */
278 public function addUploadedFile(array $uploadedFileData, $conflictMode = 'cancel') {
279 return $this->storage->addUploadedFile($uploadedFileData, $this, $uploadedFileData['name'], $conflictMode);
280 }
281
282 /**
283 * Renames this folder.
284 *
285 * @param string $newName
286 * @return Folder
287 */
288 public function rename($newName) {
289 return $this->storage->renameFolder($this, $newName);
290 }
291
292 /**
293 * Deletes this folder from its storage. This also means that this object becomes useless.
294 *
295 * @param boolean $deleteRecursively
296 * @return boolean TRUE if deletion succeeded
297 */
298 public function delete($deleteRecursively = TRUE) {
299 return $this->storage->deleteFolder($this, $deleteRecursively);
300 }
301
302 /**
303 * Creates a new blank file
304 *
305 * @param string $fileName
306 * @return File The new file object
307 */
308 public function createFile($fileName) {
309 return $this->storage->createFile($fileName, $this);
310 }
311
312 /**
313 * Creates a new folder
314 *
315 * @param string $folderName
316 * @return Folder The new folder object
317 */
318 public function createFolder($folderName) {
319 return $this->storage->createFolder($folderName, $this);
320 }
321
322 /**
323 * Copies folder to a target folder
324 *
325 * @param Folder $targetFolder Target folder to copy to.
326 * @param string $targetFolderName an optional destination fileName
327 * @param string $conflictMode "overrideExistingFile", "renameNewFile" or "cancel
328 * @return Folder New (copied) folder object.
329 */
330 public function copyTo(Folder $targetFolder, $targetFolderName = NULL, $conflictMode = 'renameNewFile') {
331 return $targetFolder->getStorage()->copyFolder($this, $targetFolder, $targetFolderName, $conflictMode);
332 }
333
334 /**
335 * Moves folder to a target folder
336 *
337 * @param Folder $targetFolder Target folder to move to.
338 * @param string $targetFolderName an optional destination fileName
339 * @param string $conflictMode "overrideExistingFile", "renameNewFile" or "cancel
340 * @return Folder New (copied) folder object.
341 */
342 public function moveTo(Folder $targetFolder, $targetFolderName = NULL, $conflictMode = 'renameNewFile') {
343 return $targetFolder->getStorage()->moveFolder($this, $targetFolder, $targetFolderName, $conflictMode);
344 }
345
346 /**
347 * Checks if a file exists in this folder
348 *
349 * @param string $name
350 * @return boolean
351 */
352 public function hasFile($name) {
353 return $this->storage->hasFileInFolder($name, $this);
354 }
355
356 /**
357 * Checks if a folder exists in this folder.
358 *
359 * @param string $name
360 * @return boolean
361 */
362 public function hasFolder($name) {
363 return $this->storage->hasFolderInFolder($name, $this);
364 }
365
366 /**
367 * Check if a file operation (= action) is allowed on this folder
368 *
369 * @param string $action Action that can be read, write or delete
370 * @return boolean
371 */
372 public function checkActionPermission($action) {
373 try {
374 return $this->getStorage()->checkFolderActionPermission($action, $this);
375 } catch (Exception\ResourcePermissionsUnavailableException $e) {
376 return FALSE;
377 }
378 }
379
380 /**
381 * Updates the properties of this folder, e.g. after re-indexing or moving it.
382 *
383 * NOTE: This method should not be called from outside the File Abstraction Layer (FAL)!
384 *
385 * @param array $properties
386 * @return void
387 * @internal
388 */
389 public function updateProperties(array $properties) {
390 // Setting identifier and name to update values
391 if (isset($properties['identifier'])) {
392 $this->identifier = $properties['identifier'];
393 }
394 if (isset($properties['name'])) {
395 $this->name = $properties['name'];
396 }
397 }
398
399 /**
400 * Prepares the filters in this folder's storage according to a set filter mode.
401 *
402 * @param integer $filterMode The filter mode to use; one of the FILTER_MODE_* constants
403 * @return array The backed up filters as an array (NULL if filters were not backed up) and whether to use filters or not (boolean)
404 */
405 protected function prepareFiltersInStorage($filterMode) {
406 $backedUpFilters = NULL;
407 $useFilters = TRUE;
408
409 switch ($filterMode) {
410 case self::FILTER_MODE_USE_OWN_FILTERS:
411 $backedUpFilters = $this->storage->getFileAndFolderNameFilters();
412 $this->storage->setFileAndFolderNameFilters($this->fileAndFolderNameFilters);
413
414 break;
415
416 case self::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS:
417 if (count($this->fileAndFolderNameFilters) > 0) {
418 $backedUpFilters = $this->storage->getFileAndFolderNameFilters();
419 foreach ($this->fileAndFolderNameFilters as $filter) {
420 $this->storage->addFileAndFolderNameFilter($filter);
421 }
422 }
423
424 break;
425
426 case self::FILTER_MODE_USE_STORAGE_FILTERS:
427 // nothing to do here
428
429 break;
430
431 case self::FILTER_MODE_NO_FILTERS:
432 $useFilters = FALSE;
433
434 break;
435 }
436 return array($backedUpFilters, $useFilters);
437 }
438
439 /**
440 * Restores the filters of a storage.
441 *
442 * @param array $backedUpFilters The filters to restore; might be NULL if no filters have been backed up, in
443 * which case this method does nothing.
444 * @see prepareFiltersInStorage()
445 */
446 protected function restoreBackedUpFiltersInStorage($backedUpFilters) {
447 if ($backedUpFilters !== NULL) {
448 $this->storage->setFileAndFolderNameFilters($backedUpFilters);
449 }
450 }
451
452 /**
453 * Sets the filters to use when listing files. These are only used if the filter mode is one of
454 * FILTER_MODE_USE_OWN_FILTERS and FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS
455 *
456 * @param array $filters
457 */
458 public function setFileAndFolderNameFilters(array $filters) {
459 $this->fileAndFolderNameFilters = $filters;
460 }
461
462 /**
463 * Returns the role of this folder (if any). See FolderInterface::ROLE_* constants for possible values.
464 *
465 * @return integer
466 */
467 public function getRole() {
468 return $this->storage->getRole($this);
469 }
470
471 /**
472 * Returns the parent folder.
473 *
474 * In non-hierarchical storages, that always is the root folder.
475 *
476 * The parent folder of the root folder is the root folder.
477 *
478 * @return Folder
479 */
480 public function getParentFolder() {
481 return $this->getStorage()->getFolder($this->getStorage()->getFolderIdentifierFromFileIdentifier($this->getIdentifier()));
482 }
483 }