7 * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
8 * @author Lars Kneschke <l.kneschke@metaways.de>
9 * @copyright Copyright (c) 2014-2014 Metaways Infosystems GmbH (http://www.metaways.de)
13 * class to handle top level folders for an application
18 abstract class Tinebase_WebDav_Collection_AbstractContainerTree extends \Sabre\DAV\Collection implements \Sabre\DAV\IProperties, \Sabre\DAVACL\IACL, \Sabre\DAV\IExtendedCollection
21 * the current application object
23 * @var Tinebase_Model_Application
25 protected $_application;
32 protected $_applicationName;
35 * app has personal folders
39 protected $_hasPersonalFolders = true;
42 * app has records folder
46 protected $_hasRecordFolder = true;
58 protected $_pathParts;
64 protected $_useIdAsName;
66 protected static $_classCache = array (
73 * @param string $path the current path
74 * @param bool $useIdAsName use name or id as node name
76 public function __construct($path, $useIdAsName = false)
79 $this->_useIdAsName = $useIdAsName;
83 * use login as folder name
87 protected function _useLoginAsFolderName()
89 return Tinebase_Config::getInstance()->get(Tinebase_Config::USE_LOGINNAME_AS_FOLDERNAME);
94 * @see \Sabre\DAV\Collection::createDirectory()
96 public function createDirectory($name)
98 return $this->_createContainer(array(
105 * @see \Sabre\DAV\IExtendedCollection::createExtendedCollection()
107 public function createExtendedCollection($name, array $resourceType, array $properties)
109 return $this->_createContainer(array(
110 'name' => isset($properties['{DAV:}displayname']) ? $properties['{DAV:}displayname'] : $name,
112 'color' => isset($properties['{http://apple.com/ns/ical/}calendar-color']) ? substr($properties['{http://apple.com/ns/ical/}calendar-color'], 0, 7) : null
118 * @see Sabre\DAV\Collection::getChild()
120 public function getChild($name)
122 switch (count($this->_getPathParts())) {
123 # path == /<applicationPrefix> (for example calendars)
124 # return folders for currentuser, other users and 'shared' folder
126 # * contact_id of user
129 if ($name === Tinebase_Model_Container::TYPE_SHARED ||
130 ($this->_hasRecordFolder && $name === Tinebase_FileSystem::FOLDER_TYPE_RECORDS)) {
131 $path = $this->_path . '/' . $name;
133 } elseif ($this->_hasPersonalFolders) {
134 if ($name === '__currentuser__') {
135 $path = $this->_path . '/__currentuser__';
139 // check if it exists only
140 $this->_getUser($name);
142 } catch (Tinebase_Exception_NotFound $tenf) {
143 $message = "Directory $this->_path/$name not found";
144 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(
145 __METHOD__ . '::' . __LINE__ . ' ' . $message);
146 throw new \Sabre\DAV\Exception\NotFound($message);
149 $path = $this->_path . '/' . $name;
153 throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
156 $className = $this->_getApplicationName() . '_Frontend_WebDAV';
158 return new $className($path, $this->_useIdAsName);
162 # path == /<applicationPrefix>/<contactid>|'shared'
165 if (Tinebase_Helper::array_value(1, $this->_getPathParts()) == Tinebase_Model_Container::TYPE_SHARED) {
167 if ($name instanceof Tinebase_Model_Container) {
169 } elseif ($this->_useIdAsName) {
170 // first try to fetch by uuid ...
172 $container = Tinebase_Container::getInstance()->getByProperty((string) $name, 'uuid');
173 } catch (Tinebase_Exception_NotFound $tenf) {
174 // ... if that fails by id
175 $container = Tinebase_Container::getInstance()->getContainerById($name);
178 $container = Tinebase_Container::getInstance()->getContainerByName(
179 $this->_getApplicationName(),
181 Tinebase_Model_Container::TYPE_SHARED
185 } catch (Tinebase_Exception_NotFound $tenf) {
186 throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
188 } catch (Tinebase_Exception_InvalidArgument $teia) {
189 // invalid container id provided
190 throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
192 } elseif ($this->_hasRecordFolder && Tinebase_Helper::array_value(1, $this->_getPathParts()) == Tinebase_FileSystem::FOLDER_TYPE_RECORDS) {
194 return new Tinebase_Frontend_WebDAV_RecordCollection($this->_path . '/' . $name);
196 } elseif ($this->_hasPersonalFolders) {
197 if (Tinebase_Helper::array_value(1, $this->_getPathParts()) === '__currentuser__') {
198 $accountId = Tinebase_Core::getUser()->accountId;
202 $accountId = $this->_getUser(Tinebase_Helper::array_value(1, $this->_getPathParts()))->accountId;
204 } catch (Tinebase_Exception_NotFound $tenf) {
205 throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
210 if ($name instanceof Tinebase_Model_Container) {
212 } elseif ($this->_useIdAsName) {
213 // first try to fetch by uuid ...
215 $container = Tinebase_Container::getInstance()->getByProperty((string) $name, 'uuid');
216 } catch (Tinebase_Exception_NotFound $tenf) {
217 // ... if that fails by id
218 $container = Tinebase_Container::getInstance()->getContainerById($name);
222 $container = Tinebase_Container::getInstance()->getContainerByName(
223 $this->_getApplicationName(),
225 Tinebase_Model_Container::TYPE_PERSONAL,
230 } catch (Tinebase_Exception_NotFound $tenf) {
231 throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
233 } catch (Tinebase_Exception_InvalidArgument $teia) {
234 // invalid container id provided
235 throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
239 throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
242 if (!Tinebase_Core::getUser()->hasGrant($container, Tinebase_Model_Grants::GRANT_READ) ||
243 !Tinebase_Core::getUser()->hasGrant($container, Tinebase_Model_Grants::GRANT_SYNC)) {
244 throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
247 $objectClass = Tinebase_Application::getInstance()->getApplicationById($container->application_id)->name . '_Frontend_WebDAV_Container';
249 if (! class_exists($objectClass)) {
250 throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
253 return new $objectClass($container, $this->_useIdAsName);
258 throw new Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
265 * Returns an array with all the child nodes
267 * the records subtree is not returned as child here. It's only available via getChild().
269 * @return \Sabre\DAV\INode[]
271 function getChildren()
275 switch (count($this->_getPathParts())) {
276 # path == /<applicationPrefix> (for example calendars)
277 # return folders for currentuser, other users and 'shared' folder
279 $children[] = $this->getChild(Tinebase_Model_Container::TYPE_SHARED);
281 if ($this->_hasPersonalFolders) {
282 $children[] = $this->getChild(
283 $this->_useIdAsName ? Tinebase_Core::getUser()->contact_id : $this->_useLoginAsFolderName() ? Tinebase_Core::getUser()->accountLoginName : Tinebase_Core::getUser()->accountDisplayName
286 $otherUsers = Tinebase_Container::getInstance()->getOtherUsers(Tinebase_Core::getUser(), $this->_getApplicationName(), array(
287 Tinebase_Model_Grants::GRANT_READ,
288 Tinebase_Model_Grants::GRANT_SYNC
291 foreach ($otherUsers as $user) {
292 if ($user->contact_id && $user->visibility === Tinebase_Model_User::VISIBILITY_DISPLAYED) {
294 $folderId = $this->_useIdAsName ? $user->contact_id : $this->_useLoginAsFolderName() ? $user->accountLoginName : $user->accountDisplayName;
296 $children[] = $this->getChild($folderId);
297 } catch (\Sabre\DAV\Exception\NotFound $sdavenf) {
298 // ignore contacts not found
306 # path == /<applicationPrefix>/<contactid>|'shared'
309 if (Tinebase_Helper::array_value(1, $this->_getPathParts()) == Tinebase_Model_Container::TYPE_SHARED) {
310 $containers = Tinebase_Container::getInstance()->getSharedContainer(
311 Tinebase_Core::getUser(),
312 $this->_getApplicationName(),
314 Tinebase_Model_Grants::GRANT_READ,
315 Tinebase_Model_Grants::GRANT_SYNC
319 } elseif ($this->_hasPersonalFolders) {
320 if (Tinebase_Helper::array_value(1, $this->_getPathParts()) === '__currentuser__') {
321 $accountId = Tinebase_Core::getUser()->accountId;
325 $accountId = $this->_getUser(Tinebase_Helper::array_value(1, $this->_getPathParts()))->accountId;
326 } catch (Tinebase_Exception_NotFound $tenf) {
327 throw new \Sabre\DAV\Exception\NotFound("Path $this->_path not found");
332 if ($this->_getApplicationName() === 'Filemanager' || $this->_clientSupportsDelegations()) {
333 $containers = Tinebase_Container::getInstance()->getPersonalContainer(
334 Tinebase_Core::getUser(),
335 $this->_getApplicationName(),
338 Tinebase_Model_Grants::GRANT_READ,
339 Tinebase_Model_Grants::GRANT_SYNC
343 $containers = Tinebase_Container::getInstance()->getContainerByACL(Tinebase_Core::getUser(), $this->_getApplicationName(), array(
344 Tinebase_Model_Grants::GRANT_READ,
345 Tinebase_Model_Grants::GRANT_SYNC
348 } catch (Tinebase_Exception_AccessDenied $tead) {
349 throw new Sabre\DAV\Exception\NotFound("Could not find path (" . $tead->getMessage() . ")");
353 throw new Sabre\DAV\Exception\NotFound("Path $this->_path not found");
356 foreach ($containers as $container) {
358 $children[] = $this->getChild($this->_useIdAsName ? $container->getId() : $container->name);
359 } catch (\Sabre\DAV\Exception\NotFound $sdavenf) {
360 // ignore containers not found
367 throw new Sabre\DAV\Exception\NotFound("Path $this->_path not found");
376 * checks if client supports delegations
380 * @todo don't use $_SERVER to fetch user agent
381 * @todo move user agent parsing to Tinebase
383 protected function _clientSupportsDelegations()
385 if (isset($_SERVER['HTTP_USER_AGENT'])) {
386 list($backend, $version) = Calendar_Convert_Event_VCalendar_Factory::parseUserAgent($_SERVER['HTTP_USER_AGENT']);
387 $clientSupportsDelegations = ($backend === Calendar_Convert_Event_VCalendar_Factory::CLIENT_MACOSX);
389 $clientSupportsDelegations = false;
392 return $clientSupportsDelegations;
400 public function getETag()
404 foreach ($this->getChildren() as $child) {
405 $etags[] = $child->getETag();
408 return '"' . sha1(implode(null, $etags)) . '"';
412 * Returns a group principal
414 * This must be a url to a principal, or null if there's no owner
416 * @return string|null
418 public function getGroup()
425 * @see \Sabre\DAV\Node::getLastModified()
427 public function getLastModified()
431 foreach ($this->getChildren() as $child) {
432 $lastModified = $child->getLastModified() > $lastModified ? $child->getLastModified() : $lastModified;
435 return $lastModified;
439 * Returns a list of ACE's for this node.
441 * Each ACE has the following properties:
442 * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
443 * currently the only supported privileges
444 * * 'principal', a url to the principal who owns the node
445 * * 'protected' (optional), indicating that this ACE is not allowed to
448 * @todo implement real logic
451 public function getACL()
453 $principal = 'principals/users/' . Tinebase_Core::getUser()->contact_id;
457 'privilege' => '{DAV:}read',
458 'principal' => $principal,
462 'privilege' => '{DAV:}write',
463 'principal' => $principal,
470 * Returns the name of the node
474 public function getName()
476 if (count($this->_getPathParts()) === 2 &&
477 Tinebase_Helper::array_value(1, $this->_getPathParts()) !== Tinebase_Model_Container::TYPE_SHARED &&
481 $user = $this->_getUser(Tinebase_Helper::array_value(1, $this->_getPathParts()));
483 $name = $this->_useLoginAsFolderName() ? $user->accountLoginName : $user->accountDisplayName;
485 } catch (Tinebase_Exception_NotFound $tenf) {
486 list(,$name) = Sabre\DAV\URLUtil::splitPath($this->_path);
490 list(,$name) = Sabre\DAV\URLUtil::splitPath($this->_path);
497 * Returns the owner principal
499 * This must be a url to a principal, or null if there's no owner
501 * @return string|null
503 public function getOwner()
505 if (count($this->_getPathParts()) === 2 && $this->getName() !== Tinebase_Model_Container::TYPE_SHARED) {
507 $user = $this->_getUser(Tinebase_Helper::array_value(1, $this->_getPathParts()));
508 } catch (Tinebase_Exception_NotFound $tenf) {
512 return 'principals/users/' . $user->contact_id;
519 * Returns the list of properties
521 * @param array $requestedProperties
524 public function getProperties($requestedProperties)
526 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
527 __METHOD__ . '::' . __LINE__ . ' path: ' . $this->_path . ' ' . print_r($requestedProperties, true));
531 foreach ($requestedProperties as $property) {
533 case '{DAV:}displayname':
534 if (count($this->_getPathParts()) === 2 && $this->getName() !== Tinebase_Model_Container::TYPE_SHARED) {
536 $user = $this->_getUser(Tinebase_Helper::array_value(1, $this->_getPathParts()));
537 $contact = Addressbook_Controller_Contact::getInstance()->get($user->contact_id);
538 } catch (Tinebase_Exception_NotFound $tenf) {
542 $response[$property] = $contact->n_fileas;
548 if ($this->getOwner()) {
549 $response[$property] = new \Sabre\DAVACL\Property\Principal(
550 \Sabre\DAVACL\Property\Principal::HREF, $this->getOwner()
556 case '{DAV:}getetag':
557 $response[$property] = $this->getETag();
563 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
564 __METHOD__ . '::' . __LINE__ . ' path: ' . $this->_path . ' ' . print_r($response, true));
572 * This method will receive a list of new ACE's.
577 public function setACL(array $acl)
579 throw new Sabre\DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
583 * Updates properties on this node,
585 * The properties array uses the propertyName in clark-notation as key,
586 * and the array value for the property value. In the case a property
587 * should be deleted, the property value will be null.
589 * This method must be atomic. If one property cannot be changed, the
590 * entire operation must fail.
592 * If the operation was successful, true can be returned.
593 * If the operation failed, false can be returned.
595 * Deletion of a non-existant property is always succesful.
597 * Lastly, it is optional to return detailed information about any
598 * failures. In this case an array should be returned with the following
603 * '{DAV:}displayname' => null,
606 * '{DAV:}owner' => null,
610 * In this example it was forbidden to update {DAV:}displayname.
611 * (403 Forbidden), which in turn also caused {DAV:}owner to fail
612 * (424 Failed Dependency) because the request needs to be atomic.
614 * @param array $mutations
617 public function updateProperties($mutations)
624 foreach ($mutations as $key => $value) {
626 // once iCal tried to set default-alarm config with a negative feedback
627 // it doesn't send default-alarms to the server any longer. So we fake
628 // success here as workaround to let the client send its default alarms
629 case '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}default-alarm-vevent-datetime':
630 case '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}default-alarm-vevent-date':
631 case '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}default-alarm-vtodo-datetime':
632 case '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}default-alarm-vtodo-date':
634 $result['200'][$key] = null;
638 $result['403'][$key] = null;
648 public function getSupportedPrivilegeSet()
654 * return application object
656 * @return Tinebase_Model_Application
658 protected function _getApplication()
660 if (!$this->_application) {
661 $this->_application = Tinebase_Application::getInstance()->getApplicationByName($this->_getApplicationName());
664 return $this->_application;
668 * creates a new container
670 * @todo allow to create personal folders only when in currents users own path
672 * @param array $properties properties for new container
673 * @throws \Sabre\DAV\Exception\Forbidden
674 * @return Tinebase_Model_Container
676 protected function _createContainer(array $properties)
678 if (count($this->_getPathParts()) !== 2) {
679 throw new \Sabre\DAV\Exception\Forbidden('Permission denied to create directory ' . $properties['name']);
682 $containerType = Tinebase_Helper::array_value(1, $this->_getPathParts()) == Tinebase_Model_Container::TYPE_SHARED ?
683 Tinebase_Model_Container::TYPE_SHARED :
684 Tinebase_Model_Container::TYPE_PERSONAL;
686 $newContainer = new Tinebase_Model_Container(array_merge($properties, array(
687 'type' => $containerType,
689 'application_id' => $this->_getApplication()->getId(),
690 'model' => Tinebase_Core::getApplicationInstance($this->_applicationName)->getDefaultModel()
694 $container = Tinebase_Container::getInstance()->addContainer($newContainer);
695 } catch (Tinebase_Exception_AccessDenied $tead) {
696 throw new \Sabre\DAV\Exception\Forbidden('Permission denied to create directory ' . $properties['name']);
703 * return application name
707 protected function _getApplicationName()
709 if (!$this->_applicationName) {
710 $this->_applicationName = Tinebase_Helper::array_value(0, explode('_', get_class($this)));
713 return $this->_applicationName;
721 protected function _getPathParts()
723 if (!$this->_pathParts) {
724 $this->_pathParts = $this->_parsePath($this->_path);
727 return $this->_pathParts;
731 * split path into parts
733 * @param string $_path
736 protected function _parsePath($_path)
738 $pathParts = explode('/', trim($this->_path, '/'));
743 protected function _getUser($_id)
745 $classCacheId = ($this->_useIdAsName ? 'contact_id' : $this->_useLoginAsFolderName() ? 'accountLoginName' : 'accountDisplayName') . $_id;
747 if (isset(self::$_classCache[__FUNCTION__][$classCacheId])) {
748 return self::$_classCache[__FUNCTION__][$classCacheId];
751 if ($this->_useIdAsName) {
752 $contact = Addressbook_Controller_Contact::getInstance()->get($_id);
753 $user = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountId', $contact->account_id, 'Tinebase_Model_FullUser');
755 if ($this->_useLoginAsFolderName()) {
756 $user = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountLoginName', $_id, 'Tinebase_Model_FullUser');
758 $user = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountDisplayName', $_id, 'Tinebase_Model_FullUser');
762 self::$_classCache[__FUNCTION__][$classCacheId] = $user;