[BUGFIX] Prevent ResourceDoesNotExistException
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Resource / ResourceStorage.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\InvalidTargetFolderException;
18 use TYPO3\CMS\Core\Resource\Index\FileIndexRepository;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Core\Utility\PathUtility;
21
22 /**
23 * A "mount point" inside the TYPO3 file handling.
24 *
25 * A "storage" object handles
26 * - abstraction to the driver
27 * - permissions (from the driver, and from the user, + capabilities)
28 * - an entry point for files, folders, and for most other operations
29 *
30 * == Driver entry point
31 * The driver itself, that does the actual work on the file system,
32 * is inside the storage but completely shadowed by
33 * the storage, as the storage also handles the abstraction to the
34 * driver
35 *
36 * The storage can be on the local system, but can also be on a remote
37 * system. The combination of driver + configurable capabilities (storage
38 * is read-only e.g.) allows for flexible uses.
39 *
40 *
41 * == Permission system
42 * As all requests have to run through the storage, the storage knows about the
43 * permissions of a BE/FE user, the file permissions / limitations of the driver
44 * and has some configurable capabilities.
45 * Additionally, a BE user can use "filemounts" (known from previous installations)
46 * to limit his/her work-zone to only a subset (identifier and its subfolders/subfolders)
47 * of the user itself.
48 *
49 * Check 1: "User Permissions" [is the user allowed to write a file) [is the user allowed to write a file]
50 * Check 2: "File Mounts" of the User (act as subsets / filters to the identifiers) [is the user allowed to do something in this folder?]
51 * Check 3: "Capabilities" of Storage (then: of Driver) [is the storage/driver writable?]
52 * Check 4: "File permissions" of the Driver [is the folder writable?]
53 *
54 * @author Andreas Wolf <andreas.wolf@typo3.org>
55 * @author Ingmar Schlecht <ingmar@typo3.org>
56 */
57 class ResourceStorage implements ResourceStorageInterface {
58
59 /**
60 * The storage driver instance belonging to this storage.
61 *
62 * @var Driver\DriverInterface
63 */
64 protected $driver;
65
66 /**
67 * The database record for this storage
68 *
69 * @var array
70 */
71 protected $storageRecord;
72
73 /**
74 * The configuration belonging to this storage (decoded from the configuration field).
75 *
76 * @var array
77 */
78 protected $configuration;
79
80 /**
81 * @var Service\FileProcessingService
82 */
83 protected $fileProcessingService;
84
85 /**
86 * Whether to check if file or folder is in user mounts
87 * and the action is allowed for a user
88 * Default is FALSE so that resources are accessible for
89 * front end rendering or admins.
90 *
91 * @var boolean
92 */
93 protected $evaluatePermissions = FALSE;
94
95 /**
96 * User filemounts, added as an array, and used as filters
97 *
98 * @var array
99 */
100 protected $fileMounts = array();
101
102 /**
103 * The file permissions of the user (and their group) merged together and
104 * available as an array
105 *
106 * @var array
107 */
108 protected $userPermissions = array();
109
110 /**
111 * The capabilities of this storage as defined in the storage record.
112 * Also see the CAPABILITY_* constants below
113 *
114 * @var int
115 */
116 protected $capabilities;
117
118 /**
119 * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
120 */
121 protected $signalSlotDispatcher;
122
123 /**
124 * @var Folder
125 */
126 protected $processingFolder;
127
128 /**
129 * All processing folders of this storage used in any storage
130 *
131 * @var Folder[]
132 */
133 protected $processingFolders;
134
135 /**
136 * whether this storage is online or offline in this request
137 *
138 * @var boolean
139 */
140 protected $isOnline = NULL;
141
142 /**
143 * @var boolean
144 */
145 protected $isDefault = FALSE;
146
147 /**
148 * The filters used for the files and folder names.
149 *
150 * @var array
151 */
152 protected $fileAndFolderNameFilters = array();
153
154 /**
155 * Constructor for a storage object.
156 *
157 * @param Driver\DriverInterface $driver
158 * @param array $storageRecord The storage record row from the database
159 */
160 public function __construct(Driver\DriverInterface $driver, array $storageRecord) {
161 $this->storageRecord = $storageRecord;
162 $this->configuration = ResourceFactory::getInstance()->convertFlexFormDataToConfigurationArray($storageRecord['configuration']);
163 $this->capabilities =
164 ($this->storageRecord['is_browsable'] ? self::CAPABILITY_BROWSABLE : 0) |
165 ($this->storageRecord['is_public'] ? self::CAPABILITY_PUBLIC : 0) |
166 ($this->storageRecord['is_writable'] ? self::CAPABILITY_WRITABLE : 0);
167
168 $this->driver = $driver;
169 $this->driver->setStorageUid($storageRecord['uid']);
170 $this->driver->mergeConfigurationCapabilities($this->capabilities);
171 try {
172 $this->driver->processConfiguration();
173 } catch (Exception\InvalidConfigurationException $e) {
174 // configuration error
175 // mark this storage as permanently unusable
176 $this->markAsPermanentlyOffline();
177 }
178 $this->driver->initialize();
179 $this->capabilities = $this->driver->getCapabilities();
180
181 $this->isDefault = (isset($storageRecord['is_default']) && $storageRecord['is_default'] == 1);
182 $this->resetFileAndFolderNameFiltersToDefault();
183 }
184
185 /**
186 * Gets the configuration.
187 *
188 * @return array
189 */
190 public function getConfiguration() {
191 return $this->configuration;
192 }
193
194 /**
195 * Sets the configuration.
196 *
197 * @param array $configuration
198 */
199 public function setConfiguration(array $configuration) {
200 $this->configuration = $configuration;
201 }
202
203 /**
204 * Gets the storage record.
205 *
206 * @return array
207 */
208 public function getStorageRecord() {
209 return $this->storageRecord;
210 }
211
212 /**
213 * Sets the storage that belongs to this storage.
214 *
215 * @param Driver\DriverInterface $driver
216 * @return ResourceStorage
217 */
218 public function setDriver(Driver\DriverInterface $driver) {
219 $this->driver = $driver;
220 return $this;
221 }
222
223 /**
224 * Returns the driver object belonging to this storage.
225 *
226 * @return Driver\DriverInterface
227 */
228 protected function getDriver() {
229 return $this->driver;
230 }
231
232 /**
233 * Deprecated function, don't use it. Will be removed in some later revision.
234 *
235 * @param string $identifier
236 *
237 * @throws \BadMethodCallException
238 */
239 public function getFolderByIdentifier($identifier) {
240 throw new \BadMethodCallException(
241 'Function TYPO3\\CMS\\Core\\Resource\\ResourceStorage::getFolderByIdentifier() has been renamed to just getFolder(). Please fix the method call.',
242 1333754514
243 );
244 }
245
246 /**
247 * Deprecated function, don't use it. Will be removed in some later revision.
248 *
249 * @param string $identifier
250 *
251 * @throws \BadMethodCallException
252 */
253 public function getFileByIdentifier($identifier) {
254 throw new \BadMethodCallException(
255 'Function TYPO3\\CMS\\Core\\Resource\\ResourceStorage::getFileByIdentifier() has been renamed to just getFileInfoByIdentifier(). ' . 'Please fix the method call.',
256 1333754533
257 );
258 }
259
260 /**
261 * Returns the name of this storage.
262 *
263 * @return string
264 */
265 public function getName() {
266 return $this->storageRecord['name'];
267 }
268
269 /**
270 * Returns the UID of this storage.
271 *
272 * @return int
273 */
274 public function getUid() {
275 return (int)$this->storageRecord['uid'];
276 }
277
278 /**
279 * Tells whether there are children in this storage.
280 *
281 * @return bool
282 */
283 public function hasChildren() {
284 return TRUE;
285 }
286
287 /*********************************
288 * Capabilities
289 ********************************/
290 /**
291 * Returns the capabilities of this storage.
292 *
293 * @return int
294 * @see CAPABILITY_* constants
295 */
296 public function getCapabilities() {
297 return (int)$this->capabilities;
298 }
299
300 /**
301 * Returns TRUE if this storage has the given capability.
302 *
303 * @param int $capability A capability, as defined in a CAPABILITY_* constant
304 * @return bool
305 */
306 protected function hasCapability($capability) {
307 return ($this->capabilities & $capability) == $capability;
308 }
309
310 /**
311 * Returns TRUE if this storage is publicly available. This is just a
312 * configuration option and does not mean that it really *is* public. OTOH
313 * a storage that is marked as not publicly available will trigger the file
314 * publishing mechanisms of TYPO3.
315 *
316 * @return bool
317 */
318 public function isPublic() {
319 return $this->hasCapability(self::CAPABILITY_PUBLIC);
320 }
321
322 /**
323 * Returns TRUE if this storage is writable. This is determined by the
324 * driver and the storage configuration; user permissions are not taken into account.
325 *
326 * @return bool
327 */
328 public function isWritable() {
329 return $this->hasCapability(self::CAPABILITY_WRITABLE);
330 }
331
332 /**
333 * Returns TRUE if this storage is browsable by a (backend) user of TYPO3.
334 *
335 * @return bool
336 */
337 public function isBrowsable() {
338 return $this->isOnline() && $this->hasCapability(self::CAPABILITY_BROWSABLE);
339 }
340
341 /**
342 * Returns TRUE if the identifiers used by this storage are case-sensitive.
343 *
344 * @return bool
345 */
346 public function usesCaseSensitiveIdentifiers() {
347 return $this->driver->isCaseSensitiveFileSystem();
348 }
349
350 /**
351 * Returns TRUE if this storage is browsable by a (backend) user of TYPO3.
352 *
353 * @return bool
354 */
355 public function isOnline() {
356 if ($this->isOnline === NULL) {
357 if ($this->getUid() === 0) {
358 $this->isOnline = TRUE;
359 }
360 // the storage is not marked as online for a longer time
361 if ($this->storageRecord['is_online'] == 0) {
362 $this->isOnline = FALSE;
363 }
364 if ($this->isOnline !== FALSE) {
365 // all files are ALWAYS available in the frontend
366 if (TYPO3_MODE === 'FE') {
367 $this->isOnline = TRUE;
368 } else {
369 // check if the storage is disabled temporary for now
370 $registryObject = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Registry');
371 $offlineUntil = $registryObject->get('core', 'sys_file_storage-' . $this->getUid() . '-offline-until');
372 if ($offlineUntil && $offlineUntil > time()) {
373 $this->isOnline = FALSE;
374 } else {
375 $this->isOnline = TRUE;
376 }
377 }
378 }
379 }
380 return $this->isOnline;
381 }
382
383 /**
384 * Blows the "fuse" and marks the storage as offline.
385 *
386 * Can only be modified by an admin.
387 *
388 * Typically, this is only done if the configuration is wrong.
389 *
390 * @return void
391 */
392 public function markAsPermanentlyOffline() {
393 if ($this->getUid() > 0) {
394 // @todo: move this to the storage repository
395 $GLOBALS['TYPO3_DB']->exec_UPDATEquery('sys_file_storage', 'uid=' . (int)$this->getUid(), array('is_online' => 0));
396 }
397 $this->storageRecord['is_online'] = 0;
398 $this->isOnline = FALSE;
399 }
400
401 /**
402 * Marks this storage as offline for the next 5 minutes.
403 *
404 * Non-permanent: This typically happens for remote storages
405 * that are "flaky" and not available all the time.
406 *
407 * @return void
408 */
409 public function markAsTemporaryOffline() {
410 $registryObject = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Registry');
411 $registryObject->set('core', 'sys_file_storage-' . $this->getUid() . '-offline-until', time() + 60 * 5);
412 $this->storageRecord['is_online'] = 0;
413 $this->isOnline = FALSE;
414 }
415
416 /*********************************
417 * User Permissions / File Mounts
418 ********************************/
419 /**
420 * Adds a filemount as a "filter" for users to only work on a subset of a
421 * storage object
422 *
423 * @param string $folderIdentifier
424 * @param array $additionalData
425 *
426 * @throws Exception\FolderDoesNotExistException
427 * @return void
428 */
429 public function addFileMount($folderIdentifier, $additionalData = array()) {
430 // check for the folder before we add it as a filemount
431 if ($this->driver->folderExists($folderIdentifier) === FALSE) {
432 // if there is an error, this is important and should be handled
433 // as otherwise the user would see the whole storage without any restrictions for the filemounts
434 throw new Exception\FolderDoesNotExistException('Folder for file mount ' . $folderIdentifier . ' does not exist.', 1334427099);
435 }
436 $data = $this->driver->getFolderInfoByIdentifier($folderIdentifier);
437 $folderObject = ResourceFactory::getInstance()->createFolderObject($this, $data['identifier'], $data['name']);
438 // Use the canonical identifier instead of the user provided one!
439 $folderIdentifier = $folderObject->getIdentifier();
440 if (
441 !empty($this->fileMounts[$folderIdentifier])
442 && empty($this->fileMounts[$folderIdentifier]['read_only'])
443 && !empty($additionalData['read_only'])
444 ) {
445 // Do not overwrite a regular mount with a read only mount
446 return;
447 }
448 if (empty($additionalData)) {
449 $additionalData = array(
450 'path' => $folderIdentifier,
451 'title' => $folderIdentifier,
452 'folder' => $folderObject
453 );
454 } else {
455 $additionalData['folder'] = $folderObject;
456 if (!isset($additionalData['title'])) {
457 $additionalData['title'] = $folderIdentifier;
458 }
459 }
460 $this->fileMounts[$folderIdentifier] = $additionalData;
461 }
462
463 /**
464 * Returns all file mounts that are registered with this storage.
465 *
466 * @return array
467 */
468 public function getFileMounts() {
469 return $this->fileMounts;
470 }
471
472 /**
473 * Checks if the given subject is within one of the registered user
474 * file mounts. If not, working with the file is not permitted for the user.
475 *
476 * @param ResourceInterface $subject file or folder
477 * @param bool $checkWriteAccess If true, it is not only checked if the subject is within the file mount but also whether it isn't a read only file mount
478 * @return bool
479 */
480 public function isWithinFileMountBoundaries($subject, $checkWriteAccess = FALSE) {
481 if (!$this->evaluatePermissions) {
482 return TRUE;
483 }
484 $isWithinFileMount = FALSE;
485 if (!$subject) {
486 $subject = $this->getRootLevelFolder();
487 }
488 $identifier = $subject->getIdentifier();
489
490 // Allow access to processing folder
491 if ($this->isWithinProcessingFolder($identifier)) {
492 $isWithinFileMount = TRUE;
493 } else {
494 // Check if the identifier of the subject is within at
495 // least one of the file mounts
496 $writableFileMountAvailable = FALSE;
497 foreach ($this->fileMounts as $fileMount) {
498 if ($this->driver->isWithin($fileMount['folder']->getIdentifier(), $identifier)) {
499 $isWithinFileMount = TRUE;
500 if (!$checkWriteAccess) {
501 break;
502 } elseif (empty($fileMount['read_only'])) {
503 $writableFileMountAvailable = TRUE;
504 break;
505 }
506 }
507 }
508 $isWithinFileMount = $checkWriteAccess ? $writableFileMountAvailable : $isWithinFileMount;
509 }
510 return $isWithinFileMount;
511 }
512
513 /**
514 * Sets whether the permissions to access or write
515 * into this storage should be checked or not.
516 *
517 * @param boolean $evaluatePermissions
518 */
519 public function setEvaluatePermissions($evaluatePermissions) {
520 $this->evaluatePermissions = (bool)$evaluatePermissions;
521 }
522
523 /**
524 * Gets whether the permissions to access or write
525 * into this storage should be checked or not.
526 *
527 * @return bool $evaluatePermissions
528 */
529 public function getEvaluatePermissions() {
530 return $this->evaluatePermissions;
531 }
532
533 /**
534 * Sets the user permissions of the storage.
535 *
536 * @param array $userPermissions
537 * @return void
538 */
539 public function setUserPermissions(array $userPermissions) {
540 $this->userPermissions = $userPermissions;
541 }
542
543 /**
544 * Checks if the ACL settings allow for a certain action
545 * (is a user allowed to read a file or copy a folder).
546 *
547 * @param string $action
548 * @param string $type either File or Folder
549 * @return bool
550 */
551 public function checkUserActionPermission($action, $type) {
552 if (!$this->evaluatePermissions) {
553 return TRUE;
554 }
555
556 $allow = FALSE;
557 if (!empty($this->userPermissions[strtolower($action) . ucfirst(strtolower($type))])) {
558 $allow = TRUE;
559 }
560
561 return $allow;
562 }
563
564 /**
565 * Checks if a file operation (= action) is allowed on a
566 * File/Folder/Storage (= subject).
567 *
568 * This method, by design, does not throw exceptions or do logging.
569 * Besides the usage from other methods in this class, it is also used by
570 * the File List UI to check whether an action is allowed and whether action
571 * related UI elements should thus be shown (move icon, edit icon, etc.)
572 *
573 * @param string $action action, can be read, write, delete
574 * @param FileInterface $file
575 * @return bool
576 */
577 public function checkFileActionPermission($action, FileInterface $file) {
578 $isProcessedFile = $file instanceof ProcessedFile;
579 // Check 1: Does the user have permission to perform the action? e.g. "readFile"
580 if (!$isProcessedFile && $this->checkUserActionPermission($action, 'File') === FALSE) {
581 return FALSE;
582 }
583 // Check 2: No action allowed on files for denied file extensions
584 if (!$this->checkFileExtensionPermission($file->getName())) {
585 return FALSE;
586 }
587 $isReadCheck = FALSE;
588 if (in_array($action, array('read', 'copy', 'move'), TRUE)) {
589 $isReadCheck = TRUE;
590 }
591 $isWriteCheck = FALSE;
592 if (in_array($action, array('add', 'write', 'move', 'rename', 'unzip', 'delete'), TRUE)) {
593 $isWriteCheck = TRUE;
594 }
595 // Check 3: Does the user have the right to perform the action?
596 // (= is he within the file mount borders)
597 if (!$isProcessedFile && !$this->isWithinFileMountBoundaries($file, $isWriteCheck)) {
598 return FALSE;
599 }
600
601 $isMissing = FALSE;
602 if (!$isProcessedFile && $file instanceof File) {
603 $isMissing = $file->isMissing();
604 }
605
606 if ($this->driver->fileExists($file->getIdentifier()) === FALSE) {
607 $file->setMissing(TRUE);
608 $isMissing = TRUE;
609 }
610
611 // Check 4: Check the capabilities of the storage (and the driver)
612 if ($isWriteCheck && ($isMissing || !$this->isWritable())) {
613 return FALSE;
614 }
615
616 // Check 5: "File permissions" of the driver (only when file isn't marked as missing)
617 if (!$isMissing) {
618 $filePermissions = $this->driver->getPermissions($file->getIdentifier());
619 if ($isReadCheck && !$filePermissions['r']) {
620 return FALSE;
621 }
622 if ($isWriteCheck && !$filePermissions['w']) {
623 return FALSE;
624 }
625 }
626 return TRUE;
627 }
628
629 /**
630 * Checks if a folder operation (= action) is allowed on a Folder.
631 *
632 * This method, by design, does not throw exceptions or do logging.
633 * See the checkFileActionPermission() method above for the reasons.
634 *
635 * @param string $action
636 * @param Folder $folder
637 * @return bool
638 */
639 public function checkFolderActionPermission($action, Folder $folder = NULL) {
640 // Check 1: Does the user have permission to perform the action? e.g. "writeFolder"
641 if ($this->checkUserActionPermission($action, 'Folder') === FALSE) {
642 return FALSE;
643 }
644
645 // If we do not have a folder here, we cannot do further checks
646 if ($folder === NULL) {
647 return TRUE;
648 }
649
650 $isReadCheck = FALSE;
651 if (in_array($action, array('read', 'copy'), TRUE)) {
652 $isReadCheck = TRUE;
653 }
654 $isWriteCheck = FALSE;
655 if (in_array($action, array('add', 'move', 'write', 'delete', 'rename'), TRUE)) {
656 $isWriteCheck = TRUE;
657 }
658 // Check 2: Does the user has the right to perform the action?
659 // (= is he within the file mount borders)
660 if (!$this->isWithinFileMountBoundaries($folder, $isWriteCheck)) {
661 return FALSE;
662 }
663 // Check 3: Check the capabilities of the storage (and the driver)
664 if ($isReadCheck && !$this->isBrowsable()) {
665 return FALSE;
666 }
667 if ($isWriteCheck && !$this->isWritable()) {
668 return FALSE;
669 }
670
671 // Check 4: "Folder permissions" of the driver
672 $folderPermissions = $this->driver->getPermissions($folder->getIdentifier());
673 if ($isReadCheck && !$folderPermissions['r']) {
674 return FALSE;
675 }
676 if ($isWriteCheck && !$folderPermissions['w']) {
677 return FALSE;
678 }
679 return TRUE;
680 }
681
682 /**
683 * If the fileName is given, checks it against the
684 * TYPO3_CONF_VARS[BE][fileDenyPattern] + and if the file extension is allowed.
685 *
686 * @param string $fileName full filename
687 * @return bool TRUE if extension/filename is allowed
688 */
689 protected function checkFileExtensionPermission($fileName) {
690 if (!$this->evaluatePermissions) {
691 return TRUE;
692 }
693 $fileName = $this->driver->sanitizeFileName($fileName);
694 $isAllowed = GeneralUtility::verifyFilenameAgainstDenyPattern($fileName);
695 if ($isAllowed) {
696 $fileExtension = strtolower(PathUtility::pathinfo($fileName, PATHINFO_EXTENSION));
697 // Set up the permissions for the file extension
698 $fileExtensionPermissions = $GLOBALS['TYPO3_CONF_VARS']['BE']['fileExtensions']['webspace'];
699 $fileExtensionPermissions['allow'] = GeneralUtility::uniqueList(strtolower($fileExtensionPermissions['allow']));
700 $fileExtensionPermissions['deny'] = GeneralUtility::uniqueList(strtolower($fileExtensionPermissions['deny']));
701 if ($fileExtension !== '') {
702 // If the extension is found amongst the allowed types, we return TRUE immediately
703 if ($fileExtensionPermissions['allow'] === '*' || GeneralUtility::inList($fileExtensionPermissions['allow'], $fileExtension)) {
704 return TRUE;
705 }
706 // If the extension is found amongst the denied types, we return FALSE immediately
707 if ($fileExtensionPermissions['deny'] === '*' || GeneralUtility::inList($fileExtensionPermissions['deny'], $fileExtension)) {
708 return FALSE;
709 }
710 // If no match we return TRUE
711 return TRUE;
712 } else {
713 if ($fileExtensionPermissions['allow'] === '*') {
714 return TRUE;
715 }
716 if ($fileExtensionPermissions['deny'] === '*') {
717 return FALSE;
718 }
719 return TRUE;
720 }
721 }
722 return FALSE;
723 }
724
725 /**
726 * Assures read permission for given folder.
727 *
728 * @param Folder $folder If a folder is given, mountpoints are checked. If not only user folder read permissions are checked.
729 * @return void
730 * @throws Exception\InsufficientFolderAccessPermissionsException
731 */
732 protected function assureFolderReadPermission(Folder $folder = NULL) {
733 if (!$this->checkFolderActionPermission('read', $folder)) {
734 if ($folder === NULL) {
735 throw new Exception\InsufficientFolderAccessPermissionsException(
736 'You are not allowed to read folders',
737 1430657869
738 );
739 } else {
740 throw new Exception\InsufficientFolderAccessPermissionsException(
741 'You are not allowed to access the given folder: "' . $folder->getName() . '"',
742 1375955684
743 );
744 }
745 }
746 }
747
748 /**
749 * Assures delete permission for given folder.
750 *
751 * @param Folder $folder If a folder is given, mountpoints are checked. If not only user folder delete permissions are checked.
752 * @param boolean $checkDeleteRecursively
753 * @return void
754 * @throws Exception\InsufficientFolderAccessPermissionsException
755 * @throws Exception\InsufficientFolderWritePermissionsException
756 * @throws Exception\InsufficientUserPermissionsException
757 */
758 protected function assureFolderDeletePermission(Folder $folder, $checkDeleteRecursively) {
759 // Check user permissions for recursive deletion if it is requested
760 if ($checkDeleteRecursively && !$this->checkUserActionPermission('recursivedelete', 'Folder')) {
761 throw new Exception\InsufficientUserPermissionsException('You are not allowed to delete folders recursively', 1377779423);
762 }
763 // Check user action permission
764 if (!$this->checkFolderActionPermission('delete', $folder)) {
765 throw new Exception\InsufficientFolderAccessPermissionsException('You are not allowed to delete the given folder', 1377779039);
766 }
767 // Check if the user has write permissions to folders
768 // Would be good if we could check for actual write permissions in the containig folder
769 // but we cannot since we have no access to the containing folder of this file.
770 if (!$this->checkUserActionPermission('write', 'Folder')) {
771 throw new Exception\InsufficientFolderWritePermissionsException('Writing to folders is not allowed.', 1377779111);
772 }
773 }
774
775 /**
776 * Assures read permission for given file.
777 *
778 * @param FileInterface $file
779 * @return void
780 * @throws Exception\InsufficientFileAccessPermissionsException
781 * @throws Exception\IllegalFileExtensionException
782 */
783 protected function assureFileReadPermission(FileInterface $file) {
784 if (!$this->checkFileActionPermission('read', $file)) {
785 throw new Exception\InsufficientFileAccessPermissionsException('You are not allowed to access that file.', 1375955429);
786 }
787 if (!$this->checkFileExtensionPermission($file->getName())) {
788 throw new Exception\IllegalFileExtensionException('You are not allowed to use that file extension', 1375955430);
789 }
790 }
791
792 /**
793 * Assures write permission for given file.
794 *
795 * @param FileInterface $file
796 * @return void
797 * @throws Exception\IllegalFileExtensionException
798 * @throws Exception\InsufficientFileWritePermissionsException
799 * @throws Exception\InsufficientUserPermissionsException
800 */
801 protected function assureFileWritePermissions(FileInterface $file) {
802 // Check if user is allowed to write the file and $file is writable
803 if (!$this->checkFileActionPermission('write', $file)) {
804 throw new Exception\InsufficientFileWritePermissionsException('Writing to file "' . $file->getIdentifier() . '" is not allowed.', 1330121088);
805 }
806 if (!$this->checkFileExtensionPermission($file->getName())) {
807 throw new Exception\IllegalFileExtensionException('You are not allowed to edit a file with extension "' . $file->getExtension() . '"', 1366711933);
808 }
809 }
810
811 /**
812 * Assures delete permission for given file.
813 *
814 * @param FileInterface $file
815 * @return void
816 * @throws Exception\IllegalFileExtensionException
817 * @throws Exception\InsufficientFileWritePermissionsException
818 * @throws Exception\InsufficientFolderWritePermissionsException
819 */
820 protected function assureFileDeletePermissions(FileInterface $file) {
821 // Check for disallowed file extensions
822 if (!$this->checkFileExtensionPermission($file->getName())) {
823 throw new Exception\IllegalFileExtensionException('You are not allowed to delete a file with extension "' . $file->getExtension() . '"', 1377778916);
824 }
825 // Check further permissions if file is not a processed file
826 if (!$file instanceof ProcessedFile) {
827 // Check if user is allowed to delete the file and $file is writable
828 if (!$this->checkFileActionPermission('delete', $file)) {
829 throw new Exception\InsufficientFileWritePermissionsException('You are not allowed to delete the file "' . $file->getIdentifier() . '"', 1319550425);
830 }
831 // Check if the user has write permissions to folders
832 // Would be good if we could check for actual write permissions in the containig folder
833 // but we cannot since we have no access to the containing folder of this file.
834 if (!$this->checkUserActionPermission('write', 'Folder')) {
835 throw new Exception\InsufficientFolderWritePermissionsException('Writing to folders is not allowed.', 1377778702);
836 }
837 }
838 }
839
840 /**
841 * Checks if a file has the permission to be uploaded to a Folder/Storage.
842 * If not, throws an exception.
843 *
844 * @param string $localFilePath DEPRECATED the temporary file name from $_FILES['file1']['tmp_name']
845 * @param Folder $targetFolder
846 * @param string $targetFileName the destination file name $_FILES['file1']['name']
847 * @return void
848 *
849 * @throws Exception\InsufficientFolderWritePermissionsException
850 * @throws Exception\UploadException
851 * @throws Exception\IllegalFileExtensionException
852 * @throws Exception\UploadSizeException
853 * @throws Exception\InsufficientUserPermissionsException
854 */
855 protected function assureFileAddPermissions($localFilePath, $targetFolder, $targetFileName) {
856 // Check for a valid file extension
857 if (!$this->checkFileExtensionPermission($targetFileName)) {
858 throw new Exception\IllegalFileExtensionException('Extension of file name is not allowed in "' . $targetFileName . '"!', 1322120271);
859 }
860 // Makes sure the user is allowed to upload
861 if (!$this->checkUserActionPermission('add', 'File')) {
862 throw new Exception\InsufficientUserPermissionsException('You are not allowed to add files to this storage "' . $this->getUid() . '"', 1376992145);
863 }
864 // Check if targetFolder is writable
865 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
866 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1322120356);
867 }
868 }
869
870 /**
871 * Checks if a file has the permission to be uploaded to a Folder/Storage.
872 * If not, throws an exception.
873 *
874 * @param string $localFilePath the temporary file name from $_FILES['file1']['tmp_name']
875 * @param Folder $targetFolder
876 * @param string $targetFileName the destination file name $_FILES['file1']['name']
877 * @param int $uploadedFileSize
878 * @return void
879 *
880 * @throws Exception\InsufficientFolderWritePermissionsException
881 * @throws Exception\UploadException
882 * @throws Exception\IllegalFileExtensionException
883 * @throws Exception\UploadSizeException
884 * @throws Exception\InsufficientUserPermissionsException
885 */
886 protected function assureFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileSize) {
887 // Makes sure this is an uploaded file
888 if (!is_uploaded_file($localFilePath)) {
889 throw new Exception\UploadException('The upload has failed, no uploaded file found!', 1322110455);
890 }
891 // Max upload size (kb) for files.
892 $maxUploadFileSize = GeneralUtility::getMaxUploadFileSize() * 1024;
893 if ($uploadedFileSize >= $maxUploadFileSize) {
894 unlink($localFilePath);
895 throw new Exception\UploadSizeException('The uploaded file exceeds the size-limit of ' . $maxUploadFileSize . ' bytes', 1322110041);
896 }
897 $this->assureFileAddPermissions('', $targetFolder, $targetFileName);
898 }
899
900 /**
901 * Checks for permissions to move a file.
902 *
903 * @throws \RuntimeException
904 * @throws Exception\InsufficientFolderAccessPermissionsException
905 * @throws Exception\InsufficientUserPermissionsException
906 * @throws Exception\IllegalFileExtensionException
907 * @param FileInterface $file
908 * @param Folder $targetFolder
909 * @param string $targetFileName
910 * @return void
911 */
912 protected function assureFileMovePermissions(FileInterface $file, Folder $targetFolder, $targetFileName) {
913 // Check if targetFolder is within this storage
914 if ($this->getUid() !== $targetFolder->getStorage()->getUid()) {
915 throw new \RuntimeException();
916 }
917 // Check for a valid file extension
918 if (!$this->checkFileExtensionPermission($targetFileName)) {
919 throw new Exception\IllegalFileExtensionException('Extension of file name is not allowed in "' . $targetFileName . '"!', 1378243279);
920 }
921 // Check if user is allowed to move and $file is readable and writable
922 if (!$file->getStorage()->checkFileActionPermission('move', $file)) {
923 throw new Exception\InsufficientUserPermissionsException('You are not allowed to move files to storage "' . $this->getUid() . '"', 1319219349);
924 }
925 // Check if target folder is writable
926 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
927 throw new Exception\InsufficientFolderAccessPermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319219350);
928 }
929 }
930
931 /**
932 * Checks for permissions to rename a file.
933 *
934 * @param FileInterface $file
935 * @param string $targetFileName
936 * @throws Exception\InsufficientFileWritePermissionsException
937 * @throws Exception\IllegalFileExtensionException
938 * @throws Exception\InsufficientFileReadPermissionsException
939 * @throws Exception\InsufficientUserPermissionsException
940 * @return void
941 */
942 protected function assureFileRenamePermissions(FileInterface $file, $targetFileName) {
943 // Check if file extension is allowed
944 if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkFileExtensionPermission($file->getName())) {
945 throw new Exception\IllegalFileExtensionException('You are not allowed to rename a file with to this extension', 1371466663);
946 }
947 // Check if user is allowed to rename
948 if (!$this->checkFileActionPermission('rename', $file)) {
949 throw new Exception\InsufficientUserPermissionsException('You are not allowed to rename files."', 1319219351);
950 }
951 // Check if the user is allowed to write to folders
952 // Although it would be good to check, we cannot check here if the folder actually is writable
953 // because we do not know in which folder the file resides.
954 // So we rely on the driver to throw an exception in case the renaming failed.
955 if (!$this->checkFolderActionPermission('write')) {
956 throw new Exception\InsufficientFileWritePermissionsException('You are not allowed to write to folders', 1319219352);
957 }
958 }
959
960 /**
961 * Check if a file has the permission to be copied on a File/Folder/Storage,
962 * if not throw an exception
963 *
964 * @param FileInterface $file
965 * @param Folder $targetFolder
966 * @param string $targetFileName
967 *
968 * @throws Exception
969 * @throws Exception\InsufficientFolderWritePermissionsException
970 * @throws Exception\IllegalFileExtensionException
971 * @throws Exception\InsufficientFileReadPermissionsException
972 * @throws Exception\InsufficientUserPermissionsException
973 * @return void
974 */
975 protected function assureFileCopyPermissions(FileInterface $file, Folder $targetFolder, $targetFileName) {
976 // Check if targetFolder is within this storage, this should never happen
977 if ($this->getUid() != $targetFolder->getStorage()->getUid()) {
978 throw new Exception('The operation of the folder cannot be called by this storage "' . $this->getUid() . '"', 1319550405);
979 }
980 // Check if user is allowed to copy
981 if (!$file->getStorage()->checkFileActionPermission('copy', $file)) {
982 throw new Exception\InsufficientFileReadPermissionsException('You are not allowed to copy the file "' . $file->getIdentifier() . '"', 1319550426);
983 }
984 // Check if targetFolder is writable
985 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
986 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319550435);
987 }
988 // Check for a valid file extension
989 if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkFileExtensionPermission($file->getName())) {
990 throw new Exception\IllegalFileExtensionException('You are not allowed to copy a file of that type.', 1319553317);
991 }
992 }
993
994 /**
995 * Check if a file has the permission to be copied on a File/Folder/Storage,
996 * if not throw an exception
997 *
998 * @param FolderInterface $folderToCopy
999 * @param FolderInterface $targetParentFolder
1000 * @return void
1001 *
1002 * @throws Exception
1003 * @throws Exception\InsufficientFolderWritePermissionsException
1004 * @throws Exception\IllegalFileExtensionException
1005 * @throws Exception\InsufficientFileReadPermissionsException
1006 * @throws Exception\InsufficientUserPermissionsException
1007 * @throws \RuntimeException
1008 */
1009 protected function assureFolderCopyPermissions(FolderInterface $folderToCopy, FolderInterface $targetParentFolder) {
1010 // Check if targetFolder is within this storage, this should never happen
1011 if ($this->getUid() !== $targetParentFolder->getStorage()->getUid()) {
1012 throw new Exception('The operation of the folder cannot be called by this storage "' . $this->getUid() . '"', 1377777624);
1013 }
1014 if (!$folderToCopy instanceof Folder) {
1015 throw new \RuntimeException('The folder "' . $folderToCopy->getIdentifier() . '" to copy is not of type Folder.', 1384209020);
1016 }
1017 // Check if user is allowed to copy and the folder is readable
1018 if (!$folderToCopy->getStorage()->checkFolderActionPermission('copy', $folderToCopy)) {
1019 throw new Exception\InsufficientFileReadPermissionsException('You are not allowed to copy the folder "' . $folderToCopy->getIdentifier() . '"', 1377777629);
1020 }
1021 if (!$targetParentFolder instanceof Folder) {
1022 throw new \RuntimeException('The target folder "' . $targetParentFolder->getIdentifier() . '" is not of type Folder.', 1384209021);
1023 }
1024 // Check if targetFolder is writable
1025 if (!$this->checkFolderActionPermission('write', $targetParentFolder)) {
1026 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetParentFolder->getIdentifier() . '"', 1377777635);
1027 }
1028 }
1029
1030 /**
1031 * Check if a file has the permission to be copied on a File/Folder/Storage,
1032 * if not throw an exception
1033 *
1034 * @param FolderInterface $folderToMove
1035 * @param FolderInterface $targetParentFolder
1036 *
1037 * @throws \InvalidArgumentException
1038 * @throws Exception\InsufficientFolderWritePermissionsException
1039 * @throws Exception\IllegalFileExtensionException
1040 * @throws Exception\InsufficientFileReadPermissionsException
1041 * @throws Exception\InsufficientUserPermissionsException
1042 * @throws \RuntimeException
1043 * @return void
1044 */
1045 protected function assureFolderMovePermissions(FolderInterface $folderToMove, FolderInterface $targetParentFolder) {
1046 // Check if targetFolder is within this storage, this should never happen
1047 if ($this->getUid() !== $targetParentFolder->getStorage()->getUid()) {
1048 throw new \InvalidArgumentException('Cannot move a folder into a folder that does not belong to this storage.', 1325777289);
1049 }
1050 if (!$folderToMove instanceof Folder) {
1051 throw new \RuntimeException('The folder "' . $folderToMove->getIdentifier() . '" to move is not of type Folder.', 1384209022);
1052 }
1053 // Check if user is allowed to move and the folder is writable
1054 // In fact we would need to check if the parent folder of the folder to move is writable also
1055 // But as of now we cannot extract the parent folder from this folder
1056 if (!$folderToMove->getStorage()->checkFolderActionPermission('move', $folderToMove)) {
1057 throw new Exception\InsufficientFileReadPermissionsException('You are not allowed to copy the folder "' . $folderToMove->getIdentifier() . '"', 1377778045);
1058 }
1059 if (!$targetParentFolder instanceof Folder) {
1060 throw new \RuntimeException('The target folder "' . $targetParentFolder->getIdentifier() . '" is not of type Folder.', 1384209023);
1061 }
1062 // Check if targetFolder is writable
1063 if (!$this->checkFolderActionPermission('write', $targetParentFolder)) {
1064 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetParentFolder->getIdentifier() . '"', 1377778049);
1065 }
1066 }
1067
1068 /********************
1069 * FILE ACTIONS
1070 ********************/
1071 /**
1072 * Moves a file from the local filesystem to this storage.
1073 *
1074 * @param string $localFilePath The file on the server's hard disk to add.
1075 * @param Folder $targetFolder The target path, without the fileName
1076 * @param string $targetFileName The fileName. If not set, the local file name is used.
1077 * @param string $conflictMode possible value are 'cancel', 'replace', 'changeName'
1078 *
1079 * @throws \InvalidArgumentException
1080 * @throws Exception\ExistingTargetFileNameException
1081 * @return FileInterface
1082 */
1083 public function addFile($localFilePath, Folder $targetFolder, $targetFileName = '', $conflictMode = 'changeName') {
1084 $localFilePath = PathUtility::getCanonicalPath($localFilePath);
1085 if (!file_exists($localFilePath)) {
1086 throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1319552745);
1087 }
1088 $targetFolder = $targetFolder ?: $this->getDefaultFolder();
1089 $targetFileName = $this->driver->sanitizeFileName($targetFileName ?: PathUtility::basename($localFilePath));
1090 $this->assureFileAddPermissions('', $targetFolder, $targetFileName);
1091
1092 // We do not care whether the file exists yet because $targetFileName may be changed by an
1093 // external slot and only then we should check how to proceed according to $conflictMode
1094 $targetFileName = $this->emitPreFileAddSignal($targetFileName, $targetFolder, $localFilePath);
1095
1096 if ($conflictMode === 'cancel' && $this->driver->fileExistsInFolder($targetFileName, $targetFolder->getIdentifier())) {
1097 throw new Exception\ExistingTargetFileNameException('File "' . $targetFileName . '" already exists in folder ' . $targetFolder->getIdentifier(), 1322121068);
1098 } elseif ($conflictMode === 'changeName') {
1099 $targetFileName = $this->getUniqueName($targetFolder, $targetFileName);
1100 }
1101
1102 $fileIdentifier = $this->driver->addFile($localFilePath, $targetFolder->getIdentifier(), $targetFileName);
1103 $file = ResourceFactory::getInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $fileIdentifier);
1104
1105 $this->emitPostFileAddSignal($file, $targetFolder);
1106
1107 return $file;
1108 }
1109
1110 /**
1111 * Updates a processed file with a new file from the local filesystem.
1112 *
1113 * @param string $localFilePath
1114 * @param ProcessedFile $processedFile
1115 * @param Folder $processingFolder
1116 * @return FileInterface
1117 * @throws \InvalidArgumentException
1118 * @internal use only
1119 */
1120 public function updateProcessedFile($localFilePath, ProcessedFile $processedFile, Folder $processingFolder = NULL) {
1121 if (!file_exists($localFilePath)) {
1122 throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1319552746);
1123 }
1124 if ($processingFolder === NULL) {
1125 $processingFolder = $this->getProcessingFolder();
1126 }
1127 $fileIdentifier = $this->driver->addFile($localFilePath, $processingFolder->getIdentifier(), $processedFile->getName());
1128
1129 // @todo check if we have to update the processed file other then the identifier
1130 $processedFile->setIdentifier($fileIdentifier);
1131 return $processedFile;
1132 }
1133
1134 /**
1135 * Creates a (cryptographic) hash for a file.
1136 *
1137 * @param FileInterface $fileObject
1138 * @param string $hash
1139 * @return string
1140 */
1141 public function hashFile(FileInterface $fileObject, $hash) {
1142 return $this->hashFileByIdentifier($fileObject->getIdentifier(), $hash);
1143 }
1144
1145 /**
1146 * Creates a (cryptographic) hash for a fileIdentifier.
1147
1148 * @param string $fileIdentifier
1149 * @param string $hash
1150 *
1151 * @return string
1152 */
1153 public function hashFileByIdentifier($fileIdentifier, $hash) {
1154 return $this->driver->hash($fileIdentifier, $hash);
1155 }
1156
1157 /**
1158 * Hashes a file identifier, taking the case sensitivity of the file system
1159 * into account. This helps mitigating problems with case-insensitive
1160 * databases.
1161 *
1162 * @param string|FileInterface $file
1163 * @return string
1164 */
1165 public function hashFileIdentifier($file) {
1166 if (is_object($file) && $file instanceof FileInterface) {
1167 /** @var FileInterface $file */
1168 $file = $file->getIdentifier();
1169 }
1170 return $this->driver->hashIdentifier($file);
1171 }
1172
1173 /**
1174 * Returns a publicly accessible URL for a file.
1175 *
1176 * WARNING: Access to the file may be restricted by further means, e.g.
1177 * some web-based authentication. You have to take care of this yourself.
1178 *
1179 * @param ResourceInterface $resourceObject The file or folder object
1180 * @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)
1181 * @return string
1182 */
1183 public function getPublicUrl(ResourceInterface $resourceObject, $relativeToCurrentScript = FALSE) {
1184 $publicUrl = NULL;
1185 if ($this->isOnline()) {
1186 // Pre-process the public URL by an accordant slot
1187 $this->emitPreGeneratePublicUrlSignal($resourceObject, $relativeToCurrentScript, array('publicUrl' => &$publicUrl));
1188 // If slot did not handle the signal, use the default way to determine public URL
1189 if ($publicUrl === NULL) {
1190
1191 if ($this->hasCapability(self::CAPABILITY_PUBLIC)) {
1192 $publicUrl = $this->driver->getPublicUrl($resourceObject->getIdentifier());
1193 }
1194
1195 if ($publicUrl === NULL && $resourceObject instanceof FileInterface) {
1196 $queryParameterArray = array('eID' => 'dumpFile', 't' => '');
1197 if ($resourceObject instanceof File) {
1198 $queryParameterArray['f'] = $resourceObject->getUid();
1199 $queryParameterArray['t'] = 'f';
1200 } elseif ($resourceObject instanceof ProcessedFile) {
1201 $queryParameterArray['p'] = $resourceObject->getUid();
1202 $queryParameterArray['t'] = 'p';
1203 }
1204
1205 $queryParameterArray['token'] = GeneralUtility::hmac(implode('|', $queryParameterArray), 'resourceStorageDumpFile');
1206 $publicUrl = 'index.php?' . str_replace('+', '%20', http_build_query($queryParameterArray));
1207 }
1208
1209 // If requested, make the path relative to the current script in order to make it possible
1210 // to use the relative file
1211 if ($publicUrl !== NULL && $relativeToCurrentScript && !GeneralUtility::isValidUrl($publicUrl)) {
1212 $absolutePathToContainingFolder = PathUtility::dirname(PATH_site . $publicUrl);
1213 $pathPart = PathUtility::getRelativePathTo($absolutePathToContainingFolder);
1214 $filePart = substr(PATH_site . $publicUrl, strlen($absolutePathToContainingFolder) + 1);
1215 $publicUrl = $pathPart . $filePart;
1216 }
1217 }
1218 }
1219 return $publicUrl;
1220 }
1221
1222 /**
1223 * Passes a file to the File Processing Services and returns the resulting ProcessedFile object.
1224 *
1225 * @param FileInterface $fileObject The file object
1226 * @param string $context
1227 * @param array $configuration
1228 *
1229 * @return ProcessedFile
1230 * @throws \InvalidArgumentException
1231 */
1232 public function processFile(FileInterface $fileObject, $context, array $configuration) {
1233 if ($fileObject->getStorage() !== $this) {
1234 throw new \InvalidArgumentException('Cannot process files of foreign storage', 1353401835);
1235 }
1236 $processedFile = $this->getFileProcessingService()->processFile($fileObject, $this, $context, $configuration);
1237
1238 return $processedFile;
1239 }
1240
1241 /**
1242 * Copies a file from the storage for local processing.
1243 *
1244 * @param FileInterface $fileObject
1245 * @param boolean $writable
1246 * @return string Path to local file (either original or copied to some temporary local location)
1247 */
1248 public function getFileForLocalProcessing(FileInterface $fileObject, $writable = TRUE) {
1249 $filePath = $this->driver->getFileForLocalProcessing($fileObject->getIdentifier(), $writable);
1250 return $filePath;
1251 }
1252
1253 /**
1254 * Gets a file by identifier.
1255 *
1256 * @param string $identifier
1257 * @return FileInterface
1258 */
1259 public function getFile($identifier) {
1260 $file = $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
1261 if (!$this->driver->fileExists($identifier)) {
1262 $file->setMissing(TRUE);
1263 }
1264 return $file;
1265 }
1266
1267 /**
1268 * Gets information about a file.
1269 *
1270 * @param FileInterface $fileObject
1271 * @return array
1272 * @internal
1273 */
1274 public function getFileInfo(FileInterface $fileObject) {
1275 return $this->getFileInfoByIdentifier($fileObject->getIdentifier());
1276 }
1277
1278 /**
1279 * Gets information about a file by its identifier.
1280 *
1281 * @param string $identifier
1282 * @param array $propertiesToExtract
1283 * @return array
1284 * @internal
1285 */
1286 public function getFileInfoByIdentifier($identifier, array $propertiesToExtract = array()) {
1287 return $this->driver->getFileInfoByIdentifier($identifier, $propertiesToExtract);
1288 }
1289
1290 /**
1291 * Unsets the file and folder name filters, thus making this storage return unfiltered file lists.
1292 *
1293 * @return void
1294 */
1295 public function unsetFileAndFolderNameFilters() {
1296 $this->fileAndFolderNameFilters = array();
1297 }
1298
1299 /**
1300 * Resets the file and folder name filters to the default values defined in the TYPO3 configuration.
1301 *
1302 * @return void
1303 */
1304 public function resetFileAndFolderNameFiltersToDefault() {
1305 $this->fileAndFolderNameFilters = $GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['defaultFilterCallbacks'];
1306 }
1307
1308 /**
1309 * Returns the file and folder name filters used by this storage.
1310 *
1311 * @return array
1312 */
1313 public function getFileAndFolderNameFilters() {
1314 return $this->fileAndFolderNameFilters;
1315 }
1316
1317 /**
1318 * @param array $filters
1319 * @return $this
1320 */
1321 public function setFileAndFolderNameFilters(array $filters) {
1322 $this->fileAndFolderNameFilters = $filters;
1323 return $this;
1324 }
1325
1326 /**
1327 * @param array $filter
1328 */
1329 public function addFileAndFolderNameFilter($filter) {
1330 $this->fileAndFolderNameFilters[] = $filter;
1331 }
1332
1333 /**
1334 * @param string $fileIdentifier
1335 *
1336 * @return string
1337 */
1338 public function getFolderIdentifierFromFileIdentifier($fileIdentifier) {
1339 return $this->driver->getParentFolderIdentifierOfIdentifier($fileIdentifier);
1340 }
1341
1342 /**
1343 * Returns a list of files in a given path, filtered by some custom filter methods.
1344 *
1345 * @see getUnfilteredFileList(), getFileListWithDefaultFilters()
1346 * @param string $path The path to list
1347 * @param int $start The position to start the listing; if not set or 0, start from the beginning
1348 * @param int $numberOfItems The number of items to list; if not set, return all items
1349 * @param bool $useFilters If FALSE, the list is returned without any filtering; otherwise, the filters defined for this storage are used.
1350 * @param bool $loadIndexRecords If set to TRUE, the index records for all files are loaded from the database. This can greatly improve performance of this method, especially with a lot of files.
1351 * @param bool $recursive
1352 * @return array Information about the files found.
1353 * @deprecated since 6.2, will be removed two versions later
1354 */
1355 public function getFileList($path, $start = 0, $numberOfItems = 0, $useFilters = TRUE, $loadIndexRecords = TRUE, $recursive = FALSE) {
1356 GeneralUtility::logDeprecatedFunction();
1357 return $this->getFilesInFolder($this->getFolder($path), $start, $numberOfItems, $useFilters, $recursive);
1358 }
1359
1360 /**
1361 * @param Folder $folder
1362 * @param int $start
1363 * @param int $maxNumberOfItems
1364 * @param bool $useFilters
1365 * @param bool $recursive
1366 * @return File[]
1367 */
1368 public function getFilesInFolder(Folder $folder, $start = 0, $maxNumberOfItems = 0, $useFilters = TRUE, $recursive = FALSE) {
1369 $this->assureFolderReadPermission($folder);
1370
1371 $rows = $this->getFileIndexRepository()->findByFolder($folder);
1372
1373 $filters = $useFilters == TRUE ? $this->fileAndFolderNameFilters : array();
1374 $fileIdentifiers = array_values($this->driver->getFilesInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters));
1375 $fileIdentifiersCount = count($fileIdentifiers);
1376 $items = array();
1377 if ($maxNumberOfItems === 0) {
1378 $maxNumberOfItems = $fileIdentifiersCount;
1379 }
1380 $end = min($fileIdentifiersCount, $start + $maxNumberOfItems);
1381 for ($i = $start; $i < $end; $i++) {
1382 $identifier = $fileIdentifiers[$i];
1383 if (isset($rows[$identifier])) {
1384 $fileObject = $this->getFileFactory()->getFileObject($rows[$identifier]['uid'], $rows[$identifier]);
1385 } else {
1386 $fileObject = $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
1387 }
1388 if ($fileObject instanceof FileInterface) {
1389 $key = $fileObject->getName();
1390 while (isset($items[$key])) {
1391 $key .= 'z';
1392 }
1393 $items[$key] = $fileObject;
1394 }
1395 }
1396 uksort($items, 'strnatcasecmp');
1397
1398 return $items;
1399 }
1400
1401 /**
1402 * @param string $folderIdentifier
1403 * @param bool $useFilters
1404 * @param bool $recursive
1405 *
1406 * @return array
1407 */
1408 public function getFileIdentifiersInFolder($folderIdentifier, $useFilters = TRUE, $recursive = FALSE) {
1409 $filters = $useFilters == TRUE ? $this->fileAndFolderNameFilters : array();
1410 return $this->driver->getFilesInFolder($folderIdentifier, 0, 0, $recursive, $filters);
1411 }
1412
1413 /**
1414 * @param string $folderIdentifier
1415 * @param bool $useFilters
1416 * @param bool $recursive
1417 *
1418 * @return array
1419 */
1420 public function getFolderIdentifiersInFolder($folderIdentifier, $useFilters = TRUE, $recursive = FALSE) {
1421 $filters = $useFilters == TRUE ? $this->fileAndFolderNameFilters : array();
1422 return $this->driver->getFoldersInFolder($folderIdentifier, 0, 0, $recursive, $filters);
1423 }
1424
1425 /**
1426 * Returns TRUE if the specified file exists
1427 *
1428 * @param string $identifier
1429 * @return bool
1430 */
1431 public function hasFile($identifier) {
1432 // Allow if identifier is in processing folder
1433 if (!$this->isWithinProcessingFolder($identifier)) {
1434 $this->assureFolderReadPermission();
1435 }
1436 return $this->driver->fileExists($identifier);
1437 }
1438
1439 /**
1440 * Get all processing folders that live in this storage
1441 *
1442 * @return Folder[]
1443 */
1444 public function getProcessingFolders() {
1445 if ($this->processingFolders === NULL) {
1446 $this->processingFolders = array();
1447 $this->processingFolders[] = $this->getProcessingFolder();
1448 /** @var $storageRepository StorageRepository */
1449 $storageRepository = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\StorageRepository');
1450 $allStorages = $storageRepository->findAll();
1451 foreach ($allStorages as $storage) {
1452 // To circumvent the permission check of the folder, we use the factory to create it "manually" instead of directly using $storage->getProcessingFolder()
1453 // See #66695 for details
1454 $storageRecord = $storage->getStorageRecord();
1455 list($storageUid, $processingFolderIdentifier) = GeneralUtility::trimExplode(':', $storageRecord['processingfolder']);
1456 if (empty($processingFolderIdentifier) || (int)$storageUid !== $this->getUid()) {
1457 continue;
1458 }
1459 $potentialProcessingFolder = ResourceFactory::getInstance()->getInstance()->createFolderObject($this, $processingFolderIdentifier, $processingFolderIdentifier);
1460 if ($potentialProcessingFolder->getStorage() === $this && $potentialProcessingFolder->getIdentifier() !== $this->getProcessingFolder()->getIdentifier()) {
1461 $this->processingFolders[] = $potentialProcessingFolder;
1462 }
1463 }
1464 }
1465
1466 return $this->processingFolders;
1467 }
1468
1469 /**
1470 * Returns TRUE if folder that is in current storage is set as
1471 * processing folder for one of the existing storages
1472 *
1473 * @param Folder $folder
1474 * @return bool
1475 */
1476 public function isProcessingFolder(Folder $folder) {
1477 $isProcessingFolder = FALSE;
1478 foreach ($this->getProcessingFolders() as $processingFolder) {
1479 if ($folder->getCombinedIdentifier() === $processingFolder->getCombinedIdentifier()) {
1480 $isProcessingFolder = TRUE;
1481 break;
1482 }
1483 }
1484 return $isProcessingFolder;
1485 }
1486
1487 /**
1488 * Checks if the queried file in the given folder exists
1489 *
1490 * @param string $fileName
1491 * @param Folder $folder
1492 * @return bool
1493 */
1494 public function hasFileInFolder($fileName, Folder $folder) {
1495 $this->assureFolderReadPermission($folder);
1496 return $this->driver->fileExistsInFolder($fileName, $folder->getIdentifier());
1497 }
1498
1499 /**
1500 * Get contents of a file object
1501 *
1502 * @param FileInterface $file
1503 *
1504 * @throws Exception\InsufficientFileReadPermissionsException
1505 * @return string
1506 */
1507 public function getFileContents($file) {
1508 $this->assureFileReadPermission($file);
1509 return $this->driver->getFileContents($file->getIdentifier());
1510 }
1511
1512 /**
1513 * Outputs file Contents,
1514 * clears output buffer first and sends headers accordingly.
1515 *
1516 * @param FileInterface $file
1517 * @param bool $asDownload If set Content-Disposition attachment is sent, inline otherwise
1518 * @param string $alternativeFilename the filename for the download (if $asDownload is set)
1519 * @return void
1520 */
1521 public function dumpFileContents(FileInterface $file, $asDownload = FALSE, $alternativeFilename = NULL) {
1522 $downloadName = $alternativeFilename ?: $file->getName();
1523 $contentDisposition = $asDownload ? 'attachment' : 'inline';
1524 header('Content-Disposition: ' . $contentDisposition . '; filename="' . $downloadName . '"');
1525 header('Content-Type: ' . $file->getMimeType());
1526 header('Content-Length: ' . $file->getSize());
1527
1528 // Cache-Control header is needed here to solve an issue with browser IE8 and lower
1529 // See for more information: http://support.microsoft.com/kb/323308
1530 header("Cache-Control: ''");
1531 header('Last-Modified: ' .
1532 gmdate('D, d M Y H:i:s', array_pop($this->driver->getFileInfoByIdentifier($file->getIdentifier(), array('mtime')))) . ' GMT',
1533 TRUE,
1534 200
1535 );
1536 ob_clean();
1537 flush();
1538 while (ob_get_level() > 0) {
1539 ob_end_clean();
1540 }
1541 $this->driver->dumpFileContents($file->getIdentifier());
1542 }
1543
1544 /**
1545 * Set contents of a file object.
1546 *
1547 * @param AbstractFile $file
1548 * @param string $contents
1549 *
1550 * @throws \Exception|\RuntimeException
1551 * @throws Exception\InsufficientFileWritePermissionsException
1552 * @throws Exception\InsufficientUserPermissionsException
1553 * @return int The number of bytes written to the file
1554 */
1555 public function setFileContents(AbstractFile $file, $contents) {
1556 // Check if user is allowed to edit
1557 $this->assureFileWritePermissions($file);
1558 // Call driver method to update the file and update file index entry afterwards
1559 $result = $this->driver->setFileContents($file->getIdentifier(), $contents);
1560 $this->getIndexer()->updateIndexEntry($file);
1561 $this->emitPostFileSetContentsSignal($file, $contents);
1562 return $result;
1563 }
1564
1565 /**
1566 * Creates a new file
1567 *
1568 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_newfile()
1569 *
1570 * @param string $fileName
1571 * @param Folder $targetFolderObject
1572 *
1573 * @throws Exception\IllegalFileExtensionException
1574 * @throws Exception\InsufficientFolderWritePermissionsException
1575 * @return FileInterface The file object
1576 */
1577 public function createFile($fileName, Folder $targetFolderObject) {
1578 $this->assureFileAddPermissions('', $targetFolderObject, $fileName);
1579 $newFileIdentifier = $this->driver->createFile($fileName, $targetFolderObject->getIdentifier());
1580 $this->emitPostFileCreateSignal($newFileIdentifier, $targetFolderObject);
1581 return ResourceFactory::getInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $newFileIdentifier);
1582 }
1583
1584 /**
1585 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::deleteFile()
1586 *
1587 * @param $fileObject FileInterface
1588 * @throws Exception\InsufficientFileAccessPermissionsException
1589 * @throws Exception\FileOperationErrorException
1590 * @return bool TRUE if deletion succeeded
1591 */
1592 public function deleteFile($fileObject) {
1593 $this->assureFileDeletePermissions($fileObject);
1594
1595 $this->emitPreFileDeleteSignal($fileObject);
1596
1597 if ($this->driver->fileExists($fileObject->getIdentifier())) {
1598 $result = $this->driver->deleteFile($fileObject->getIdentifier());
1599 if (!$result) {
1600 throw new Exception\FileOperationErrorException('Deleting the file "' . $fileObject->getIdentifier() . '\' failed.', 1329831691);
1601 }
1602 }
1603 // Mark the file object as deleted
1604 if ($fileObject instanceof File) {
1605 $fileObject->setDeleted();
1606 }
1607
1608 $this->emitPostFileDeleteSignal($fileObject);
1609
1610 return TRUE;
1611 }
1612
1613 /**
1614 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_copy()
1615 * copies a source file (from any location) in to the target
1616 * folder, the latter has to be part of this storage
1617 *
1618 * @param FileInterface $file
1619 * @param Folder $targetFolder
1620 * @param string $targetFileName an optional destination fileName
1621 * @param string $conflictMode "overrideExistingFile", "renameNewFile", "cancel
1622 *
1623 * @throws \Exception|Exception\AbstractFileOperationException
1624 * @throws Exception\ExistingTargetFileNameException
1625 * @return FileInterface
1626 */
1627 public function copyFile(FileInterface $file, Folder $targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
1628 if ($targetFileName === NULL) {
1629 $targetFileName = $file->getName();
1630 }
1631 $sanitizedTargetFileName = $this->driver->sanitizeFileName($targetFileName);
1632 $this->assureFileCopyPermissions($file, $targetFolder, $sanitizedTargetFileName);
1633 $this->emitPreFileCopySignal($file, $targetFolder);
1634 // File exists and we should abort, let's abort
1635 if ($conflictMode === 'cancel' && $targetFolder->hasFile($sanitizedTargetFileName)) {
1636 throw new Exception\ExistingTargetFileNameException('The target file already exists.', 1320291064);
1637 }
1638 // File exists and we should find another name, let's find another one
1639 if ($conflictMode === 'renameNewFile' && $targetFolder->hasFile($sanitizedTargetFileName)) {
1640 $sanitizedTargetFileName = $this->getUniqueName($targetFolder, $sanitizedTargetFileName);
1641 }
1642 $sourceStorage = $file->getStorage();
1643 // Call driver method to create a new file from an existing file object,
1644 // and return the new file object
1645 if ($sourceStorage === $this) {
1646 $newFileObjectIdentifier = $this->driver->copyFileWithinStorage($file->getIdentifier(), $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1647 } else {
1648 $tempPath = $file->getForLocalProcessing();
1649 $newFileObjectIdentifier = $this->driver->addFile($tempPath, $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1650 }
1651 $newFileObject = ResourceFactory::getInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $newFileObjectIdentifier);
1652 $this->emitPostFileCopySignal($file, $targetFolder);
1653 return $newFileObject;
1654 }
1655
1656 /**
1657 * Moves a $file into a $targetFolder
1658 * the target folder has to be part of this storage
1659 *
1660 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_move()
1661 *
1662 * @param FileInterface $file
1663 * @param Folder $targetFolder
1664 * @param string $targetFileName an optional destination fileName
1665 * @param string $conflictMode "overrideExistingFile", "renameNewFile", "cancel
1666 *
1667 * @throws Exception\ExistingTargetFileNameException
1668 * @throws \RuntimeException
1669 * @return FileInterface
1670 */
1671 public function moveFile($file, $targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
1672 if ($targetFileName === NULL) {
1673 $targetFileName = $file->getName();
1674 }
1675 $originalFolder = $file->getParentFolder();
1676 $sanitizedTargetFileName = $this->driver->sanitizeFileName($targetFileName);
1677 $this->assureFileMovePermissions($file, $targetFolder, $sanitizedTargetFileName);
1678 if ($targetFolder->hasFile($sanitizedTargetFileName)) {
1679 // File exists and we should abort, let's abort
1680 if ($conflictMode === 'renameNewFile') {
1681 $sanitizedTargetFileName = $this->getUniqueName($targetFolder, $sanitizedTargetFileName);
1682 } elseif ($conflictMode === 'cancel') {
1683 throw new Exception\ExistingTargetFileNameException('The target file already exists', 1329850997);
1684 }
1685 }
1686 $this->emitPreFileMoveSignal($file, $targetFolder);
1687 $sourceStorage = $file->getStorage();
1688 // Call driver method to move the file and update the index entry
1689 try {
1690 if ($sourceStorage === $this) {
1691 $newIdentifier = $this->driver->moveFileWithinStorage($file->getIdentifier(), $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1692 if (!$file instanceof AbstractFile) {
1693 throw new \RuntimeException('The given file is not of type AbstractFile.', 1384209025);
1694 }
1695 $file->updateProperties(array('identifier' => $newIdentifier));
1696 } else {
1697 $tempPath = $file->getForLocalProcessing();
1698 $newIdentifier = $this->driver->addFile($tempPath, $targetFolder->getIdentifier(), $sanitizedTargetFileName);
1699 $sourceStorage->driver->deleteFile($file->getIdentifier());
1700 if ($file instanceof File) {
1701 $file->updateProperties(array('storage' => $this->getUid(), 'identifier' => $newIdentifier));
1702 }
1703 }
1704 $this->getIndexer()->updateIndexEntry($file);
1705 } catch (\TYPO3\CMS\Core\Exception $e) {
1706 echo $e->getMessage();
1707 }
1708 $this->emitPostFileMoveSignal($file, $targetFolder, $originalFolder);
1709 return $file;
1710 }
1711
1712 /**
1713 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_rename()
1714 *
1715 * @param FileInterface $file
1716 * @param string $targetFileName
1717 *
1718 * @throws Exception\InsufficientFileWritePermissionsException
1719 * @throws Exception\InsufficientFileReadPermissionsException
1720 * @throws Exception\InsufficientUserPermissionsException
1721 * @return FileInterface
1722 */
1723 public function renameFile($file, $targetFileName) {
1724 // TODO add $conflictMode setting
1725
1726 // The name should be different from the current.
1727 if ($file->getName() === $targetFileName) {
1728 return $file;
1729 }
1730 $sanitizedTargetFileName = $this->driver->sanitizeFileName($targetFileName);
1731 $this->assureFileRenamePermissions($file, $sanitizedTargetFileName);
1732 $this->emitPreFileRenameSignal($file, $sanitizedTargetFileName);
1733
1734 // Call driver method to rename the file and update the index entry
1735 try {
1736 $newIdentifier = $this->driver->renameFile($file->getIdentifier(), $sanitizedTargetFileName);
1737 if ($file instanceof File) {
1738 $file->updateProperties(array('identifier' => $newIdentifier));
1739 }
1740 $this->getIndexer()->updateIndexEntry($file);
1741 } catch (\RuntimeException $e) {
1742
1743 }
1744
1745 $this->emitPostFileRenameSignal($file, $sanitizedTargetFileName);
1746
1747 return $file;
1748 }
1749
1750 /**
1751 * Replaces a file with a local file (e.g. a freshly uploaded file)
1752 *
1753 * @param FileInterface $file
1754 * @param string $localFilePath
1755 *
1756 * @return FileInterface
1757 *
1758 * @throws Exception\IllegalFileExtensionException
1759 * @throws \InvalidArgumentException
1760 */
1761 public function replaceFile(FileInterface $file, $localFilePath) {
1762 $this->assureFileWritePermissions($file);
1763 if (!$this->checkFileExtensionPermission($localFilePath)) {
1764 throw new Exception\IllegalFileExtensionException('Source file extension not allowed.', 1378132239);
1765 }
1766 if (!file_exists($localFilePath)) {
1767 throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1325842622);
1768 }
1769 $this->emitPreFileReplaceSignal($file, $localFilePath);
1770 $result = $this->driver->replaceFile($file->getIdentifier(), $localFilePath);
1771 if ($file instanceof File) {
1772 $this->getIndexer()->updateIndexEntry($file);
1773 }
1774 $this->emitPostFileReplaceSignal($file, $localFilePath);
1775 return $result;
1776 }
1777
1778 /**
1779 * Adds an uploaded file into the Storage. Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::file_upload()
1780 *
1781 * @param array $uploadedFileData contains information about the uploaded file given by $_FILES['file1']
1782 * @param Folder $targetFolder the target folder
1783 * @param string $targetFileName the file name to be written
1784 * @param string $conflictMode possible value are 'cancel', 'replace'
1785 * @return FileInterface The file object
1786 */
1787 public function addUploadedFile(array $uploadedFileData, Folder $targetFolder = NULL, $targetFileName = NULL, $conflictMode = 'cancel') {
1788 $localFilePath = $uploadedFileData['tmp_name'];
1789 if ($targetFolder === NULL) {
1790 $targetFolder = $this->getDefaultFolder();
1791 }
1792 if ($targetFileName === NULL) {
1793 $targetFileName = $uploadedFileData['name'];
1794 }
1795 // Handling $conflictMode is delegated to addFile()
1796 $this->assureFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileData['size']);
1797 $resultObject = $this->addFile($localFilePath, $targetFolder, $targetFileName, $conflictMode);
1798 return $resultObject;
1799 }
1800
1801 /********************
1802 * FOLDER ACTIONS
1803 ********************/
1804 /**
1805 * Returns an array with all file objects in a folder and its subfolders, with the file identifiers as keys.
1806 * @todo check if this is a duplicate
1807 * @param Folder $folder
1808 * @return File[]
1809 */
1810 protected function getAllFileObjectsInFolder(Folder $folder) {
1811 $files = array();
1812 $folderQueue = array($folder);
1813 while (!empty($folderQueue)) {
1814 $folder = array_shift($folderQueue);
1815 foreach ($folder->getSubfolders() as $subfolder) {
1816 $folderQueue[] = $subfolder;
1817 }
1818 foreach ($folder->getFiles() as $file) {
1819 /** @var FileInterface $file */
1820 $files[$file->getIdentifier()] = $file;
1821 }
1822 }
1823
1824 return $files;
1825 }
1826
1827 /**
1828 * Moves a folder. If you want to move a folder from this storage to another
1829 * one, call this method on the target storage, otherwise you will get an exception.
1830 *
1831 * @param Folder $folderToMove The folder to move.
1832 * @param Folder $targetParentFolder The target parent folder
1833 * @param string $newFolderName
1834 * @param string $conflictMode How to handle conflicts; one of "overrideExistingFile", "renameNewFolder", "cancel
1835 *
1836 * @throws \Exception|\TYPO3\CMS\Core\Exception
1837 * @throws \InvalidArgumentException
1838 * @throws InvalidTargetFolderException
1839 * @return Folder
1840 */
1841 public function moveFolder(Folder $folderToMove, Folder $targetParentFolder, $newFolderName = NULL, $conflictMode = 'renameNewFolder') {
1842 // TODO add tests
1843 $originalFolder = $folderToMove->getParentFolder();
1844 $this->assureFolderMovePermissions($folderToMove, $targetParentFolder);
1845 $sourceStorage = $folderToMove->getStorage();
1846 $returnObject = NULL;
1847 $sanitizedNewFolderName = $this->driver->sanitizeFileName($newFolderName ?: $folderToMove->getName());
1848 // TODO check if folder already exists in $targetParentFolder, handle this conflict then
1849 $this->emitPreFolderMoveSignal($folderToMove, $targetParentFolder, $sanitizedNewFolderName);
1850 // Get all file objects now so we are able to update them after moving the folder
1851 $fileObjects = $this->getAllFileObjectsInFolder($folderToMove);
1852 if ($sourceStorage === $this) {
1853 if ($this->isWithinFolder($folderToMove, $targetParentFolder)) {
1854 throw new InvalidTargetFolderException(
1855 sprintf(
1856 'Cannot move folder "%s" into target folder "%s", because the target folder is already within the folder to be moved!',
1857 $folderToMove->getName(),
1858 $targetParentFolder->getName()
1859 ),
1860 1422723050
1861 );
1862 }
1863 $fileMappings = $this->driver->moveFolderWithinStorage($folderToMove->getIdentifier(), $targetParentFolder->getIdentifier(), $sanitizedNewFolderName);
1864 } else {
1865 $fileMappings = $this->moveFolderBetweenStorages($folderToMove, $targetParentFolder, $sanitizedNewFolderName);
1866 }
1867 // Update the identifier and storage of all file objects
1868 foreach ($fileObjects as $oldIdentifier => $fileObject) {
1869 $newIdentifier = $fileMappings[$oldIdentifier];
1870 $fileObject->updateProperties(array('storage' => $this->getUid(), 'identifier' => $newIdentifier));
1871 $this->getIndexer()->updateIndexEntry($fileObject);
1872 }
1873 $returnObject = $this->getFolder($fileMappings[$folderToMove->getIdentifier()]);
1874 $this->emitPostFolderMoveSignal($folderToMove, $targetParentFolder, $returnObject->getName(), $originalFolder);
1875 return $returnObject;
1876 }
1877
1878 /**
1879 * Moves the given folder from a different storage to the target folder in this storage.
1880 *
1881 * @param Folder $folderToMove
1882 * @param Folder $targetParentFolder
1883 * @param string $newFolderName
1884 *
1885 * @return bool
1886 * @throws \RuntimeException
1887 */
1888 protected function moveFolderBetweenStorages(Folder $folderToMove, Folder $targetParentFolder, $newFolderName) {
1889 throw new \RuntimeException('Not yet implemented');
1890 }
1891
1892 /**
1893 * Copies a folder.
1894 *
1895 * @param FolderInterface $folderToCopy The folder to copy
1896 * @param FolderInterface $targetParentFolder The target folder
1897 * @param string $newFolderName
1898 * @param string $conflictMode "overrideExistingFolder", "renameNewFolder", "cancel
1899 * @return Folder The new (copied) folder object
1900 * @throws InvalidTargetFolderException
1901 */
1902 public function copyFolder(FolderInterface $folderToCopy, FolderInterface $targetParentFolder, $newFolderName = NULL, $conflictMode = 'renameNewFolder') {
1903 // TODO implement the $conflictMode handling
1904 $this->assureFolderCopyPermissions($folderToCopy, $targetParentFolder);
1905 $returnObject = NULL;
1906 $sanitizedNewFolderName = $this->driver->sanitizeFileName($newFolderName ?: $folderToCopy->getName());
1907 if ($folderToCopy instanceof Folder && $targetParentFolder instanceof Folder) {
1908 $this->emitPreFolderCopySignal($folderToCopy, $targetParentFolder, $sanitizedNewFolderName);
1909 }
1910 $sourceStorage = $folderToCopy->getStorage();
1911 // call driver method to move the file
1912 // that also updates the file object properties
1913 if ($sourceStorage === $this) {
1914 if ($this->isWithinFolder($folderToCopy, $targetParentFolder)) {
1915 throw new InvalidTargetFolderException(
1916 sprintf(
1917 'Cannot copy folder "%s" into target folder "%s", because the target folder is already within the folder to be copied!',
1918 $folderToCopy->getName(),
1919 $targetParentFolder->getName()
1920 ),
1921 1422723059
1922 );
1923 }
1924 $this->driver->copyFolderWithinStorage($folderToCopy->getIdentifier(), $targetParentFolder->getIdentifier(), $sanitizedNewFolderName);
1925 $returnObject = $this->getFolder($targetParentFolder->getSubfolder($sanitizedNewFolderName)->getIdentifier());
1926 } else {
1927 $this->copyFolderBetweenStorages($folderToCopy, $targetParentFolder, $sanitizedNewFolderName);
1928 }
1929 $this->emitPostFolderCopySignal($folderToCopy, $targetParentFolder, $returnObject->getName());
1930 return $returnObject;
1931 }
1932
1933 /**
1934 * Copies a folder between storages.
1935 *
1936 * @param Folder $folderToCopy
1937 * @param Folder $targetParentFolder
1938 * @param string $newFolderName
1939 *
1940 * @return bool
1941 * @throws \RuntimeException
1942 */
1943 protected function copyFolderBetweenStorages(Folder $folderToCopy, Folder $targetParentFolder, $newFolderName) {
1944 throw new \RuntimeException('Not yet implemented.');
1945 }
1946
1947 /**
1948 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::folder_move()
1949 *
1950 * @param Folder $folderObject
1951 * @param string $newName
1952 * @throws \Exception
1953 * @throws \InvalidArgumentException
1954 * @return Folder
1955 */
1956 public function renameFolder($folderObject, $newName) {
1957
1958 // Renaming the folder should check if the parent folder is writable
1959 // We cannot do this however because we cannot extract the parent folder from a folder currently
1960 if (!$this->checkFolderActionPermission('rename', $folderObject)) {
1961 throw new Exception\InsufficientUserPermissionsException('You are not allowed to rename the folder "' . $folderObject->getIdentifier() . '\'', 1357811441);
1962 }
1963
1964 $sanitizedNewName = $this->driver->sanitizeFileName($newName);
1965 $returnObject = NULL;
1966 if ($this->driver->folderExistsInFolder($sanitizedNewName, $folderObject->getIdentifier())) {
1967 throw new \InvalidArgumentException('The folder ' . $sanitizedNewName . ' already exists in folder ' . $folderObject->getIdentifier(), 1325418870);
1968 }
1969
1970 $this->emitPreFolderRenameSignal($folderObject, $sanitizedNewName);
1971
1972 $fileObjects = $this->getAllFileObjectsInFolder($folderObject);
1973 $fileMappings = $this->driver->renameFolder($folderObject->getIdentifier(), $sanitizedNewName);
1974 // Update the identifier of all file objects
1975 foreach ($fileObjects as $oldIdentifier => $fileObject) {
1976 $newIdentifier = $fileMappings[$oldIdentifier];
1977 $fileObject->updateProperties(array('identifier' => $newIdentifier));
1978 $this->getIndexer()->updateIndexEntry($fileObject);
1979 }
1980 $returnObject = $this->getFolder($fileMappings[$folderObject->getIdentifier()]);
1981
1982 $this->emitPostFolderRenameSignal($folderObject, $returnObject->getName());
1983
1984 return $returnObject;
1985 }
1986
1987 /**
1988 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::folder_delete()
1989 *
1990 * @param Folder $folderObject
1991 * @param bool $deleteRecursively
1992 * @throws \RuntimeException
1993 * @throws \TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException
1994 * @throws \TYPO3\CMS\Core\Resource\Exception\InsufficientUserPermissionsException
1995 * @throws \TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException
1996 * @throws \TYPO3\CMS\Core\Resource\Exception\InvalidPathException
1997 * @return bool
1998 */
1999 public function deleteFolder($folderObject, $deleteRecursively = FALSE) {
2000 $isEmpty = $this->driver->isFolderEmpty($folderObject->getIdentifier());
2001 $this->assureFolderDeletePermission($folderObject, ($deleteRecursively && !$isEmpty));
2002 if (!$isEmpty && !$deleteRecursively) {
2003 throw new \RuntimeException('Could not delete folder "' . $folderObject->getIdentifier() . '" because it is not empty.', 1325952534);
2004 }
2005
2006 $this->emitPreFolderDeleteSignal($folderObject);
2007
2008 foreach ($this->getFilesInFolder($folderObject, 0, 0, FALSE, $deleteRecursively) as $file) {
2009 $this->deleteFile($file);
2010 }
2011
2012 $result = $this->driver->deleteFolder($folderObject->getIdentifier(), $deleteRecursively);
2013
2014 $this->emitPostFolderDeleteSignal($folderObject);
2015
2016 return $result;
2017 }
2018
2019 /**
2020 * Returns a list of folders in a given path.
2021 *
2022 * @param string $path The path to list
2023 * @param int $start The position to start the listing; if not set or 0, start from the beginning
2024 * @param int $numberOfItems The number of items to list; if not set, return all items
2025 * @param bool $useFilters If FALSE, the list is returned without any filtering; otherwise, the filters defined for this storage are used.
2026 * @return array Information about the folders found.
2027 * @deprecated since TYPO3 6.2, will be removed to versions later
2028 */
2029 public function getFolderList($path, $start = 0, $numberOfItems = 0, $useFilters = TRUE) {
2030 GeneralUtility::logDeprecatedFunction();
2031 // Permissions are checked in $this->fetchFolderListFromDriver()
2032 $filters = $useFilters === TRUE ? $this->fileAndFolderNameFilters : array();
2033 return $this->fetchFolderListFromDriver($path, $start, $numberOfItems, $filters);
2034 }
2035
2036 /**
2037 * @param Folder $folder
2038 * @param int $start
2039 * @param int $maxNumberOfItems
2040 * @param bool $useFilters
2041 * @param bool $recursive
2042 *
2043 * @return Folder[]
2044 */
2045 public function getFoldersInFolder(Folder $folder, $start = 0, $maxNumberOfItems = 0, $useFilters = TRUE, $recursive = FALSE) {
2046 $filters = $useFilters == TRUE ? $this->fileAndFolderNameFilters : array();
2047 $folderIdentifiers = $this->driver->getFoldersInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters);
2048
2049 // Exclude processing folders
2050 foreach ($this->getProcessingFolders() as $processingFolder) {
2051 $processingIdentifier = $processingFolder->getIdentifier();
2052 if (isset($folderIdentifiers[$processingIdentifier])) {
2053 unset($folderIdentifiers[$processingIdentifier]);
2054 }
2055 }
2056 $folders = array();
2057 foreach ($folderIdentifiers as $folderIdentifier) {
2058 $folders[$folderIdentifier] = $this->getFolder($folderIdentifier, TRUE);
2059 }
2060 return $folders;
2061 }
2062
2063 /**
2064 * @param $path
2065 * @param int $start
2066 * @param int $numberOfItems
2067 * @param array $folderFilterCallbacks
2068 * @param bool $recursive
2069 * @return array
2070 * @deprecated since 6.2, will be removed 2 versions later
2071 */
2072 public function fetchFolderListFromDriver($path, $start = 0, $numberOfItems = 0, array $folderFilterCallbacks = array(), $recursive = FALSE) {
2073 GeneralUtility::logDeprecatedFunction();
2074 // This also checks for access to that path and throws exceptions accordingly
2075 $parentFolder = $this->getFolder($path);
2076 if ($parentFolder === NULL) {
2077 return array();
2078 }
2079 $folders = $this->getFoldersInFolder($parentFolder, $start, $numberOfItems, count($folderFilterCallbacks) > 0, $recursive);
2080 $folderInfo = array();
2081 foreach ($folders as $folder) {
2082 $folderInfo[$folder->getIdentifier()] = array(
2083 'name' => $folder->getName(),
2084 'identifier' => $folder->getIdentifier()
2085 );
2086 }
2087 return $folderInfo;
2088 }
2089
2090 /**
2091 * Returns TRUE if the specified folder exists.
2092 *
2093 * @param string $identifier
2094 * @return bool
2095 */
2096 public function hasFolder($identifier) {
2097 $this->assureFolderReadPermission();
2098 return $this->driver->folderExists($identifier);
2099 }
2100
2101 /**
2102 * Checks if the given file exists in the given folder
2103 *
2104 * @param string $folderName
2105 * @param Folder $folder
2106 * @return bool
2107 */
2108 public function hasFolderInFolder($folderName, Folder $folder) {
2109 $this->assureFolderReadPermission($folder);
2110 return $this->driver->folderExistsInFolder($folderName, $folder->getIdentifier());
2111 }
2112
2113 /**
2114 * Creates a new folder.
2115 *
2116 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_newfolder()
2117 *
2118 * @param string $folderName The new folder name
2119 * @param Folder $parentFolder (optional) the parent folder to create the new folder inside of. If not given, the root folder is used
2120 *
2121 * @throws Exception\InsufficientFolderWritePermissionsException
2122 * @throws \InvalidArgumentException
2123 * @return Folder The new folder object
2124 */
2125 public function createFolder($folderName, Folder $parentFolder = NULL) {
2126 if ($parentFolder === NULL) {
2127 $parentFolder = $this->getRootLevelFolder();
2128 } elseif (!$this->driver->folderExists($parentFolder->getIdentifier())) {
2129 throw new \InvalidArgumentException('Parent folder "' . $parentFolder->getIdentifier() . '" does not exist.', 1325689164);
2130 }
2131 if (!$this->checkFolderActionPermission('add', $parentFolder)) {
2132 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to create directories in the folder "' . $parentFolder->getIdentifier() . '"', 1323059807);
2133 }
2134
2135 $this->emitPreFolderAddSignal($parentFolder, $folderName);
2136
2137 $newFolder = $this->getDriver()->createFolder($folderName, $parentFolder->getIdentifier(), TRUE);
2138 $newFolder = $this->getFolder($newFolder);
2139
2140 $this->emitPostFolderAddSignal($newFolder);
2141
2142 return $newFolder;
2143 }
2144
2145 /**
2146 * Returns the default folder where new files are stored if no other folder is given.
2147 *
2148 * @return Folder
2149 */
2150 public function getDefaultFolder() {
2151 return $this->getFolder($this->driver->getDefaultFolder());
2152 }
2153
2154 /**
2155 * @param string $identifier
2156 * @param bool $returnInaccessibleFolderObject
2157 *
2158 * @return Folder
2159 * @throws \Exception
2160 * @throws Exception\InsufficientFolderAccessPermissionsException
2161 */
2162 public function getFolder($identifier, $returnInaccessibleFolderObject = FALSE) {
2163
2164 $data = $this->driver->getFolderInfoByIdentifier($identifier);
2165 $folder = ResourceFactory::getInstance()->createFolderObject($this, $data['identifier'], $data['name']);
2166
2167 try {
2168 $this->assureFolderReadPermission($folder);
2169 } catch (Exception\InsufficientFolderAccessPermissionsException $e) {
2170 $folder = NULL;
2171 if ($returnInaccessibleFolderObject) {
2172 // if parent folder is readable return inaccessible folder object
2173 $parentPermissions = $this->driver->getPermissions($this->driver->getParentFolderIdentifierOfIdentifier($identifier));
2174 if ($parentPermissions['r']) {
2175 $folder = GeneralUtility::makeInstance(
2176 'TYPO3\\CMS\\Core\\Resource\\InaccessibleFolder', $this, $data['identifier'], $data['name']
2177 );
2178 }
2179 }
2180
2181 if ($folder === NULL) {
2182 throw $e;
2183 }
2184 }
2185 return $folder;
2186 }
2187
2188 /**
2189 * Returns TRUE if the specified file is in a folder that is set a processing for a storage
2190 *
2191 * @param string $identifier
2192 * @return bool
2193 */
2194 public function isWithinProcessingFolder($identifier) {
2195 $inProcessingFolder = FALSE;
2196 foreach ($this->getProcessingFolders() as $processingFolder) {
2197 if ($this->driver->isWithin($processingFolder->getIdentifier(), $identifier)) {
2198 $inProcessingFolder = TRUE;
2199 break;
2200 }
2201 }
2202 return $inProcessingFolder;
2203 }
2204
2205 /**
2206 * Checks if a resource (file or folder) is within the given folder
2207 *
2208 * @param Folder $folder
2209 * @param ResourceInterface $resource
2210 * @return bool
2211 * @throws \InvalidArgumentException
2212 */
2213 public function isWithinFolder(Folder $folder, ResourceInterface $resource) {
2214 if ($folder->getStorage() !== $this) {
2215 throw new \InvalidArgumentException('Given folder "' . $folder->getIdentifier() . '" is not part of this storage!', 1422709241);
2216 }
2217 if ($folder->getStorage() !== $resource->getStorage()) {
2218 return FALSE;
2219 }
2220 return $this->driver->isWithin($folder->getIdentifier(), $resource->getIdentifier());
2221 }
2222
2223 /**
2224 * Returns the folders on the root level of the storage
2225 * or the first mount point of this storage for this user
2226 * if $respectFileMounts is set.
2227 *
2228 * @param bool $respectFileMounts
2229 * @return Folder
2230 */
2231 public function getRootLevelFolder($respectFileMounts = TRUE) {
2232 if ($respectFileMounts && count($this->fileMounts)) {
2233 $mount = reset($this->fileMounts);
2234 return $mount['folder'];
2235 } else {
2236 return ResourceFactory::getInstance()->createFolderObject($this, $this->driver->getRootLevelFolder(), '');
2237 }
2238 }
2239
2240 /**
2241 * Emits file pre-add signal.
2242 *
2243 * @param string $targetFileName
2244 * @param Folder $targetFolder
2245 * @param string $sourceFilePath
2246 * @return string Modified target file name
2247 */
2248 protected function emitPreFileAddSignal($targetFileName, Folder $targetFolder, $sourceFilePath) {
2249 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreFileAdd, array(&$targetFileName, $targetFolder, $sourceFilePath, $this, $this->driver));
2250 return $targetFileName;
2251 }
2252
2253 /**
2254 * Emits the file post-add signal.
2255 *
2256 * @param FileInterface $file
2257 * @param Folder $targetFolder
2258 * @return void
2259 */
2260 protected function emitPostFileAddSignal(FileInterface $file, Folder $targetFolder) {
2261 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFileAdd, array($file, $targetFolder));
2262 }
2263
2264 /**
2265 * Emits file pre-copy signal.
2266 *
2267 * @param FileInterface $file
2268 * @param Folder $targetFolder
2269 * @return void
2270 */
2271 protected function emitPreFileCopySignal(FileInterface $file, Folder $targetFolder) {
2272 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreFileCopy, array($file, $targetFolder));
2273 }
2274
2275 /**
2276 * Emits the file post-copy signal.
2277 *
2278 * @param FileInterface $file
2279 * @param Folder $targetFolder
2280 * @return void
2281 */
2282 protected function emitPostFileCopySignal(FileInterface $file, Folder $targetFolder) {
2283 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFileCopy, array($file, $targetFolder));
2284 }
2285
2286 /**
2287 * Emits the file pre-move signal.
2288 *
2289 * @param FileInterface $file
2290 * @param Folder $targetFolder
2291 * @return void
2292 */
2293 protected function emitPreFileMoveSignal(FileInterface $file, Folder $targetFolder) {
2294 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreFileMove, array($file, $targetFolder));
2295 }
2296
2297 /**
2298 * Emits the file post-move signal.
2299 *
2300 * @param FileInterface $file
2301 * @param Folder $targetFolder
2302 * @param Folder $originalFolder
2303 * @return void
2304 */
2305 protected function emitPostFileMoveSignal(FileInterface $file, Folder $targetFolder, Folder $originalFolder) {
2306 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFileMove, array($file, $targetFolder, $originalFolder));
2307 }
2308
2309 /**
2310 * Emits the file pre-rename signal
2311 *
2312 * @param FileInterface $file
2313 * @param $targetFolder
2314 * @return void
2315 */
2316 protected function emitPreFileRenameSignal(FileInterface $file, $targetFolder) {
2317 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreFileRename, array($file, $targetFolder));
2318 }
2319
2320 /**
2321 * Emits the file post-rename signal.
2322 *
2323 * @param FileInterface $file
2324 * @param $targetFolder
2325 * @return void
2326 */
2327 protected function emitPostFileRenameSignal(FileInterface $file, $targetFolder) {
2328 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFileRename, array($file, $targetFolder));
2329 }
2330
2331 /**
2332 * Emits the file pre-replace signal.
2333 *
2334 * @param FileInterface $file
2335 * @param $localFilePath
2336 * @return void
2337 */
2338 protected function emitPreFileReplaceSignal(FileInterface $file, $localFilePath) {
2339 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreFileReplace, array($file, $localFilePath));
2340 }
2341
2342 /**
2343 * Emits the file post-replace signal
2344 *
2345 * @param FileInterface $file
2346 * @param string $localFilePath
2347 * @return void
2348 */
2349 protected function emitPostFileReplaceSignal(FileInterface $file, $localFilePath) {
2350 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFileReplace, array($file, $localFilePath));
2351 }
2352
2353 /**
2354 * Emits the file post-create signal
2355 *
2356 * @param string $newFileIdentifier
2357 * @param Folder $targetFolder
2358 */
2359 protected function emitPostFileCreateSignal($newFileIdentifier, Folder $targetFolder) {
2360 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFileCreate, array($newFileIdentifier, $targetFolder));
2361 }
2362
2363 /**
2364 * Emits the file pre-deletion signal.
2365 *
2366 * @param FileInterface $file
2367 * @return void
2368 */
2369 protected function emitPreFileDeleteSignal(FileInterface $file) {
2370 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreFileDelete, array($file));
2371 }
2372
2373 /**
2374 * Emits the file post-deletion signal
2375 *
2376 * @param FileInterface $file
2377 * @return void
2378 */
2379 protected function emitPostFileDeleteSignal(FileInterface $file) {
2380 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFileDelete, array($file));
2381 }
2382
2383 /**
2384 * Emits the file post-set-contents signal
2385 *
2386 * @param FileInterface $file
2387 * @param mixed $content
2388 * @return void
2389 */
2390 protected function emitPostFileSetContentsSignal(FileInterface $file, $content) {
2391 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFileSetContents, array($file, $content));
2392 }
2393
2394 /**
2395 * Emits the folder pre-add signal.
2396 *
2397 * @param Folder $targetFolder
2398 * @param string $name
2399 * @return void
2400 */
2401 protected function emitPreFolderAddSignal(Folder $targetFolder, $name) {
2402 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreFolderAdd, array($targetFolder, $name));
2403 }
2404
2405 /**
2406 * Emits the folder post-add signal.
2407 *
2408 * @param Folder $folder
2409 * @return void
2410 */
2411 protected function emitPostFolderAddSignal(Folder $folder) {
2412 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFolderAdd, array($folder));
2413 }
2414
2415 /**
2416 * Emits the folder pre-copy signal.
2417 *
2418 * @param Folder $folder
2419 * @param Folder $targetFolder
2420 * @param $newName
2421 * @return void
2422 */
2423 protected function emitPreFolderCopySignal(Folder $folder, Folder $targetFolder, $newName) {
2424 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreFolderCopy, array($folder, $targetFolder, $newName));
2425 }
2426
2427 /**
2428 * Emits the folder post-copy signal.
2429 *
2430 * @param Folder $folder
2431 * @param Folder $targetFolder
2432 * @param $newName
2433 * @return void
2434 */
2435 protected function emitPostFolderCopySignal(Folder $folder, Folder $targetFolder, $newName) {
2436 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFolderCopy, array($folder, $targetFolder, $newName));
2437 }
2438
2439 /**
2440 * Emits the folder pre-move signal.
2441 *
2442 * @param Folder $folder
2443 * @param Folder $targetFolder
2444 * @param $newName
2445 * @return void
2446 */
2447 protected function emitPreFolderMoveSignal(Folder $folder, Folder $targetFolder, $newName) {
2448 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreFolderMove, array($folder, $targetFolder, $newName));
2449 }
2450
2451 /**
2452 * Emits the folder post-move signal.
2453 *
2454 * @param Folder $folder
2455 * @param Folder $targetFolder
2456 * @param $newName
2457 * @param Folder $originalFolder
2458 * @return void
2459 */
2460 protected function emitPostFolderMoveSignal(Folder $folder, Folder $targetFolder, $newName, Folder $originalFolder) {
2461 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFolderMove, array($folder, $targetFolder, $newName, $originalFolder));
2462 }
2463
2464 /**
2465 * Emits the folder pre-rename signal.
2466 *
2467 * @param Folder $folder
2468 * @param $newName
2469 * @return void
2470 */
2471 protected function emitPreFolderRenameSignal(Folder $folder, $newName) {
2472 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreFolderRename, array($folder, $newName));
2473 }
2474
2475 /**
2476 * Emits the folder post-rename signal.
2477 *
2478 * @param Folder $folder
2479 * @param $newName
2480 * @return void
2481 */
2482 protected function emitPostFolderRenameSignal(Folder $folder, $newName) {
2483 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFolderRename, array($folder, $newName));
2484 }
2485
2486 /**
2487 * Emits the folder pre-deletion signal.
2488 *
2489 * @param Folder $folder
2490 * @return void
2491 */
2492 protected function emitPreFolderDeleteSignal(Folder $folder) {
2493 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreFolderDelete, array($folder));
2494 }
2495
2496 /**
2497 * Emits folder post-deletion signal..
2498 *
2499 * @param Folder $folder
2500 * @return void
2501 */
2502 protected function emitPostFolderDeleteSignal(Folder $folder) {
2503 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFolderDelete, array($folder));
2504 }
2505
2506 /**
2507 * Emits file pre-processing signal when generating a public url for a file or folder.
2508 *
2509 * @param ResourceInterface $resourceObject
2510 * @param bool $relativeToCurrentScript
2511 * @param array $urlData
2512 */
2513 protected function emitPreGeneratePublicUrlSignal(ResourceInterface $resourceObject, $relativeToCurrentScript, array $urlData) {
2514 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreGeneratePublicUrl, array($this, $this->driver, $resourceObject, $relativeToCurrentScript, $urlData));
2515 }
2516
2517 /**
2518 * Returns the destination path/fileName of a unique fileName/foldername in that path.
2519 * If $theFile exists in $theDest (directory) the file have numbers appended up to $this->maxNumber. Hereafter a unique string will be appended.
2520 * This function is used by fx. TCEmain when files are attached to records and needs to be uniquely named in the uploads/* folders
2521 *
2522 * @param Folder $folder
2523 * @param string $theFile The input fileName to check
2524 * @param bool $dontCheckForUnique If set the fileName is returned with the path prepended without checking whether it already existed!
2525 *
2526 * @throws \RuntimeException
2527 * @return string A unique fileName inside $folder, based on $theFile.
2528 * @see \TYPO3\CMS\Core\Utility\File\BasicFileUtility::getUniqueName()
2529 */
2530 protected function getUniqueName(Folder $folder, $theFile, $dontCheckForUnique = FALSE) {
2531 static $maxNumber = 99, $uniqueNamePrefix = '';
2532 // Fetches info about path, name, extension of $theFile
2533 $origFileInfo = PathUtility::pathinfo($theFile);
2534 // Adds prefix
2535 if ($uniqueNamePrefix) {
2536 $origFileInfo['basename'] = $uniqueNamePrefix . $origFileInfo['basename'];
2537 $origFileInfo['filename'] = $uniqueNamePrefix . $origFileInfo['filename'];
2538 }
2539 // Check if the file exists and if not - return the fileName...
2540 // The destinations file
2541 $theDestFile = $origFileInfo['basename'];
2542 // If the file does NOT exist we return this fileName
2543 if (!$this->driver->fileExistsInFolder($theDestFile, $folder->getIdentifier()) || $dontCheckForUnique) {
2544 return $theDestFile;
2545 }
2546 // Well the fileName in its pure form existed. Now we try to append
2547 // numbers / unique-strings and see if we can find an available fileName
2548 // This removes _xx if appended to the file
2549 $theTempFileBody = preg_replace('/_[0-9][0-9]$/', '', $origFileInfo['filename']);
2550 $theOrigExt = $origFileInfo['extension'] ? '.' . $origFileInfo['extension'] : '';
2551 for ($a = 1; $a <= $maxNumber + 1; $a++) {
2552 // First we try to append numbers
2553 if ($a <= $maxNumber) {
2554 $insert = '_' . sprintf('%02d', $a);
2555 } else {
2556 $insert = '_' . substr(md5(uniqid('', TRUE)), 0, 6);
2557 }
2558 $theTestFile = $theTempFileBody . $insert . $theOrigExt;
2559 // The destinations file
2560 $theDestFile = $theTestFile;
2561 // If the file does NOT exist we return this fileName
2562 if (!$this->driver->fileExistsInFolder($theDestFile, $folder->getIdentifier())) {
2563 return $theDestFile;
2564 }
2565 }
2566 throw new \RuntimeException('Last possible name "' . $theDestFile . '" is already taken.', 1325194291);
2567 }
2568
2569 /**
2570 * Get the SignalSlot dispatcher.
2571 *
2572 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
2573 */
2574 protected function getSignalSlotDispatcher() {
2575 if (!isset($this->signalSlotDispatcher)) {
2576 $this->signalSlotDispatcher = $this->getObjectManager()->get('TYPO3\\CMS\\Extbase\\SignalSlot\\Dispatcher');
2577 }
2578 return $this->signalSlotDispatcher;
2579 }
2580
2581 /**
2582 * Gets the ObjectManager.
2583 *
2584 * @return \TYPO3\CMS\Extbase\Object\ObjectManager
2585 */
2586 protected function getObjectManager() {
2587 return GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
2588 }
2589
2590 /**
2591 * @return ResourceFactory
2592 */
2593 protected function getFileFactory() {
2594 return GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\ResourceFactory');
2595 }
2596
2597 /**
2598 * @return \TYPO3\CMS\Core\Resource\Index\FileIndexRepository
2599 */
2600 protected function getFileIndexRepository() {
2601 return FileIndexRepository::getInstance();
2602 }
2603
2604 /**
2605 * @return Service\FileProcessingService
2606 */
2607 protected function getFileProcessingService() {
2608 if (!$this->fileProcessingService) {
2609 $this->fileProcessingService = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\Service\\FileProcessingService', $this, $this->driver);
2610 }
2611 return $this->fileProcessingService;
2612 }
2613
2614 /**
2615 * Gets the role of a folder.
2616 *
2617 * @param FolderInterface $folder Folder object to get the role from
2618 * @return string The role the folder has
2619 */
2620 public function getRole(FolderInterface $folder) {
2621 $folderRole = FolderInterface::ROLE_DEFAULT;
2622 $identifier = $folder->getIdentifier();
2623 if (method_exists($this->driver, 'getRole')) {
2624 $folderRole = $this->driver->getRole($folder->getIdentifier());
2625 }
2626 if (isset($this->fileMounts[$identifier])) {
2627 $folderRole = FolderInterface::ROLE_MOUNT;
2628
2629 if (!empty($this->fileMounts[$identifier]['read_only'])) {
2630 $folderRole = FolderInterface::ROLE_READONLY_MOUNT;
2631 }
2632 if ($this->fileMounts[$identifier]['user_mount']) {
2633 $folderRole = FolderInterface::ROLE_USER_MOUNT;
2634 }
2635 }
2636 if ($folder instanceof Folder && $this->isProcessingFolder($folder)) {
2637 $folderRole = FolderInterface::ROLE_PROCESSING;
2638 }
2639
2640 return $folderRole;
2641 }
2642
2643 /**
2644 * Getter function to return the folder where the files can
2645 * be processed. Does not check for access rights here.
2646 *
2647 * @return Folder
2648 */
2649 public function getProcessingFolder() {
2650 if (!isset($this->processingFolder)) {
2651 $processingFolder = self::DEFAULT_ProcessingFolder;
2652 if (!empty($this->storageRecord['processingfolder'])) {
2653 $processingFolder = $this->storageRecord['processingfolder'];
2654 }
2655 try {
2656 if (strpos($processingFolder, ':') !== FALSE) {
2657 $this->processingFolder = ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier($processingFolder);
2658 } else {
2659 if ($this->driver->folderExists($processingFolder) === FALSE) {
2660 $this->processingFolder = $this->createFolder($processingFolder);
2661 } else {
2662 $data = $this->driver->getFolderInfoByIdentifier($processingFolder);
2663 $this->processingFolder = ResourceFactory::getInstance()->createFolderObject($this, $data['identifier'], $data['name']);
2664 }
2665 }
2666 } catch(Exception\InsufficientFolderWritePermissionsException $e) {
2667 $this->processingFolder = GeneralUtility::makeInstance(
2668 'TYPO3\\CMS\\Core\\Resource\\InaccessibleFolder', $this, $processingFolder, $processingFolder
2669 );
2670 } catch(Exception\ResourcePermissionsUnavailableException $e) {
2671 $this->processingFolder = GeneralUtility::makeInstance(
2672 'TYPO3\\CMS\\Core\\Resource\\InaccessibleFolder', $this, $processingFolder, $processingFolder
2673 );
2674 }
2675 }
2676 return $this->processingFolder;
2677 }
2678
2679 /**
2680 * Gets the driver Type configured for this storage.
2681 *
2682 * @return string
2683 */
2684 public function getDriverType() {
2685 return $this->storageRecord['driver'];
2686 }
2687
2688 /**
2689 * Gets the Indexer.
2690 *
2691 * @return \TYPO3\CMS\Core\Resource\Index\Indexer
2692 */
2693 protected function getIndexer() {
2694 return GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\Index\\Indexer', $this);
2695 }
2696
2697 /**
2698 * @param bool $isDefault
2699 * @return void
2700 */
2701 public function setDefault($isDefault) {
2702 $this->isDefault = (bool)$isDefault;
2703 }
2704
2705 /**
2706 * @return bool
2707 */
2708 public function isDefault() {
2709 return $this->isDefault;
2710 }
2711 }