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