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