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