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