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