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