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