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