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