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