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