[FEATURE] Store passwords encrypted
[TYPO3CMS/Extensions/fal_webdav.git] / Classes / Driver / WebDavDriver.php
1 <?php
2
3 include_once 'Sabre/autoload.php';
4
5 class Tx_FalWebdav_Driver_WebDavDriver extends t3lib_file_Driver_AbstractDriver {
6
7 /**
8 * The base URL of the WebDAV share. Always ends with a trailing slash.
9 *
10 * @var string
11 */
12 protected $baseUrl;
13
14 /**
15 * The base path of the WebDAV store. This is the URL without protocol, host and port (i.e., only the path on the host).
16 * Always ends with a trailing slash.
17 *
18 * @var string
19 */
20 protected $basePath;
21
22 /**
23 * @var Sabre_DAV_Client
24 */
25 protected $davClient;
26
27 /**
28 * The username to use for connecting to the storage.
29 *
30 * @var string
31 */
32 protected $username;
33
34 /**
35 * The password to use for connecting to the storage.
36 *
37 * @var string
38 */
39 protected $password;
40
41 public function __construct(array $configuration = array()) {
42 // TODO Iterate through all string properties and trim them...
43 $configuration['baseUrl'] = trim($configuration['baseUrl']);
44 $password = Tx_FalWebdav_Utility_Encryption::decryptPassword($configuration['password']);
45
46 // TODO check useAuthentication configuration option
47 $this->password = $password;
48 $this->username = $configuration['username'];
49
50 parent::__construct($configuration);
51 }
52
53 /**
54 * Initializes this object. This is called by the storage after the driver has been attached.
55 *
56 * @return void
57 */
58 public function initialize() {
59 $this->capabilities = self::CAPABILITY_BROWSABLE + self::CAPABILITY_PUBLIC + self::CAPABILITY_WRITABLE;
60 }
61
62 public function injectDavClient(Sabre_DAV_Client $client) {
63 $this->davClient = $client;
64 }
65
66 protected function processConfiguration() {
67 $urlInfo = parse_url($this->configuration['baseUrl']);
68 if ($urlInfo === FALSE) {
69 throw new InvalidArgumentException('Invalid base URL configured for WebDAV driver: ' . $this->configuration['baseUrl'], 1325771040);
70 }
71 $this->basePath = rtrim($urlInfo['path'], '/') . '/';
72
73 $username = $urlInfo['user'] ? $urlInfo['user'] : $this->username;
74 $password = $urlInfo['pass'] ? $urlInfo['pass'] : $this->password;
75
76 $settings = array(
77 'baseUri' => $this->configuration['baseUrl'],
78 'userName' => $username,
79 'password' => $password
80 );
81 unset($urlInfo['user']);
82 unset($urlInfo['pass']);
83 $this->baseUrl = t3lib_utility_Http::buildUrl($urlInfo);
84
85 $this->davClient = new Sabre_DAV_Client($settings);
86 }
87
88 /**
89 * Checks if a configuration is valid for this driver.
90 *
91 * Throws an exception if a configuration will not work.
92 *
93 * @param array $configuration
94 * @return void
95 */
96 public static function verifyConfiguration(array $configuration) {
97 // TODO: Implement verifyConfiguration() method.
98 }
99
100 /**
101 * Executes a MOVE request from $oldPath to $newPath.
102 *
103 * @param string $oldPath
104 * @param string $newPath
105 * @return array The result as returned by SabreDAV
106 */
107 public function executeMoveRequest($oldPath, $newPath) {
108 $oldUrl = $this->baseUrl . ltrim($oldPath, '/');
109 $newUrl = $this->baseUrl . ltrim($newPath, '/');
110
111 // force overwriting the file (header Overwrite: T) because the Storage already handled possible conflicts
112 // for us
113 return $this->davClient->request('MOVE', $oldUrl, NULL, array('Destination' => $newUrl, 'Overwrite' => 'T'));
114 }
115
116
117
118 /**
119 * Executes a PROPFIND request on the given URL and returns the result array
120 *
121 * @param string $url
122 * @return array
123 */
124 protected function davPropFind($url) {
125 return $this->davClient->propfind($url, array(
126 '{DAV:}resourcetype',
127 '{DAV:}creationdate',
128 '{DAV:}getcontentlength',
129 '{DAV:}getlastmodified'
130 ), 1);
131 // TODO throw exception on error
132 }
133
134 /**
135 * Checks if a given resource exists in this DAV share.
136 *
137 * @param string $resourcePath The path to the resource, i.e. a regular identifier as used everywhere else here.
138 * @return bool
139 */
140 protected function resourceExists($resourcePath) {
141 if ($resourcePath == '') {
142 throw new InvalidArgumentException('Resource path cannot be empty');
143 }
144 $url = $this->baseUrl . ltrim($resourcePath, '/');
145 try {
146 $this->davClient->request('HEAD', $url);
147 } catch (Sabre_DAV_Exception_NotFound $exception) {
148 return FALSE;
149 }
150 // TODO check if other status codes may also indicate that the file is present
151 return TRUE;
152 }
153
154
155 /**
156 * Returns the complete URL to a file. This is not necessarily the publicly available URL!
157 *
158 * @param string|t3lib_file_File|t3lib_file_Folder $file The file object or its identifier
159 * @return string
160 */
161 protected function getResourceUrl($file) {
162 if (is_object($file)) {
163 return $this->baseUrl . ltrim($file->getIdentifier(), '/');
164 } else {
165 return $this->baseUrl . ltrim($file, '/');
166 }
167 }
168
169 /**
170 * Returns the public URL to a file.
171 *
172 * @param t3lib_file_File $file
173 * @return string
174 */
175 public function getPublicUrl(t3lib_file_File $file) {
176 if ($this->storage->isPublic()) {
177 // as the storage is marked as public, we can simply use the public URL here.
178 return $this->getResourceUrl($file);
179 }
180 }
181
182 /**
183 * Creates a (cryptographic) hash for a file.
184 *
185 * @param t3lib_file_File $file
186 * @param string $hashAlgorithm The hash algorithm to use
187 * @return string
188 * TODO switch parameter order?
189 */
190 public function hash(t3lib_file_File $file, $hashAlgorithm) {
191 // TODO add unit test
192 $fileCopy = $this->copyFileToTemporaryPath($file);
193
194 switch ($hashAlgorithm) {
195 case 'sha1':
196 return sha1_file($fileCopy);
197 break;
198 }
199
200 unlink($fileCopy);
201 }
202
203 /**
204 * Creates a new file and returns the matching file object for it.
205 *
206 * @param string $fileName
207 * @param t3lib_file_Folder $parentFolder
208 * @return t3lib_file_File
209 */
210 public function createFile($fileName, t3lib_file_Folder $parentFolder) {
211 $fileIdentifier = $parentFolder->getIdentifier() . $fileName;
212 $fileUrl = $this->baseUrl . ltrim($fileIdentifier, '/');
213
214 $this->davClient->request('PUT', $fileUrl, '');
215
216 return $this->getFile($fileIdentifier);
217 }
218
219 /**
220 * Returns the contents of a file. Beware that this requires to load the complete file into memory and also may
221 * require fetching the file from an external location. So this might be an expensive operation (both in terms of
222 * processing resources and money) for large files.
223 *
224 * @param t3lib_file_File $file
225 * @return string The file contents
226 */
227 public function getFileContents(t3lib_file_File $file) {
228 $fileUrl = $this->baseUrl . ltrim($file->getIdentifier(), '/');
229
230 $result = $this->davClient->request('GET', $fileUrl);
231
232 return $result['body'];
233 }
234
235 /**
236 * Sets the contents of a file to the specified value.
237 *
238 * @param t3lib_file_File $file
239 * @param string $contents
240 * @return bool TRUE if setting the contents succeeded
241 * @throws RuntimeException if the operation failed
242 */
243 public function setFileContents(t3lib_file_File $file, $contents) {
244 // Apache returns a "204 no content" status after a successful put operation
245
246 $fileUrl = $this->getResourceUrl($file);
247 $result = $this->davClient->request('PUT', $fileUrl, $contents);
248
249 // TODO check result
250 }
251
252 /**
253 * Adds a file from the local server hard disk to a given path in TYPO3s virtual file system.
254 *
255 * This assumes that the local file exists, so no further check is done here!
256 *
257 * @param string $localFilePath
258 * @param t3lib_file_Folder $targetFolder
259 * @param string $fileName The name to add the file under
260 * @return t3lib_file_File
261 */
262 public function addFile($localFilePath, t3lib_file_Folder $targetFolder, $fileName) {
263 $fileIdentifier = $targetFolder->getIdentifier() . $fileName;
264 $fileUrl = $this->baseUrl . ltrim($fileIdentifier);
265
266 $fileHandle = fopen($localFilePath, 'r');
267 if (!is_resource($fileHandle)) {
268 throw new RuntimeException('Could not open handle for ' . $localFilePath, 1325959310);
269 }
270 $result = $this->davClient->request('PUT', $fileUrl, $fileHandle);
271
272 return $this->getFile($fileIdentifier);
273 }
274
275 /**
276 * Checks if a file exists.
277 *
278 * @param string $identifier
279 * @return bool
280 */
281 public function fileExists($identifier) {
282 return $this->resourceExists($identifier);
283 }
284
285 /**
286 * Checks if a file inside a storage folder exists.
287 *
288 * @param string $fileName
289 * @param t3lib_file_Folder $folder
290 * @return boolean
291 */
292 public function fileExistsInFolder($fileName, t3lib_file_Folder $folder) {
293 // TODO add unit test
294 $fileIdentifier = $folder->getIdentifier() . $fileName;
295
296 return $this->resourceExists($fileIdentifier);
297 }
298
299 /**
300 * Returns a (local copy of) a file for processing it. When changing the file, you have to take care of replacing the
301 * current version yourself!
302 *
303 * @param t3lib_file_File $file
304 * @param bool $writable Set this to FALSE if you only need the file for read operations. This might speed up things, e.g. by using a cached local version. Never modify the file if you have set this flag!
305 * @return string The path to the file on the local disk
306 */
307 public function getFileForLocalProcessing(t3lib_file_File $file, $writable = TRUE) {
308 return $this->copyFileToTemporaryPath($file);
309 }
310
311 /**
312 * Returns the permissions of a file as an array (keys r, w) of boolean flags
313 *
314 * @param t3lib_file_File $file
315 * @return array
316 */
317 public function getFilePermissions(t3lib_file_File $file) {
318 return array('r' => TRUE, 'w' => TRUE);
319 }
320
321 /**
322 * Returns the permissions of a folder as an array (keys r, w) of boolean flags
323 *
324 * @param t3lib_file_Folder $folder
325 * @return array
326 */
327 public function getFolderPermissions(t3lib_file_Folder $folder) {
328 return array('r' => TRUE, 'w' => TRUE);
329 }
330
331 /**
332 * Renames a file
333 *
334 * @param t3lib_file_File $file
335 * @param string $newName
336 * @return string The new identifier of the file
337 */
338 public function renameFile(t3lib_file_File $file, $newName) {
339 // TODO add unit test
340 // Renaming works by invoking the MOVE method on the source URL and providing the new destination in the
341 // "Destination:" HTTP header.
342 $sourcePath = $file->getIdentifier();
343 $targetPath = dirname($file->getIdentifier()) . '/' . $newName;
344
345 $this->executeMoveRequest($sourcePath, $targetPath);
346
347 return $targetPath;
348 }
349
350 /**
351 * Replaces the contents (and file-specific metadata) of a file object with a local file.
352 *
353 * @param t3lib_file_File $file
354 * @param string $localFilePath
355 * @return bool
356 */
357 public function replaceFile(t3lib_file_File $file, $localFilePath) {
358 $fileUrl = $this->getResourceUrl($file);
359 $fileHandle = fopen($localFilePath, 'r');
360 if (!is_resource($fileHandle)) {
361 throw new RuntimeException('Could not open handle for ' . $localFilePath, 1325959311);
362 }
363
364 $this->davClient->request('PUT', $fileUrl, $fileHandle);
365 }
366
367 /**
368 * Returns information about a file for a given file identifier.
369 *
370 * @param string $identifier The (relative) path to the file.
371 * @return array
372 */
373 public function getFileInfoByIdentifier($identifier) {
374 $fileUrl = $this->baseUrl . ltrim($identifier, '/');
375
376 $properties = $this->davClient->request('PROPFIND', $fileUrl);
377 $properties = $this->davClient->parseMultiStatus($properties['body']);
378 $properties = $properties[$this->basePath . ltrim($identifier, '/')][200];
379
380 // TODO make this more robust (check if properties are available etc.)
381 $fileInfo = array(
382 'mtime' => strtotime($properties['{DAV:}getlastmodified']),
383 'ctime' => strtotime($properties['{DAV:}creationdate']),
384 'mimetype' => $properties['{DAV:}getcontenttype'],
385 'name' => basename($identifier),
386 'size' => $properties['{DAV:}getcontentlength'],
387 'identifier' => $identifier,
388 'storage' => $this->storage->getUid()
389 );
390
391 return $fileInfo;
392 }
393
394 /**
395 * Returns a list of files inside the specified path
396 *
397 * @param string $path
398 * @param string $pattern
399 * @param integer $start The position to start the listing; if not set, start from the beginning
400 * @param integer $numberOfItems The number of items to list; if not set, return all items
401 * @param array $fileData Two-dimensional, identifier-indexed array of file index records from the database
402 * @return array
403 */
404 // TODO add unit tests
405 // TODO implement pattern matching
406 public function getFileList($path, $pattern = '', $start = 0, $numberOfItems = 0, $fileData = array()) {
407 return $this->getDirectoryItemList($path, $pattern, $start, $numberOfItems, 'getFileList_itemCallback');
408 }
409
410 /**
411 * Returns a list of all folders in a given path
412 *
413 * @param string $path
414 * @param string $pattern
415 * @param integer $start The position to start the listing; if not set, start from the beginning
416 * @param integer $numberOfItems The number of items to list; if not set, return all items
417 * @return array
418 */
419 public function getFolderList($path, $pattern = '', $start = 0, $numberOfItems = 0) {
420 return $this->getDirectoryItemList($path, $pattern, $start, $numberOfItems, 'getFolderList_itemCallback');
421 }
422
423 /**
424 * Generic handler method for directory listings - gluing together the listing items is done
425 *
426 * @param string $path
427 * @param string $pattern
428 * @param integer $start
429 * @param integer $numberOfItems
430 * @param callback $itemHandlerMethod
431 * @return array
432 */
433 // TODO implement pattern matching
434 protected function getDirectoryItemList($path, $pattern, $start, $numberOfItems, $itemHandlerMethod) {
435 $url = $this->baseUrl . ltrim($path, '/');
436 $basePath = $this->basePath . ltrim($path, '/');
437 $properties = $this->davPropFind($url);
438
439 // the returned items are indexed by their key, so sort them here to return the correct items
440 // at least Apache does not sort them before returning
441 uksort($properties, 'strnatcasecmp');
442 $propertyIterator = new ArrayIterator($properties);
443
444 // TODO handle errors
445
446 if ($path !== '' && $path != '/') {
447 $path = '/' . trim($path, '/') . '/';
448 }
449
450 // if we have only one entry, this is the folder we are currently in, so there are no items -> return an empty array
451 if (count($properties) == 1) {
452 return array();
453 }
454
455 $c = $numberOfItems > 0 ? $numberOfItems : $propertyIterator->count();
456 $propertyIterator->seek($start);
457
458 $items = array();
459 while ($propertyIterator->valid() && $c > 0) {
460 --$c;
461
462 $item = $propertyIterator->current();
463 $filePath = $propertyIterator->key();
464 $propertyIterator->next();
465
466 list($key, $entry) = $this->$itemHandlerMethod($item, $filePath, $basePath, $path);
467
468 if (empty($entry)) {
469 ++$c;
470 continue;
471 }
472 $items[$key] = $entry;
473 }
474
475 return $items;
476 }
477
478 /**
479 * Callback method that extracts file information from a single entry inside a DAV PROPFIND response. Called by getDirectoryItemList.
480 *
481 * @param array $item
482 * @param string $filePath
483 * @param string $basePath
484 * @param string $path
485 * @return array
486 */
487 protected function getFileList_itemCallback(array $item, $filePath, $basePath, $path) {
488 if ($item['{DAV:}resourcetype']->is('{DAV:}collection')) {
489 return array('', array());
490 }
491 $fileName = substr($filePath, strlen($basePath));
492
493 // TODO add more items
494 return array($fileName, array(
495 'name' => $fileName,
496 'identifier' => $path . $fileName,
497 'creationDate' => strtotime($item['{DAV:}creationdate']),
498 'storage' => $this->storage->getUid()
499 ));
500 }
501
502 /**
503 * Callback method that extracts folder information from a single entry inside a DAV PROPFIND response. Called by getDirectoryItemList.
504 *
505 * @param array $item
506 * @param string $filePath
507 * @param string $basePath
508 * @param string $path
509 * @return array
510 */
511 protected function getFolderList_itemCallback(array $item, $filePath, $basePath, $path) {
512 if (!$item['{DAV:}resourcetype']->is('{DAV:}collection')) {
513 return array('', array());
514 }
515 $folderName = trim(substr($filePath, strlen($basePath)), '/');
516
517 if ($folderName == '') {
518 return array('', array());
519 }
520
521 // TODO add more items
522 return array($folderName, array(
523 'name' => $folderName,
524 'identifier' => $path . trim($folderName, '/') . '/',
525 'creationDate' => strtotime($item['{DAV:}creationdate']),
526 'storage' => $this->storage->getUid()
527 ));
528 }
529
530 /**
531 * Copies a file to a temporary path and returns that path. You have to take care of removing the temporary file yourself!
532 *
533 * @param t3lib_file_File $file
534 * @return string The temporary path
535 */
536 public function copyFileToTemporaryPath(t3lib_file_File $file) {
537 $temporaryPath = t3lib_div::tempnam('vfs-tempfile-');
538 $fileUrl = $this->getResourceUrl($file);
539
540 $result = $this->davClient->request('GET', $fileUrl);
541 file_put_contents($temporaryPath, $result['body']);
542
543 return $temporaryPath;
544 }
545
546 /**
547 * Moves a file *within* the current storage.
548 * Note that this is only about an intra-storage move action, where a file is just
549 * moved to another folder in the same storage.
550 *
551 * @param t3lib_file_File $file
552 * @param t3lib_file_Folder $targetFolder
553 * @param string $fileName
554 * @return string The new identifier of the file
555 */
556 public function moveFileWithinStorage(t3lib_file_File $file, t3lib_file_Folder $targetFolder, $fileName) {
557 $newPath = $targetFolder->getIdentifier() . $fileName;
558
559 try {
560 $result = $this->executeMoveRequest($file->getIdentifier(), $newPath);
561 } catch (Sabre_DAV_Exception $e) {
562 // TODO insert correct exception here
563 throw new t3lib_file_exception_AbstractFileOperationException('Moving file ' . $file->getIdentifier()
564 . ' to ' . $newPath . ' failed.', 1325848030);
565 }
566 // TODO check if there are some return codes that signalize an error, but do not throw an exception
567 // status codes: 204: file was overwritten; 201: file was created;
568
569 return $targetFolder->getIdentifier() . $fileName;
570 }
571
572 /**
573 * Copies a file *within* the current storage.
574 * Note that this is only about an intra-storage copy action, where a file is just
575 * copied to another folder in the same storage.
576 *
577 * @param t3lib_file_File $file
578 * @param t3lib_file_Folder $targetFolder
579 * @param string $fileName
580 * @return t3lib_file_File The new (copied) file object.
581 */
582 public function copyFileWithinStorage(t3lib_file_File $file, t3lib_file_Folder $targetFolder, $fileName) {
583 $oldFileUrl = $this->getResourceUrl($file);
584 $newFileUrl = $this->getResourceUrl($targetFolder) . $fileName;
585 $newFileIdentifier = $targetFolder->getIdentifier() . $fileName;
586
587 try {
588 // force overwriting the file (header Overwrite: T) because the Storage already handled possible conflicts
589 // for us
590 $result = $this->davClient->request('COPY', $oldFileUrl, NULL, array('Destination' => $newFileUrl, 'Overwrite' => 'T'));
591 } catch (Sabre_DAV_Exception $e) {
592 // TODO insert correct exception here
593 throw new t3lib_file_exception_AbstractFileOperationException('Copying file ' . $file->getIdentifier() . ' to '
594 . $newFileIdentifier . ' failed.', 1325848030);
595 }
596 // TODO check if there are some return codes that signalize an error, but do not throw an exception
597 // status codes: 204: file was overwritten; 201: file was created;
598
599 return $this->getFile($newFileIdentifier);
600 }
601
602 /**
603 * Folder equivalent to moveFileWithinStorage().
604 *
605 * @param t3lib_file_Folder $folderToMove
606 * @param t3lib_file_Folder $targetFolder
607 * @param string $newFolderName
608 * @return array Mapping of old file identifiers to new ones
609 */
610 public function moveFolderWithinStorage(t3lib_file_Folder $folderToMove, t3lib_file_Folder $targetFolder,
611 $newFolderName = NULL) {
612 $newFolderIdentifier = $targetFolder->getIdentifier() . $newFolderName . '/';
613
614 try {
615 $result = $this->executeMoveRequest($folderToMove->getIdentifier(), $newFolderIdentifier);
616 } catch (Sabre_DAV_Exception $e) {
617 // TODO insert correct exception here
618 throw new t3lib_file_exception_AbstractFileOperationException('Moving folder ' . $folderToMove->getIdentifier()
619 . ' to ' . $newFolderIdentifier . ' failed: ' . $e->getMessage(), 1326135944);
620 }
621 // TODO check if there are some return codes that signalize an error, but do not throw an exception
622 // status codes: 204: file was overwritten; 201: file was created;
623
624 // TODO extract mapping of old to new identifiers from server response
625 }
626
627 /**
628 * Folder equivalent to copyFileWithinStorage().
629 *
630 * @param t3lib_file_Folder $folderToMove
631 * @param t3lib_file_Folder $targetFolder
632 * @param string $newFolderName
633 * @return bool
634 */
635 public function copyFolderWithinStorage(t3lib_file_Folder $folderToMove, t3lib_file_Folder $targetFolder,
636 $newFolderName = NULL) {
637 $oldFolderUrl = $this->getResourceUrl($folderToMove);
638 $newFolderUrl = $this->getResourceUrl($targetFolder) . $newFolderName . '/';
639 $newFolderIdentifier = $targetFolder->getIdentifier() . $newFolderName . '/';
640
641 try {
642 $result = $this->davClient->request('COPY', $oldFolderUrl, NULL, array('Destination' => $newFolderUrl, 'Overwrite' => 'T'));
643 } catch (Sabre_DAV_Exception $e) {
644 // TODO insert correct exception here
645 throw new t3lib_file_exception_AbstractFileOperationException('Moving folder ' . $folderToMove->getIdentifier()
646 . ' to ' . $newFolderIdentifier . ' failed.', 1326135944);
647 }
648 // TODO check if there are some return codes that signalize an error, but do not throw an exception
649 // status codes: 204: file was overwritten; 201: file was created;
650
651 return $newFolderIdentifier;
652 }
653
654 /**
655 * Removes a file from this storage. This does not check if the file is still used or if it is a bad idea to delete
656 * it for some other reason - this has to be taken care of in the upper layers (e.g. the Storage)!
657 *
658 * @param t3lib_file_File $file
659 * @return boolean TRUE if the operation succeeded
660 */
661 public function deleteFile(t3lib_file_File $file) {
662 // TODO add unit tests
663 $fileUrl = $this->baseUrl . ltrim($file->getIdentifier(), '/');
664
665 $result = $this->davClient->request('DELETE', $fileUrl);
666
667 // 204 is derived from the answer Apache gives - there might be other status codes that indicate success
668 return ($result['statusCode'] == 204);
669 }
670
671 /**
672 * Adds a file at the specified location. This should only be used internally.
673 *
674 * @param string $localFilePath
675 * @param t3lib_file_Folder $targetFolder
676 * @param string $targetFileName
677 * @return string The new identifier of the file
678 */
679 public function addFileRaw($localFilePath, t3lib_file_Folder $targetFolder, $targetFileName) {
680 return $this->addFile($localFilePath, $targetFolder, $targetFileName)->getIdentifier();
681 }
682
683 /**
684 * Deletes a file without access and usage checks. This should only be used internally.
685 *
686 * This accepts an identifier instead of an object because we might want to delete files that have no object
687 * associated with (or we don't want to create an object for) them - e.g. when moving a file to another storage.
688 *
689 * @param string $identifier
690 * @return bool TRUE if removing the file succeeded
691 */
692 public function deleteFileRaw($identifier) {
693 return $this->deleteFile($this->getFile($identifier));
694 }
695
696 /**
697 * Returns the root level folder of the storage.
698 *
699 * @return t3lib_file_Folder
700 */
701 public function getRootLevelFolder() {
702 return $this->getFolder('/');
703 }
704
705 /**
706 * Returns the default folder new files should be put into.
707 *
708 * @return t3lib_file_Folder
709 */
710 public function getDefaultFolder() {
711 return $this->getFolder('/');
712 }
713
714 /**
715 * Creates a folder.
716 *
717 * @param string $newFolderName
718 * @param t3lib_file_Folder $parentFolder
719 * @return t3lib_file_Folder The new (created) folder object
720 */
721 public function createFolder($newFolderName, t3lib_file_Folder $parentFolder) {
722 // We add a slash to the path as some actions require a trailing slash on some servers.
723 // Apache's mod_dav e.g. does not do it for this action, but it does not do harm either, so we add it anyways
724 $folderPath = $parentFolder->getIdentifier() . $newFolderName . '/';
725 $folderUrl = $this->baseUrl . ltrim($folderPath, '/');
726
727 $this->davClient->request('MKCOL', $folderUrl);
728
729 /** @var $factory t3lib_file_Factory */
730 $factory = t3lib_div::makeInstance('t3lib_file_Factory');
731 return $factory->createFolderObject($this->storage, $folderPath, $newFolderName);
732 }
733
734 /**
735 * Checks if a folder exists
736 *
737 * @param string $identifier
738 * @return bool
739 */
740 public function folderExists($identifier) {
741 // TODO add unit test
742 // TODO check if this test suffices to find out if the resource really is a folder - it might not do with some implementations
743 $identifier = '/' . trim($identifier, '/') . '/';
744 return $this->resourceExists($identifier);
745 }
746
747 /**
748 * Checks if a file inside a storage folder exists.
749 *
750 * @param string $folderName
751 * @param t3lib_file_Folder $folder
752 * @return bool
753 */
754 public function folderExistsInFolder($folderName, t3lib_file_Folder $folder) {
755 $folderIdentifier = $folder->getIdentifier() . $folderName . '/';
756 return $this->resourceExists($folderIdentifier);
757 }
758
759 /**
760 * Checks if a given identifier is within a container, e.g. if a file or folder is within another folder.
761 * This can be used to check for webmounts.
762 *
763 * @param t3lib_file_Folder $container
764 * @param string $content
765 * @return bool
766 */
767 public function isWithin(t3lib_file_Folder $container, $content) {
768 // TODO extend this to also support objects as $content
769 $folderPath = $container->getIdentifier();
770 $content = '/' . ltrim($content, '/');
771
772 return t3lib_div::isFirstPartOfStr($content, $folderPath);
773 }
774
775 /**
776 * Removes a folder from this storage.
777 *
778 * @param t3lib_file_Folder $folder
779 * @param bool $deleteRecursively
780 * @return boolean
781 */
782 public function deleteFolder(t3lib_file_Folder $folder, $deleteRecursively = FALSE) {
783 $folderUrl = $this->getResourceUrl($folder);
784
785 // We don't need to specify a depth header when deleting (see sect. 9.6.1 of RFC #4718)
786 $this->davClient->request('DELETE', $folderUrl, '', array());
787 }
788
789 /**
790 * Renames a folder in this storage.
791 *
792 * @param t3lib_file_Folder $folder
793 * @param string $newName The new folder name
794 * @return string The new identifier of the folder if the operation succeeds
795 * @throws RuntimeException if renaming the folder failed
796 */
797 public function renameFolder(t3lib_file_Folder $folder, $newName) {
798 $sourcePath = $folder->getIdentifier();
799 $targetPath = dirname($folder->getIdentifier()) . '/' . $newName . '/';
800
801 try {
802 $result = $this->executeMoveRequest($sourcePath, $targetPath);
803 } catch (Sabre_DAV_Exception $e) {
804 // TODO insert correct exception here
805 throw new t3lib_file_exception_AbstractFileOperationException('Renaming ' . $sourcePath . ' to '
806 . $targetPath . ' failed.', 1325848030);
807 }
808
809 return $targetPath;
810 }
811
812 /**
813 * Checks if a folder contains files and (if supported) other folders.
814 *
815 * @param t3lib_file_Folder $folder
816 * @return bool TRUE if there are no files and folders within $folder
817 */
818 public function isFolderEmpty(t3lib_file_Folder $folder) {
819 $folderUrl = $this->getResourceUrl($folder);
820
821 $folderContents = $this->davPropFind($folderUrl);
822
823 return (count($folderContents) == 1);
824 }
825
826 }