f8c984a2c540ccd43658ff8dc2539c30728b0878
[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 int $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)) {
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 // Check 1: Does the user have permission to perform the action? e.g. "readFile"
576 if ($this->checkUserActionPermission($action, 'File') === FALSE) {
577 return FALSE;
578 }
579 // Check 2: Does the user has the right to perform the action?
580 // (= is he within the file mount borders)
581 if (is_array($this->fileMounts) && count($this->fileMounts) && !$this->isWithinFileMountBoundaries($file)) {
582 return FALSE;
583 }
584 $isReadCheck = FALSE;
585 if ($action === 'read') {
586 $isReadCheck = TRUE;
587 }
588 $isWriteCheck = FALSE;
589 if (in_array($action, array('add', 'edit', 'write', 'upload', 'move', 'rename', 'unzip', 'remove'))) {
590 $isWriteCheck = TRUE;
591 }
592 // Check 3: Check the capabilities of the storage (and the driver)
593 if ($isReadCheck && !$this->isBrowsable()) {
594 return FALSE;
595 }
596 if ($isWriteCheck && !$this->isWritable()) {
597 return FALSE;
598 }
599 // Check 4: "File permissions" of the driver
600 $filePermissions = $this->driver->getFilePermissions($file);
601 if ($isReadCheck && !$filePermissions['r']) {
602 return FALSE;
603 }
604 if ($isWriteCheck && !$filePermissions['w']) {
605 return FALSE;
606 }
607 return TRUE;
608 }
609
610 /**
611 * Check if a folder operation (= action) is allowed on a Folder
612 *
613 * This method, by design, does not throw exceptions or do logging.
614 * See the checkFileActionPermission() method above for the reasons.
615 *
616 * @param string $action
617 * @param Folder $folder
618 * @return boolean
619 */
620 public function checkFolderActionPermission($action, Folder $folder = NULL) {
621 // Check 1: Does the user have permission to perform the action? e.g. "writeFolder"
622 if ($this->checkUserActionPermission($action, 'Folder') === FALSE) {
623 return FALSE;
624 }
625 // Check 2: Does the user has the right to perform the action?
626 // (= is he within the file mount borders)
627 if (is_array($this->fileMounts) && count($this->fileMounts) && !$this->isWithinFileMountBoundaries($folder)) {
628 return FALSE;
629 }
630 $isReadCheck = FALSE;
631 if ($action === 'read') {
632 $isReadCheck = TRUE;
633 }
634 $isWriteCheck = FALSE;
635 if (in_array($action, array('add', 'move', 'write', 'remove', 'rename'))) {
636 $isWriteCheck = TRUE;
637 }
638 // Check 3: Check the capabilities of the storage (and the driver)
639 if ($isReadCheck && !$this->isBrowsable()) {
640 return FALSE;
641 }
642 if ($isWriteCheck && !$this->isWritable()) {
643 return FALSE;
644 }
645 // Check 4: "Folder permissions" of the driver
646 $folderPermissions = $this->driver->getFolderPermissions($folder);
647 if ($isReadCheck && !$folderPermissions['r']) {
648 return FALSE;
649 }
650 if ($isWriteCheck && !$folderPermissions['w']) {
651 return FALSE;
652 }
653 return TRUE;
654 }
655
656 /**
657 * If the fileName is given, check it against the
658 * TYPO3_CONF_VARS[BE][fileDenyPattern] + and if the file extension is allowed
659 *
660 * @param string $fileName Full filename
661 * @return boolean TRUE if extension/filename is allowed
662 */
663 protected function checkFileExtensionPermission($fileName) {
664 $isAllowed = GeneralUtility::verifyFilenameAgainstDenyPattern($fileName);
665 if ($isAllowed) {
666 $fileInfo = GeneralUtility::split_fileref($fileName);
667 // Set up the permissions for the file extension
668 $fileExtensionPermissions = $GLOBALS['TYPO3_CONF_VARS']['BE']['fileExtensions']['webspace'];
669 $fileExtensionPermissions['allow'] = GeneralUtility::uniqueList(strtolower($fileExtensionPermissions['allow']));
670 $fileExtensionPermissions['deny'] = GeneralUtility::uniqueList(strtolower($fileExtensionPermissions['deny']));
671 $fileExtension = strtolower($fileInfo['fileext']);
672 if ($fileExtension !== '') {
673 // If the extension is found amongst the allowed types, we return TRUE immediately
674 if ($fileExtensionPermissions['allow'] === '*' || GeneralUtility::inList($fileExtensionPermissions['allow'], $fileExtension)) {
675 return TRUE;
676 }
677 // If the extension is found amongst the denied types, we return FALSE immediately
678 if ($fileExtensionPermissions['deny'] === '*' || GeneralUtility::inList($fileExtensionPermissions['deny'], $fileExtension)) {
679 return FALSE;
680 }
681 // If no match we return TRUE
682 return TRUE;
683 } else {
684 if ($fileExtensionPermissions['allow'] === '*') {
685 return TRUE;
686 }
687 if ($fileExtensionPermissions['deny'] === '*') {
688 return FALSE;
689 }
690 return TRUE;
691 }
692 }
693 return FALSE;
694 }
695
696 /********************
697 * FILE ACTIONS
698 ********************/
699 /**
700 * Moves a file from the local filesystem to this storage.
701 *
702 * @param string $localFilePath The file on the server's hard disk to add.
703 * @param Folder $targetFolder The target path, without the fileName
704 * @param string $fileName The fileName. If not set, the local file name is used.
705 * @param string $conflictMode possible value are 'cancel', 'replace', 'changeName'
706 *
707 * @throws \InvalidArgumentException
708 * @throws Exception\ExistingTargetFileNameException
709 * @return FileInterface
710 */
711 public function addFile($localFilePath, Folder $targetFolder, $fileName = '', $conflictMode = 'changeName') {
712 // TODO check permissions (write on target, upload, ...)
713 if (!file_exists($localFilePath)) {
714 throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1319552745);
715 }
716 $targetFolder = $targetFolder ? $targetFolder : $this->getDefaultFolder();
717 $fileName = $fileName ? $fileName : PathUtility::basename($localFilePath);
718 if ($conflictMode === 'cancel' && $this->driver->fileExistsInFolder($fileName, $targetFolder)) {
719 throw new Exception\ExistingTargetFileNameException('File "' . $fileName . '" already exists in folder ' . $targetFolder->getIdentifier(), 1322121068);
720 } elseif ($conflictMode === 'changeName') {
721 $fileName = $this->getUniqueName($targetFolder, $fileName);
722 }
723 // We do not care whether the file exists if $conflictMode is "replace",
724 // so just use the name as is in that case
725 return $this->driver->addFile($localFilePath, $targetFolder, $fileName);
726 }
727
728 /**
729 * Creates a (cryptographic) hash for a file.
730 *
731 * @param FileInterface $fileObject
732 * @param $hash
733 * @return string
734 */
735 public function hashFile(FileInterface $fileObject, $hash) {
736 return $this->driver->hash($fileObject, $hash);
737 }
738
739 /**
740 * Returns a publicly accessible URL for a file.
741 *
742 * WARNING: Access to the file may be restricted by further means, e.g.
743 * some web-based authentication. You have to take care of this yourself.
744 *
745 * @param ResourceInterface $resourceObject The file or folder object
746 * @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)
747 * @return string
748 */
749 public function getPublicUrl(ResourceInterface $resourceObject, $relativeToCurrentScript = FALSE) {
750 $publicUrl = NULL;
751 // Pre-process the public URL by an accordant slot
752 $this->emitPreGeneratePublicUrl($resourceObject, $relativeToCurrentScript, array('publicUrl' => &$publicUrl));
753 // If slot did not handle the signal, use the default way to determine public URL
754 if ($publicUrl === NULL) {
755 $publicUrl = $this->driver->getPublicUrl($resourceObject, $relativeToCurrentScript);
756 }
757 return $publicUrl;
758 }
759
760 /**
761 * Passes a file to the File Processing Services and returns the resulting ProcessedFile object.
762 *
763 * @param FileInterface $fileObject The file object
764 * @param string $context
765 * @param array $configuration
766 *
767 * @return ProcessedFile
768 * @throws \InvalidArgumentException
769 */
770 public function processFile(FileInterface $fileObject, $context, array $configuration) {
771 if ($fileObject->getStorage() !== $this) {
772 throw new \InvalidArgumentException('Cannot process files of foreign storage', 1353401835);
773 }
774 $processedFile = $this->getFileProcessingService()->processFile($fileObject, $this, $context, $configuration);
775
776 return $processedFile;
777 }
778
779 /**
780 * Copies a file from the storage for local processing.
781 *
782 * @param FileInterface $fileObject
783 * @param bool $writable
784 * @return string Path to local file (either original or copied to some temporary local location)
785 */
786 public function getFileForLocalProcessing(FileInterface $fileObject, $writable = TRUE) {
787 $filePath = $this->driver->getFileForLocalProcessing($fileObject, $writable);
788 // @todo: shouldn't this go in the driver? this function is called from the indexing service
789 // @todo: and recursively calls itself over and over again, this is left out for now with getModificationTime()
790 // touch($filePath, $fileObject->getModificationTime());
791 return $filePath;
792 }
793
794 /**
795 * Get file by identifier
796 *
797 * @param string $identifier
798 * @return FileInterface
799 */
800 public function getFile($identifier) {
801 return $this->driver->getFile($identifier);
802 }
803
804 /**
805 * Get information about a file
806 *
807 * @param FileInterface $fileObject
808 * @return array
809 */
810 public function getFileInfo(FileInterface $fileObject) {
811 return $this->driver->getFileInfo($fileObject);
812 }
813
814 /**
815 * Get information about a file by its identifier
816 *
817 * @param string $identifier
818 *
819 * @throws \BadMethodCallException
820 * @return array
821 */
822 public function getFileInfoByIdentifier($identifier) {
823 throw new \BadMethodCallException('The method ResourceStorage::getFileInfoByIdentifier() has been deprecated. Please fix your method call and use getFileInfo with the file object instead.', 1346577887);
824 }
825
826 /**
827 * Unsets the file and folder name filters, thus making this storage return unfiltered file lists.
828 *
829 * @return void
830 */
831 public function unsetFileAndFolderNameFilters() {
832 $this->fileAndFolderNameFilters = array();
833 }
834
835 /**
836 * Resets the file and folder name filters to the default values defined in the TYPO3 configuration.
837 *
838 * @return void
839 */
840 public function resetFileAndFolderNameFiltersToDefault() {
841 $this->fileAndFolderNameFilters = $GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['defaultFilterCallbacks'];
842 }
843
844 /**
845 * Returns the file and folder name filters used by this storage.
846 *
847 * @return array
848 */
849 public function getFileAndFolderNameFilters() {
850 return $this->fileAndFolderNameFilters;
851 }
852
853 public function setFileAndFolderNameFilters(array $filters) {
854 $this->fileAndFolderNameFilters = $filters;
855 return $this;
856 }
857
858 public function addFileAndFolderNameFilter($filter) {
859 $this->fileAndFolderNameFilters[] = $filter;
860 }
861
862 /**
863 * Returns a list of files in a given path, filtered by some custom filter methods.
864 *
865 * @see getUnfilteredFileList(), getFileListWithDefaultFilters()
866 * @param string $path The path to list
867 * @param integer $start The position to start the listing; if not set or 0, start from the beginning
868 * @param integer $numberOfItems The number of items to list; if not set, return all items
869 * @param bool $useFilters If FALSE, the list is returned without any filtering; otherwise, the filters defined for this storage are used.
870 * @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.
871 * @param boolean $recursive
872 * @return array Information about the files found.
873 */
874 // TODO check if we should use a folder object instead of $path
875 // TODO add unit test for $loadIndexRecords
876 public function getFileList($path, $start = 0, $numberOfItems = 0, $useFilters = TRUE, $loadIndexRecords = TRUE, $recursive = FALSE) {
877 $rows = array();
878 if ($loadIndexRecords) {
879 $rows = $this->getFileRepository()->getFileIndexRecordsForFolder($this->getFolder($path));
880 }
881 $filters = $useFilters == TRUE ? $this->fileAndFolderNameFilters : array();
882 $items = $this->driver->getFileList($path, $start, $numberOfItems, $filters, $rows, $recursive);
883
884 // We should not sort when fetching a recursive list, as these are indexed numerically
885 if ($recursive === FALSE) {
886 uksort($items, 'strnatcasecmp');
887 }
888
889 return $items;
890 }
891
892 /**
893 * Returns TRUE if the specified file exists.
894 *
895 * @param string $identifier
896 * @return boolean
897 */
898 public function hasFile($identifier) {
899 // @todo: access check?
900 return $this->driver->fileExists($identifier);
901 }
902
903 /**
904 * Checks if the queried file in the given folder exists.
905 *
906 * @param string $fileName
907 * @param Folder $folder
908 * @return boolean
909 */
910 public function hasFileInFolder($fileName, Folder $folder) {
911 return $this->driver->fileExistsInFolder($fileName, $folder);
912 }
913
914 /**
915 * Get contents of a file object
916 *
917 * @param FileInterface $file
918 *
919 * @throws Exception\InsufficientFileReadPermissionsException
920 * @return string
921 */
922 public function getFileContents($file) {
923 // Check if $file is readable
924 if (!$this->checkFileActionPermission('read', $file)) {
925 throw new Exception\InsufficientFileReadPermissionsException('Reading file "' . $file->getIdentifier() . '" is not allowed.', 1330121089);
926 }
927 return $this->driver->getFileContents($file);
928 }
929
930 /**
931 * Set contents of a file object.
932 *
933 * @param AbstractFile $file
934 * @param string $contents
935 *
936 * @throws \Exception|\RuntimeException
937 * @throws Exception\InsufficientFileWritePermissionsException
938 * @throws Exception\InsufficientUserPermissionsException
939 * @return integer The number of bytes written to the file
940 */
941 public function setFileContents(AbstractFile $file, $contents) {
942 // Check if user is allowed to edit
943 if (!$this->checkUserActionPermission('edit', 'File')) {
944 throw new Exception\InsufficientUserPermissionsException(('Updating file "' . $file->getIdentifier()) . '" not allowed for user.', 1330121117);
945 }
946 // Check if $file is writable
947 if (!$this->checkFileActionPermission('write', $file)) {
948 throw new Exception\InsufficientFileWritePermissionsException('Writing to file "' . $file->getIdentifier() . '" is not allowed.', 1330121088);
949 }
950 // Call driver method to update the file and update file properties afterwards
951 $result = $this->driver->setFileContents($file, $contents);
952 $fileInfo = $this->driver->getFileInfo($file);
953 $fileInfo['sha1'] = $this->driver->hash($file, 'sha1');
954 $file->updateProperties($fileInfo);
955 $this->getFileRepository()->update($file);
956 return $result;
957 }
958
959 /**
960 * Creates a new file
961 *
962 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_newfile()
963 *
964 * @param string $fileName
965 * @param Folder $targetFolderObject
966 *
967 * @throws Exception\InsufficientFolderWritePermissionsException
968 * @return FileInterface The file object
969 */
970 public function createFile($fileName, Folder $targetFolderObject) {
971 if (!$this->checkFolderActionPermission('add', $targetFolderObject)) {
972 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to create directories on this storage "' . $targetFolderObject->getIdentifier() . '"', 1323059807);
973 }
974 return $this->driver->createFile($fileName, $targetFolderObject);
975 }
976
977 /**
978 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::deleteFile()
979 *
980 * @param $fileObject FileInterface
981 *
982 * @throws Exception\InsufficientFileAccessPermissionsException
983 * @throws Exception\FileOperationErrorException
984 * @return bool TRUE if deletion succeeded
985 */
986 public function deleteFile($fileObject) {
987 if (!$this->checkFileActionPermission('remove', $fileObject)) {
988 throw new Exception\InsufficientFileAccessPermissionsException('You are not allowed to delete the file "' . $fileObject->getIdentifier() . '\'', 1319550425);
989 }
990
991 $this->emitPreFileDeleteSignal($fileObject);
992
993 $result = $this->driver->deleteFile($fileObject);
994 if ($result === FALSE) {
995 throw new Exception\FileOperationErrorException('Deleting the file "' . $fileObject->getIdentifier() . '\' failed.', 1329831691);
996 }
997 // Mark the file object as deleted
998 $fileObject->setDeleted();
999 $this->getFileRepository()->update($fileObject);
1000
1001 $this->emitPostFileDeleteSignal($fileObject);
1002
1003 return TRUE;
1004 }
1005
1006 /**
1007 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_copy()
1008 * copies a source file (from any location) in to the target
1009 * folder, the latter has to be part of this storage
1010 *
1011 * @param FileInterface $file
1012 * @param Folder $targetFolder
1013 * @param string $targetFileName an optional destination fileName
1014 * @param string $conflictMode "overrideExistingFile", "renameNewFile", "cancel
1015 *
1016 * @throws \Exception|Exception\AbstractFileOperationException
1017 * @throws Exception\ExistingTargetFileNameException
1018 * @return FileInterface
1019 */
1020 public function copyFile(FileInterface $file, Folder $targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
1021 $this->emitPreFileCopySignal($file, $targetFolder);
1022 $this->checkFileCopyPermissions($file, $targetFolder, $targetFileName);
1023 if ($targetFileName === NULL) {
1024 $targetFileName = $file->getName();
1025 }
1026 // File exists and we should abort, let's abort
1027 if ($conflictMode === 'cancel' && $targetFolder->hasFile($targetFileName)) {
1028 throw new Exception\ExistingTargetFileNameException('The target file already exists.', 1320291063);
1029 }
1030 // File exists and we should find another name, let's find another one
1031 if ($conflictMode === 'renameNewFile' && $targetFolder->hasFile($targetFileName)) {
1032 $targetFileName = $this->getUniqueName($targetFolder, $targetFileName);
1033 }
1034 $sourceStorage = $file->getStorage();
1035 // Call driver method to create a new file from an existing file object,
1036 // and return the new file object
1037 if ($sourceStorage === $this) {
1038 $newFileObject = $this->driver->copyFileWithinStorage($file, $targetFolder, $targetFileName);
1039 } else {
1040 $tempPath = $file->getForLocalProcessing();
1041 $newFileObject = $this->driver->addFile($tempPath, $targetFolder, $targetFileName);
1042 }
1043 $this->emitPostFileCopySignal($file, $targetFolder);
1044 return $newFileObject;
1045 }
1046
1047 /**
1048 * Check if a file has the permission to be uploaded to a Folder/Storage,
1049 * if not throw an exception
1050 *
1051 * @param string $localFilePath the temporary file name from $_FILES['file1']['tmp_name']
1052 * @param Folder $targetFolder
1053 * @param string $targetFileName the destination file name $_FILES['file1']['name']
1054 * @param int $uploadedFileSize
1055 *
1056 * @throws Exception\InsufficientFolderWritePermissionsException
1057 * @throws Exception\UploadException
1058 * @throws Exception\IllegalFileExtensionException
1059 * @throws Exception\UploadSizeException
1060 * @throws Exception\InsufficientUserPermissionsException
1061 * @return void
1062 */
1063 protected function checkFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileSize) {
1064 // Makes sure the user is allowed to upload
1065 if (!$this->checkUserActionPermission('upload', 'File')) {
1066 throw new Exception\InsufficientUserPermissionsException('You are not allowed to upload files to this storage "' . $this->getUid() . '"', 1322112430);
1067 }
1068 // Makes sure this is an uploaded file
1069 if (!is_uploaded_file($localFilePath)) {
1070 throw new Exception\UploadException('The upload has failed, no uploaded file found!', 1322110455);
1071 }
1072 // Max upload size (kb) for files.
1073 $maxUploadFileSize = GeneralUtility::getMaxUploadFileSize() * 1024;
1074 if ($uploadedFileSize >= $maxUploadFileSize) {
1075 throw new Exception\UploadSizeException('The uploaded file exceeds the size-limit of ' . $maxUploadFileSize . ' bytes', 1322110041);
1076 }
1077 // Check if targetFolder is writable
1078 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
1079 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1322120356);
1080 }
1081 // Check for a valid file extension
1082 if (!$this->checkFileExtensionPermission($targetFileName)) {
1083 throw new Exception\IllegalFileExtensionException('Extension of file name is not allowed in "' . $targetFileName . '"!', 1322120271);
1084 }
1085 }
1086
1087 /**
1088 * Check if a file has the permission to be copied on a File/Folder/Storage,
1089 * if not throw an exception
1090 *
1091 * @param FileInterface $file
1092 * @param Folder $targetFolder
1093 * @param string $targetFileName
1094 *
1095 * @throws Exception
1096 * @throws Exception\InsufficientFolderWritePermissionsException
1097 * @throws Exception\IllegalFileExtensionException
1098 * @throws Exception\InsufficientFileReadPermissionsException
1099 * @throws Exception\InsufficientUserPermissionsException
1100 * @return void
1101 */
1102 protected function checkFileCopyPermissions(FileInterface $file, Folder $targetFolder, $targetFileName) {
1103 // Check if targetFolder is within this storage, this should never happen
1104 if ($this->getUid() != $targetFolder->getStorage()->getUid()) {
1105 throw new Exception('The operation of the folder cannot be called by this storage "' . $this->getUid() . '"', 1319550405);
1106 }
1107 // Check if user is allowed to copy
1108 if (!$this->checkUserActionPermission('copy', 'File')) {
1109 throw new Exception\InsufficientUserPermissionsException('You are not allowed to copy files to this storage "' . $this->getUid() . '"', 1319550415);
1110 }
1111 // Check if $file is readable
1112 if (!$this->checkFileActionPermission('read', $file)) {
1113 throw new Exception\InsufficientFileReadPermissionsException('You are not allowed to read the file "' . $file->getIdentifier() . '\'', 1319550425);
1114 }
1115 // Check if targetFolder is writable
1116 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
1117 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319550435);
1118 }
1119 // Check for a valid file extension
1120 if (!$this->checkFileExtensionPermission($targetFileName)) {
1121 throw new Exception\IllegalFileExtensionException('You are not allowed to copy a file of that type.', 1319553317);
1122 }
1123 }
1124
1125 /**
1126 * Moves a $file into a $targetFolder
1127 * the target folder has to be part of this storage
1128 *
1129 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_move()
1130 *
1131 * @param FileInterface $file
1132 * @param Folder $targetFolder
1133 * @param string $targetFileName an optional destination fileName
1134 * @param string $conflictMode "overrideExistingFile", "renameNewFile", "cancel
1135 *
1136 * @throws Exception\ExistingTargetFileNameException
1137 * @return FileInterface
1138 */
1139 public function moveFile($file, $targetFolder, $targetFileName = NULL, $conflictMode = 'renameNewFile') {
1140 $this->checkFileMovePermissions($file, $targetFolder);
1141 if ($targetFileName === NULL) {
1142 $targetFileName = $file->getName();
1143 }
1144 if ($targetFolder->hasFile($targetFileName)) {
1145 // File exists and we should abort, let's abort
1146 if ($conflictMode === 'renameNewFile') {
1147 $targetFileName = $this->getUniqueName($targetFolder, $targetFileName);
1148 } elseif ($conflictMode === 'cancel') {
1149 throw new Exception\ExistingTargetFileNameException('The target file already exists', 1329850997);
1150 }
1151 }
1152 $this->emitPreFileMoveSignal($file, $targetFolder);
1153 $sourceStorage = $file->getStorage();
1154 // Call driver method to move the file that also updates the file
1155 // object properties
1156 try {
1157 if ($sourceStorage === $this) {
1158 $newIdentifier = $this->driver->moveFileWithinStorage($file, $targetFolder, $targetFileName);
1159 $this->updateFile($file, $newIdentifier);
1160 } else {
1161 $tempPath = $file->getForLocalProcessing();
1162 $newIdentifier = $this->driver->addFileRaw($tempPath, $targetFolder, $targetFileName);
1163 $sourceStorage->driver->deleteFileRaw($file->getIdentifier());
1164 $this->updateFile($file, $newIdentifier, $this);
1165 }
1166 } catch (\TYPO3\CMS\Core\Exception $e) {
1167 echo $e->getMessage();
1168 }
1169 $this->emitPostFileMoveSignal($file, $targetFolder);
1170 return $file;
1171 }
1172
1173 /**
1174 * Updates the properties of a file object with some that are freshly
1175 * fetched from the driver.
1176 *
1177 * @param AbstractFile $file
1178 * @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)
1179 * @param ResourceStorage $storage
1180 * @return void
1181 */
1182 protected function updateFile(AbstractFile $file, $identifier = '', $storage = NULL) {
1183 if ($identifier === '') {
1184 $identifier = $file->getIdentifier();
1185 }
1186 $fileInfo = $this->driver->getFileInfoByIdentifier($identifier);
1187 // TODO extend mapping
1188 $newProperties = array(
1189 'storage' => $fileInfo['storage'],
1190 'identifier' => $fileInfo['identifier'],
1191 'tstamp' => $fileInfo['mtime'],
1192 'crdate' => $fileInfo['ctime'],
1193 'mime_type' => $fileInfo['mimetype'],
1194 'size' => $fileInfo['size'],
1195 'name' => $fileInfo['name']
1196 );
1197 if ($storage !== NULL) {
1198 $newProperties['storage'] = $storage->getUid();
1199 }
1200 $file->updateProperties($newProperties);
1201 $this->getFileRepository()->update($file);
1202 }
1203
1204 /**
1205 * Checks for permissions to move a file.
1206 *
1207 * @throws \RuntimeException
1208 * @throws Exception\InsufficientFileReadPermissionsException
1209 * @throws Exception\InsufficientFileWritePermissionsException
1210 * @throws Exception\InsufficientFolderAccessPermissionsException
1211 * @throws Exception\InsufficientUserPermissionsException
1212 * @param FileInterface $file
1213 * @param Folder $targetFolder
1214 * @return void
1215 */
1216 protected function checkFileMovePermissions(FileInterface $file, Folder $targetFolder) {
1217 // Check if targetFolder is within this storage
1218 if ($this->getUid() != $targetFolder->getStorage()->getUid()) {
1219 throw new \RuntimeException();
1220 }
1221 // Check if user is allowed to move
1222 if (!$this->checkUserActionPermission('move', 'File')) {
1223 throw new Exception\InsufficientUserPermissionsException('You are not allowed to move files to storage "' . $this->getUid() . '"', 1319219349);
1224 }
1225 // Check if $file is readable
1226 if (!$this->checkFileActionPermission('read', $file)) {
1227 throw new Exception\InsufficientFileReadPermissionsException('You are not allowed to read the file "' . $file->getIdentifier() . '\'', 1319219349);
1228 }
1229 // Check if $file is writable
1230 if (!$this->checkFileActionPermission('write', $file)) {
1231 throw new Exception\InsufficientFileWritePermissionsException('You are not allowed to move the file "' . $file->getIdentifier() . '\'', 1319219349);
1232 }
1233 // Check if targetFolder is writable
1234 if (!$this->checkFolderActionPermission('write', $targetFolder)) {
1235 throw new Exception\InsufficientFolderAccessPermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319219349);
1236 }
1237 }
1238
1239 /**
1240 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_rename()
1241 *
1242 * @param FileInterface $file
1243 * @param string $targetFileName
1244 *
1245 * @throws Exception\InsufficientFileWritePermissionsException
1246 * @throws Exception\InsufficientFileReadPermissionsException
1247 * @throws Exception\InsufficientUserPermissionsException
1248 * @return FileInterface
1249 */
1250 // TODO add $conflictMode setting
1251 public function renameFile($file, $targetFileName) {
1252 // The name should be different from the current.
1253 if ($file->getIdentifier() == $targetFileName) {
1254 return $file;
1255 }
1256 // Check if user is allowed to rename
1257 if (!$this->checkUserActionPermission('rename', 'File')) {
1258 throw new Exception\InsufficientUserPermissionsException('You are not allowed to rename files."', 1319219349);
1259 }
1260 // Check if $file is readable
1261 if (!$this->checkFileActionPermission('read', $file)) {
1262 throw new Exception\InsufficientFileReadPermissionsException('You are not allowed to read the file "' . $file->getIdentifier() . '\'', 1319219349);
1263 }
1264 // Check if $file is writable
1265 if (!$this->checkFileActionPermission('write', $file)) {
1266 throw new Exception\InsufficientFileWritePermissionsException('You are not allowed to rename the file "' . $file->getIdentifier() . '\'', 1319219349);
1267 }
1268
1269 $this->emitPreFileRenameSignal($file, $targetFileName);
1270
1271 // Call driver method to rename the file that also updates the file
1272 // object properties
1273 try {
1274 $newIdentifier = $this->driver->renameFile($file, $targetFileName);
1275 $this->updateFile($file, $newIdentifier);
1276 $this->getFileRepository()->update($file);
1277 } catch (\RuntimeException $e) {
1278
1279 }
1280
1281 $this->emitPostFileRenameSignal($file, $targetFileName);
1282
1283 return $file;
1284 }
1285
1286 /**
1287 * Replaces a file with a local file (e.g. a freshly uploaded file)
1288 *
1289 * @param FileInterface $file
1290 * @param string $localFilePath
1291 *
1292 * @throws \InvalidArgumentException
1293 * @return FileInterface
1294 */
1295 public function replaceFile(FileInterface $file, $localFilePath) {
1296 if (!file_exists($localFilePath)) {
1297 throw new \InvalidArgumentException('File "' . $localFilePath . '" does not exist.', 1325842622);
1298 }
1299 // TODO check permissions
1300 $this->emitPreFileReplaceSignal($file, $localFilePath);
1301 $result = $this->driver->replaceFile($file, $localFilePath);
1302 $this->emitPostFileReplaceSignal($file, $localFilePath);
1303 return $result;
1304 }
1305
1306 /**
1307 * Adds an uploaded file into the Storage. Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::file_upload()
1308 *
1309 * @param array $uploadedFileData contains information about the uploaded file given by $_FILES['file1']
1310 * @param Folder $targetFolder the target folder
1311 * @param string $targetFileName the file name to be written
1312 * @param string $conflictMode possible value are 'cancel', 'replace'
1313 * @return FileInterface The file object
1314 */
1315 public function addUploadedFile(array $uploadedFileData, Folder $targetFolder = NULL, $targetFileName = NULL, $conflictMode = 'cancel') {
1316 $localFilePath = $uploadedFileData['tmp_name'];
1317 if ($targetFolder === NULL) {
1318 $targetFolder = $this->getDefaultFolder();
1319 }
1320 if ($targetFileName === NULL) {
1321 $targetFileName = $uploadedFileData['name'];
1322 }
1323 // Handling $conflictMode is delegated to addFile()
1324 $this->checkFileUploadPermissions($localFilePath, $targetFolder, $targetFileName, $uploadedFileData['size']);
1325 $resultObject = $this->addFile($localFilePath, $targetFolder, $targetFileName, $conflictMode);
1326 return $resultObject;
1327 }
1328
1329 /********************
1330 * FOLDER ACTIONS
1331 ********************/
1332 /**
1333 * Returns an array with all file objects in a folder and its subfolders, with the file identifiers as keys.
1334 *
1335 * @param Folder $folder
1336 * @return File[]
1337 */
1338 protected function getAllFileObjectsInFolder(Folder $folder) {
1339 $files = array();
1340 $folderQueue = array($folder);
1341 while (!empty($folderQueue)) {
1342 $folder = array_shift($folderQueue);
1343 foreach ($folder->getSubfolders() as $subfolder) {
1344 $folderQueue[] = $subfolder;
1345 }
1346 foreach ($folder->getFiles() as $file) {
1347 $files[$file->getIdentifier()] = $file;
1348 }
1349 }
1350 return $files;
1351 }
1352
1353 /**
1354 * Moves a folder. If you want to move a folder from this storage to another
1355 * one, call this method on the target storage, otherwise you will get an exception.
1356 *
1357 * @param Folder $folderToMove The folder to move.
1358 * @param Folder $targetParentFolder The target parent folder
1359 * @param string $newFolderName
1360 * @param string $conflictMode How to handle conflicts; one of "overrideExistingFile", "renameNewFolder", "cancel
1361 *
1362 * @throws \Exception|\TYPO3\CMS\Core\Exception
1363 * @throws \InvalidArgumentException
1364 * @return Folder
1365 */
1366 // TODO add tests
1367 public function moveFolder(Folder $folderToMove, Folder $targetParentFolder, $newFolderName = NULL, $conflictMode = 'renameNewFolder') {
1368 $sourceStorage = $folderToMove->getStorage();
1369 $returnObject = NULL;
1370 if (!$targetParentFolder->getStorage() == $this) {
1371 throw new \InvalidArgumentException('Cannot move a folder into a folder that does not belong to this storage.', 1325777289);
1372 }
1373 $newFolderName = $newFolderName ? $newFolderName : $folderToMove->getName();
1374 // TODO check if folder already exists in $targetParentFolder, handle this conflict then
1375 $this->emitPreFolderMoveSignal($folderToMove, $targetParentFolder, $newFolderName);
1376 // Get all file objects now so we are able to update them after moving the folder
1377 $fileObjects = $this->getAllFileObjectsInFolder($folderToMove);
1378 if ($sourceStorage === $this) {
1379 $fileMappings = $this->driver->moveFolderWithinStorage($folderToMove, $targetParentFolder, $newFolderName);
1380 } else {
1381 $fileMappings = $this->moveFolderBetweenStorages($folderToMove, $targetParentFolder, $newFolderName);
1382 }
1383 // Update the identifier and storage of all file objects
1384 foreach ($fileObjects as $oldIdentifier => $fileObject) {
1385 $newIdentifier = $fileMappings[$oldIdentifier];
1386 $fileObject->updateProperties(array('storage' => $this, 'identifier' => $newIdentifier));
1387 $this->getFileRepository()->update($fileObject);
1388 }
1389 $returnObject = $this->getFolder($fileMappings[$folderToMove->getIdentifier()]);
1390 $this->emitPostFolderMoveSignal($folderToMove, $targetParentFolder, $newFolderName);
1391 return $returnObject;
1392 }
1393
1394 /**
1395 * Moves the given folder from a different storage to the target folder in this storage.
1396 *
1397 * @param Folder $folderToMove
1398 * @param Folder $targetParentFolder
1399 * @param string $newFolderName
1400 *
1401 * @return boolean
1402 */
1403 protected function moveFolderBetweenStorages(Folder $folderToMove, Folder $targetParentFolder, $newFolderName) {
1404 return $this->getDriver()->moveFolderBetweenStorages($folderToMove, $targetParentFolder, $newFolderName);
1405 }
1406
1407 /**
1408 * Copy folder
1409 *
1410 * @param Folder $folderToCopy The folder to copy
1411 * @param Folder $targetParentFolder The target folder
1412 * @param string $newFolderName
1413 * @param string $conflictMode "overrideExistingFolder", "renameNewFolder", "cancel
1414 * @return Folder The new (copied) folder object
1415 */
1416 public function copyFolder(Folder $folderToCopy, Folder $targetParentFolder, $newFolderName = NULL, $conflictMode = 'renameNewFolder') {
1417 // TODO implement the $conflictMode handling
1418 // TODO permission checks
1419 $returnObject = NULL;
1420 $newFolderName = $newFolderName ? $newFolderName : $folderToCopy->getName();
1421 $this->emitPreFolderCopySignal($folderToCopy, $targetParentFolder, $newFolderName);
1422 $sourceStorage = $folderToCopy->getStorage();
1423 // call driver method to move the file
1424 // that also updates the file object properties
1425 try {
1426 if ($sourceStorage === $this) {
1427 $this->driver->copyFolderWithinStorage($folderToCopy, $targetParentFolder, $newFolderName);
1428 $returnObject = $this->getFolder($targetParentFolder->getSubfolder($newFolderName)->getIdentifier());
1429 } else {
1430 $this->copyFolderBetweenStorages($folderToCopy, $targetParentFolder, $newFolderName);
1431 }
1432 } catch (\TYPO3\CMS\Core\Exception $e) {
1433 echo $e->getMessage();
1434 }
1435 $this->emitPostFolderCopySignal($folderToCopy, $targetParentFolder, $newFolderName);
1436 return $returnObject;
1437 }
1438
1439 /**
1440 * Copy folders between storages
1441 *
1442 * @param Folder $folderToCopy
1443 * @param Folder $targetParentFolder
1444 * @param string $newFolderName
1445 *
1446 * @return boolean
1447 */
1448 protected function copyFolderBetweenStorages(Folder $folderToCopy, Folder $targetParentFolder, $newFolderName) {
1449 return $this->getDriver()->copyFolderBetweenStorages($folderToCopy, $targetParentFolder, $newFolderName);
1450 }
1451
1452 /**
1453 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::folder_move()
1454 *
1455 * @param Folder $folderObject
1456 * @param string $newName
1457 * @throws \Exception
1458 * @throws \InvalidArgumentException
1459 * @return Folder
1460 */
1461 public function renameFolder($folderObject, $newName) {
1462 // TODO unit tests
1463
1464 if (!$this->checkFolderActionPermission('rename', $folderObject)) {
1465 throw new \TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException('You are not allowed to rename the folder "' . $folderObject->getIdentifier() . '\'', 1357811441);
1466 }
1467
1468 $returnObject = NULL;
1469 if ($this->driver->folderExistsInFolder($newName, $folderObject)) {
1470 throw new \InvalidArgumentException('The folder ' . $newName . ' already exists in folder ' . $folderObject->getIdentifier(), 1325418870);
1471 }
1472
1473 $this->emitPreFolderRenameSignal($folderObject, $newName);
1474
1475 $fileObjects = $this->getAllFileObjectsInFolder($folderObject);
1476 $fileMappings = $this->driver->renameFolder($folderObject, $newName);
1477 // Update the identifier of all file objects
1478 foreach ($fileObjects as $oldIdentifier => $fileObject) {
1479 $newIdentifier = $fileMappings[$oldIdentifier];
1480 $fileObject->updateProperties(array('identifier' => $newIdentifier));
1481 $this->getFileRepository()->update($fileObject);
1482 }
1483 $returnObject = $this->getFolder($fileMappings[$folderObject->getIdentifier()]);
1484
1485 $this->emitPostFolderRenameSignal($folderObject, $newName);
1486
1487 return $returnObject;
1488 }
1489
1490 /**
1491 * Previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::folder_delete()
1492 *
1493 * @param Folder $folderObject
1494 * @param bool $deleteRecursively
1495 * @throws \RuntimeException
1496 * @throws Exception\InsufficientFileAccessPermissionsException
1497 * @return boolean
1498 */
1499 public function deleteFolder($folderObject, $deleteRecursively = FALSE) {
1500 if (!$this->checkFolderActionPermission('remove', $folderObject)) {
1501 throw new Exception\InsufficientFileAccessPermissionsException('You are not allowed to access the folder "' . $folderObject->getIdentifier() . '\'', 1323423953);
1502 }
1503 if ($this->driver->isFolderEmpty($folderObject) && !$deleteRecursively) {
1504 throw new \RuntimeException('Could not delete folder "' . $folderObject->getIdentifier() . '" because it is not empty.', 1325952534);
1505 }
1506
1507 $this->emitPreFolderDeleteSignal($folderObject);
1508
1509 $result = $this->driver->deleteFolder($folderObject, $deleteRecursively);
1510
1511 $this->emitPostFolderDeleteSignal($folderObject);
1512
1513 return $result;
1514 }
1515
1516 /**
1517 * Returns a list of folders in a given path.
1518 *
1519 * @param string $path The path to list
1520 * @param integer $start The position to start the listing; if not set or 0, start from the beginning
1521 * @param integer $numberOfItems The number of items to list; if not set, return all items
1522 * @param boolean $useFilters If FALSE, the list is returned without any filtering; otherwise, the filters defined for this storage are used.
1523 * @return array Information about the folders found.
1524 */
1525 public function getFolderList($path, $start = 0, $numberOfItems = 0, $useFilters = TRUE) {
1526 $filters = $useFilters === TRUE ? $this->fileAndFolderNameFilters : array();
1527 return $this->fetchFolderListFromDriver($path, $start, $numberOfItems, $filters);
1528 }
1529
1530 /**
1531 * @param $path
1532 * @param int $start
1533 * @param int $numberOfItems
1534 * @param array $folderFilterCallbacks
1535 * @param boolean $recursive
1536 * @return array
1537 */
1538 public function fetchFolderListFromDriver($path, $start = 0, $numberOfItems = 0, array $folderFilterCallbacks = array(), $recursive = FALSE) {
1539 $items = $this->driver->getFolderList($path, $start, $numberOfItems, $folderFilterCallbacks, $recursive);
1540 if (!empty($items)) {
1541 // Exclude the _processed_ folder, so it won't get indexed etc
1542 // The processed folder might be any sub folder in storage
1543 $processingFolder = $this->getProcessingFolder();
1544 if ($processingFolder) {
1545 $processedFolderIdentifier = $this->processingFolder->getIdentifier();
1546 $processedFolderIdentifier = trim($processedFolderIdentifier, '/');
1547 $processedFolderIdentifierParts = explode('/', $processedFolderIdentifier);
1548 $processedFolderName = array_pop($processedFolderIdentifierParts);
1549 $processedFolderParent = implode('/', $processedFolderIdentifierParts);
1550 if ($processedFolderParent === trim($path, '/') && isset($items[$processedFolderName])) {
1551 unset($items[$processedFolderName]);
1552 }
1553 }
1554 uksort($items, 'strnatcasecmp');
1555 }
1556 return $items;
1557 }
1558
1559 /**
1560 * Returns TRUE if the specified folder exists.
1561 *
1562 * @param string $identifier
1563 * @return boolean
1564 */
1565 public function hasFolder($identifier) {
1566 return $this->driver->folderExists($identifier);
1567 }
1568
1569 /**
1570 * Checks if the given file exists in the given folder
1571 *
1572 * @param string $folderName
1573 * @param Folder $folder
1574 * @return boolean
1575 */
1576 public function hasFolderInFolder($folderName, Folder $folder) {
1577 return $this->driver->folderExistsInFolder($folderName, $folder);
1578 }
1579
1580 /**
1581 * Creates a new folder.
1582 *
1583 * previously in \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_newfolder()
1584 *
1585 * @param string $folderName The new folder name
1586 * @param Folder $parentFolder (optional) the parent folder to create the new folder inside of. If not given, the root folder is used
1587 *
1588 * @throws Exception\InsufficientFolderWritePermissionsException
1589 * @throws \InvalidArgumentException
1590 * @return Folder The new folder object
1591 */
1592 public function createFolder($folderName, Folder $parentFolder = NULL) {
1593 if ($parentFolder === NULL) {
1594 $parentFolder = $this->getRootLevelFolder();
1595 }
1596 if (!$this->driver->folderExists($parentFolder->getIdentifier())) {
1597 throw new \InvalidArgumentException('Parent folder "' . $parentFolder->getIdentifier() . '" does not exist.', 1325689164);
1598 }
1599 if (!$this->checkFolderActionPermission('add', $parentFolder)) {
1600 throw new Exception\InsufficientFolderWritePermissionsException('You are not allowed to create directories in the folder "' . $parentFolder->getIdentifier() . '"', 1323059807);
1601 }
1602 $folderParts = GeneralUtility::trimExplode('/', $folderName, TRUE);
1603 foreach ($folderParts as $folder) {
1604 // TODO check if folder creation succeeded
1605 if ($this->hasFolderInFolder($folder, $parentFolder)) {
1606 $parentFolder = $this->driver->getFolderInFolder($folder, $parentFolder);
1607 } else {
1608 $parentFolder = $this->driver->createFolder($folder, $parentFolder);
1609 }
1610 }
1611 return $parentFolder;
1612 }
1613
1614 /**
1615 * Returns the default folder where new files are stored if no other folder is given.
1616 *
1617 * @return Folder
1618 */
1619 public function getDefaultFolder() {
1620 return $this->driver->getDefaultFolder();
1621 }
1622
1623 /**
1624 * @param string $identifier
1625 *
1626 * @throws Exception\NotInMountPointException
1627 * @throws Exception\FolderDoesNotExistException
1628 * @return Folder
1629 */
1630 public function getFolder($identifier) {
1631 if (!$this->driver->folderExists($identifier)) {
1632 throw new Exception\FolderDoesNotExistException('Folder ' . $identifier . ' does not exist.', 1320575630);
1633 }
1634 $folderObject = $this->driver->getFolder($identifier);
1635 if ($this->fileMounts && !$this->isWithinFileMountBoundaries($folderObject)) {
1636 throw new Exception\NotInMountPointException('Folder "' . $identifier . '" is not within your mount points.', 1330120649);
1637 } else {
1638 return $folderObject;
1639 }
1640 }
1641
1642 /**
1643 * Returns the folders on the root level of the storage
1644 * or the first mount point of this storage for this user
1645 *
1646 * @return Folder
1647 */
1648 public function getRootLevelFolder() {
1649 if (count($this->fileMounts)) {
1650 $mount = reset($this->fileMounts);
1651 return $mount['folder'];
1652 } else {
1653 return $this->driver->getRootLevelFolder();
1654 }
1655 }
1656
1657 /**
1658 * Emits the configuration pre-processing signal
1659 *
1660 * @return void
1661 */
1662 protected function emitPreProcessConfigurationSignal() {
1663 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreProcessConfiguration, array($this));
1664 }
1665
1666 /**
1667 * Emits the configuration post-processing signal
1668 *
1669 * @return void
1670 */
1671 protected function emitPostProcessConfigurationSignal() {
1672 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostProcessConfiguration, array($this));
1673 }
1674
1675 /**
1676 * Emits file pre-copy signal
1677 *
1678 * @param FileInterface $file
1679 * @param Folder $targetFolder
1680 * @return void
1681 */
1682 protected function emitPreFileCopySignal(FileInterface $file, Folder $targetFolder) {
1683 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreFileCopy, array($file, $targetFolder));
1684 }
1685
1686 /**
1687 * Emits the file post-copy signal
1688 *
1689 * @param FileInterface $file
1690 * @param Folder $targetFolder
1691 * @return void
1692 */
1693 protected function emitPostFileCopySignal(FileInterface $file, Folder $targetFolder) {
1694 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFileCopy, array($file, $targetFolder));
1695 }
1696
1697 /**
1698 * Emits the file pre-move signal
1699 *
1700 * @param FileInterface $file
1701 * @param Folder $targetFolder
1702 * @return void
1703 */
1704 protected function emitPreFileMoveSignal(FileInterface $file, Folder $targetFolder) {
1705 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreFileMove, array($file, $targetFolder));
1706 }
1707
1708 /**
1709 * Emits the file post-move signal
1710 *
1711 * @param FileInterface $file
1712 * @param Folder $targetFolder
1713 * @return void
1714 */
1715 protected function emitPostFileMoveSignal(FileInterface $file, Folder $targetFolder) {
1716 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFileMove, array($file, $targetFolder));
1717 }
1718
1719 /**
1720 * Emits the file pre-rename signal
1721 *
1722 * @param FileInterface $file
1723 * @param $targetFolder
1724 * @return void
1725 */
1726 protected function emitPreFileRenameSignal(FileInterface $file, $targetFolder) {
1727 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreFileRename, array($file, $targetFolder));
1728 }
1729
1730 /**
1731 * Emits the file post-rename signal
1732 *
1733 * @param FileInterface $file
1734 * @param $targetFolder
1735 * @return void
1736 */
1737 protected function emitPostFileRenameSignal(FileInterface $file, $targetFolder) {
1738 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFileRename, array($file, $targetFolder));
1739 }
1740
1741 /**
1742 * Emits the file pre-replace signal
1743 *
1744 * @param FileInterface $file
1745 * @param $localFilePath
1746 * @return void
1747 */
1748 protected function emitPreFileReplaceSignal(FileInterface $file, $localFilePath) {
1749 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreFileReplace, array($file, $localFilePath));
1750 }
1751
1752 /**
1753 * Emits the file post-replace signal
1754 *
1755 * @param FileInterface $file
1756 * @param $localFilePath
1757 * @return void
1758 */
1759 protected function emitPostFileReplaceSignal(FileInterface $file, $localFilePath) {
1760 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFileReplace, array($file, $localFilePath));
1761 }
1762
1763 /**
1764 * Emits the file pre-deletion signal
1765 *
1766 * @param FileInterface $file
1767 * @return void
1768 */
1769 protected function emitPreFileDeleteSignal(FileInterface $file) {
1770 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreFileDelete, array($file));
1771 }
1772
1773 /**
1774 * Emits the file post-deletion signal
1775 *
1776 * @param FileInterface $file
1777 * @return void
1778 */
1779 protected function emitPostFileDeleteSignal(FileInterface $file) {
1780 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFileDelete, array($file));
1781 }
1782
1783 /**
1784 * Emits the folder pre-copy signal
1785 *
1786 * @param Folder $folder
1787 * @param Folder $targetFolder
1788 * @param $newName
1789 * @return void
1790 */
1791 protected function emitPreFolderCopySignal(Folder $folder, Folder $targetFolder, $newName) {
1792 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self::SIGNAL_PreFolderCopy, array($folder, $targetFolder));
1793 }
1794
1795 /**
1796 * Emits the folder post-copy signal
1797 *
1798 * @param Folder $folder
1799 * @param Folder $targetFolder
1800 * @param $newName
1801 * @return void
1802 */
1803 protected function emitPostFolderCopySignal(Folder $folder, Folder $targetFolder, $newName) {
1804 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self::SIGNAL_PostFolderCopy, array($folder, $targetFolder));
1805 }
1806
1807 /**
1808 * Emits the folder pre-move signal
1809 *
1810 * @param Folder $folder
1811 * @param Folder $targetFolder
1812 * @param $newName
1813 * @return void
1814 */
1815 protected function emitPreFolderMoveSignal(Folder $folder, Folder $targetFolder, $newName) {
1816 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self::SIGNAL_PreFolderMove, array($folder, $targetFolder));
1817 }
1818
1819 /**
1820 * Emits the folder post-move signal
1821 *
1822 * @param Folder $folder
1823 * @param Folder $targetFolder
1824 * @param $newName
1825 * @return void
1826 */
1827 protected function emitPostFolderMoveSignal(Folder $folder, Folder $targetFolder, $newName) {
1828 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self::SIGNAL_PostFolderMove, array($folder, $targetFolder));
1829 }
1830
1831 /**
1832 * Emits the folder pre-rename signal
1833 *
1834 * @param Folder $folder
1835 * @param $newName
1836 * @return void
1837 */
1838 protected function emitPreFolderRenameSignal(Folder $folder, $newName) {
1839 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self::SIGNAL_PreFolderRename, array($folder, $newName));
1840 }
1841
1842 /**
1843 * Emits the folder post-rename signal
1844 *
1845 * @param Folder $folder
1846 * @param $newName
1847 * @return void
1848 */
1849 protected function emitPostFolderRenameSignal(Folder $folder, $newName) {
1850 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self::SIGNAL_PostFolderRename, array($folder, $newName));
1851 }
1852
1853 /**
1854 * Emits the folder pre-deletion signal
1855 *
1856 * @param Folder $folder
1857 * @return void
1858 */
1859 protected function emitPreFolderDeleteSignal(Folder $folder) {
1860 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self::SIGNAL_PreFolderDelete, array($folder));
1861 }
1862
1863 /**
1864 * Emits folder postdeletion signal.
1865 *
1866 * @param Folder $folder
1867 * @return void
1868 */
1869 protected function emitPostFolderDeleteSignal(Folder $folder) {
1870 $this->getSignalSlotDispatcher()->dispatch('ResourceStorage', self::SIGNAL_PostFolderDelete, array($folder));
1871 }
1872
1873 /**
1874 * Emits file pre-processing signal when generating a public url for a file or folder.
1875 *
1876 * @param ResourceInterface $resourceObject
1877 * @param boolean $relativeToCurrentScript
1878 * @param array $urlData
1879 */
1880 protected function emitPreGeneratePublicUrl(ResourceInterface $resourceObject, $relativeToCurrentScript, array $urlData) {
1881 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreGeneratePublicUrl, array($this, $this->driver, $resourceObject, $relativeToCurrentScript, $urlData));
1882 }
1883
1884 /**
1885 * Returns the destination path/fileName of a unique fileName/foldername in that path.
1886 * If $theFile exists in $theDest (directory) the file have numbers appended up to $this->maxNumber. Hereafter a unique string will be appended.
1887 * This function is used by fx. TCEmain when files are attached to records and needs to be uniquely named in the uploads/* folders
1888 *
1889 * @param Folder $folder
1890 * @param string $theFile The input fileName to check
1891 * @param boolean $dontCheckForUnique If set the fileName is returned with the path prepended without checking whether it already existed!
1892 *
1893 * @throws \RuntimeException
1894 * @return string A unique fileName inside $folder, based on $theFile.
1895 * @see \TYPO3\CMS\Core\Utility\File\BasicFileUtility::getUniqueName()
1896 */
1897 // TODO check if this should be moved back to Folder
1898 protected function getUniqueName(Folder $folder, $theFile, $dontCheckForUnique = FALSE) {
1899 static $maxNumber = 99, $uniqueNamePrefix = '';
1900 // Fetches info about path, name, extention of $theFile
1901 $origFileInfo = GeneralUtility::split_fileref($theFile);
1902 // Adds prefix
1903 if ($uniqueNamePrefix) {
1904 $origFileInfo['file'] = $uniqueNamePrefix . $origFileInfo['file'];
1905 $origFileInfo['filebody'] = $uniqueNamePrefix . $origFileInfo['filebody'];
1906 }
1907 // Check if the file exists and if not - return the fileName...
1908 $fileInfo = $origFileInfo;
1909 // The destinations file
1910 $theDestFile = $fileInfo['file'];
1911 // If the file does NOT exist we return this fileName
1912 if (!$this->driver->fileExistsInFolder($theDestFile, $folder) || $dontCheckForUnique) {
1913 return $theDestFile;
1914 }
1915 // Well the fileName in its pure form existed. Now we try to append
1916 // numbers / unique-strings and see if we can find an available fileName
1917 // This removes _xx if appended to the file
1918 $theTempFileBody = preg_replace('/_[0-9][0-9]$/', '', $origFileInfo['filebody']);
1919 $theOrigExt = $origFileInfo['realFileext'] ? '.' . $origFileInfo['realFileext'] : '';
1920 for ($a = 1; $a <= $maxNumber + 1; $a++) {
1921 // First we try to append numbers
1922 if ($a <= $maxNumber) {
1923 $insert = '_' . sprintf('%02d', $a);
1924 } else {
1925 // TODO remove constant 6
1926 $insert = '_' . substr(md5(uniqId('')), 0, 6);
1927 }
1928 $theTestFile = $theTempFileBody . $insert . $theOrigExt;
1929 // The destinations file
1930 $theDestFile = $theTestFile;
1931 // If the file does NOT exist we return this fileName
1932 if (!$this->driver->fileExistsInFolder($theDestFile, $folder)) {
1933 return $theDestFile;
1934 }
1935 }
1936 throw new \RuntimeException('Last possible name "' . $theDestFile . '" is already taken.', 1325194291);
1937 }
1938
1939 /**
1940 * Get the SignalSlot dispatcher
1941 *
1942 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
1943 */
1944 protected function getSignalSlotDispatcher() {
1945 if (!isset($this->signalSlotDispatcher)) {
1946 $this->signalSlotDispatcher = $this->getObjectManager()->get('TYPO3\\CMS\\Extbase\\SignalSlot\\Dispatcher');
1947 }
1948 return $this->signalSlotDispatcher;
1949 }
1950
1951 /**
1952 * Get the ObjectManager
1953 *
1954 * @return \TYPO3\CMS\Extbase\Object\ObjectManager
1955 */
1956 protected function getObjectManager() {
1957 return GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
1958 }
1959
1960 /**
1961 * @return ResourceFactory
1962 */
1963 protected function getFileFactory() {
1964 return GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\ResourceFactory');
1965 }
1966
1967 /**
1968 * @return \TYPO3\CMS\Core\Resource\FileRepository
1969 */
1970 protected function getFileRepository() {
1971 return GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\FileRepository');
1972 }
1973
1974 /**
1975 * @return Service\FileProcessingService
1976 */
1977 protected function getFileProcessingService() {
1978 if (!$this->fileProcessingService) {
1979 $this->fileProcessingService = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\Service\\FileProcessingService', $this, $this->driver);
1980 }
1981 return $this->fileProcessingService;
1982 }
1983
1984 /**
1985 * Gets the role of a folder
1986 *
1987 * @param FolderInterface $folder Folder object to get the role from
1988 * @return string The role the folder has
1989 */
1990 public function getRole(FolderInterface $folder) {
1991 $folderRole = FolderInterface::ROLE_DEFAULT;
1992
1993 if (method_exists($this->driver, 'getRole')) {
1994 $folderRole = $this->driver->getRole($folder);
1995 }
1996
1997 if ($folder->getIdentifier() === $this->getProcessingFolder()->getIdentifier()) {
1998 $folderRole = FolderInterface::ROLE_PROCESSING;
1999 }
2000
2001 return $folderRole;
2002 }
2003
2004 /**
2005 * Getter function to return the folder where the files can
2006 * be processed. does not check for access rights here
2007 *
2008 * @todo check if we need to implement "is writable" capability
2009 * @return Folder the processing folder, can be empty as well, if the storage doesn't have a processing folder
2010 */
2011 public function getProcessingFolder() {
2012 if (!isset($this->processingFolder)) {
2013 $processingFolder = self::DEFAULT_ProcessingFolder;
2014 if (!empty($this->storageRecord['processingfolder'])) {
2015 $processingFolder = $this->storageRecord['processingfolder'];
2016 }
2017 $processingFolder = '/' . trim($processingFolder, '/') . '/';
2018 // this way, we also worry about deeplinked folders like typo3temp/_processed_
2019 if ($this->driver->folderExists($processingFolder) === FALSE) {
2020 $processingFolderParts = explode('/', $processingFolder);
2021 $parentFolder = $this->driver->getRootLevelFolder();
2022 foreach ($processingFolderParts as $folderPart) {
2023 if ($folderPart === '') {
2024 continue;
2025 }
2026 if (!$this->driver->folderExistsInFolder($folderPart, $parentFolder)) {
2027 $parentFolder = $this->driver->createFolder($folderPart, $parentFolder);
2028 } else {
2029 $parentFolder = $parentFolder->getSubfolder($folderPart);
2030 }
2031 }
2032 }
2033 $this->processingFolder = $this->driver->getFolder($processingFolder);
2034 }
2035 return $this->processingFolder;
2036 }
2037 }
2038
2039 ?>