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