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