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