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