e57c45ea3132ea26182d7e8e224e62c155391088
[tine20] / tine20 / Tinebase / WebDav / Container / Abstract.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Tinebase
6  * @subpackage  WebDAV
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Lars Kneschke <l.kneschke@metaways.de>
9  * @copyright   Copyright (c) 2011-2014 Metaways Infosystems GmbH (http://www.metaways.de)
10  *
11  */
12
13 /**
14  * abstract class to handle containers in Cal/CardDAV tree
15  *
16  * @package     Tinebase
17  * @subpackage  WebDAV
18  */
19 abstract class Tinebase_WebDav_Container_Abstract extends \Sabre\DAV\Collection implements \Sabre\DAV\IProperties, \Sabre\DAVACL\IACL
20 {
21     /**
22      * the current application object
23      * 
24      * @var Tinebase_Model_Application
25      */
26     protected $_application;
27     
28     protected $_applicationName;
29     
30     protected $_container;
31     
32     protected $_controller;
33     
34     protected $_model;
35     
36     protected $_suffix;
37     
38     protected $_useIdAsName;
39     
40     /**
41      * contructor
42      * 
43      * @param  string|Tinebase_Model_Application  $_application  the current application
44      * @param  string                             $_container    the current path
45      */
46     public function __construct(Tinebase_Model_Container $_container, $_useIdAsName = false)
47     {
48         $this->_application = Tinebase_Application::getInstance()->getApplicationByName($this->_applicationName);
49         $this->_container   = $_container;
50         $this->_useIdAsName = (boolean)$_useIdAsName;
51     }
52     
53     /**
54      * Creates a new file
55      *
56      * The contents of the new file must be a valid VCARD
57      *
58      * @param  string    $name
59      * @param  resource  $vcardData
60      * @return string    the etag of the record
61      */
62     public function createFile($name, $vobjectData = null) 
63     {
64         $objectClass = $this->_application->name . '_Frontend_WebDAV_' . $this->_model;
65
66         $object = $objectClass::create($this->_container, $name, $vobjectData);
67         
68         return $object->getETag();
69     }
70     
71     /**
72      * (non-PHPdoc)
73      * @see \Sabre\DAV\Node::delete()
74      */
75     public function delete()
76     {
77         try {
78             Tinebase_Container::getInstance()->deleteContainer($this->_container);
79         } catch (Tinebase_Exception_AccessDenied $tead) {
80             throw new Sabre\DAV\Exception\Forbidden('Permission denied to delete node');
81         } catch (Tinebase_Exception_Record_SystemContainer $ters) {
82             throw new Sabre\DAV\Exception\Forbidden('Permission denied to delete system container');
83         } catch (Exception $e) {
84             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE))
85                 Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' failed to delete container ' .$this->_container->getId() . "\n$e" );
86
87             throw new \Sabre\DAV\Exception($e->getMessage());
88         }
89     }
90     
91     /**
92      * (non-PHPdoc)
93      * @see Sabre\DAV\Collection::getChild()
94      */
95     public function getChild($_name)
96     {
97         $modelName = $this->_application->name . '_Model_' . $this->_model;
98         
99         if ($_name instanceof $modelName) {
100             $object = $_name;
101         } else {
102             $filterClass = $this->_application->name . '_Model_' . $this->_model . 'Filter';
103             $filter = new $filterClass(array(
104                 array(
105                     'field'     => 'container_id',
106                     'operator'  => 'equals',
107                     'value'     => $this->_container->getId()
108                 ),
109                 array(
110                     'field'     => 'id',
111                     'operator'  => 'equals',
112                     'value'     => $this->_getIdFromName($_name)
113                 )
114             ));
115             $object = $this->_getController()->search($filter, null, false, false, 'sync')->getFirstRecord();
116             
117             if ($object == null) {
118                 throw new Sabre\DAV\Exception\NotFound('Object not found');
119             }
120         }
121         
122         if ($object->has('tags') && !isset($object->tags)) {
123             Tinebase_Tags::getInstance()->getTagsOfRecord($object);
124         }
125         
126         $objectClass = $this->_application->name . '_Frontend_WebDAV_' . $this->_model;
127         
128         return new $objectClass($this->_container, $object);
129     }
130     
131     /**
132      * Returns an array with all the child nodes
133      *
134      * @return Sabre\DAV\INode[]
135      */
136     function getChildren()
137     {
138         $filterClass = $this->_application->name . '_Model_' . $this->_model . 'Filter';
139         $filter = new $filterClass(array(
140             array(
141                 'field'     => 'container_id',
142                 'operator'  => 'equals',
143                 'value'     => $this->_container->getId()
144             )
145         ));
146
147         /*
148          * see http://forge.tine20.org/mantisbt/view.php?id=5122
149          * we must use action 'sync' and not 'get' as 
150          * otherwise the calendar also return events the user only can see because of freebusy
151          */        
152         $objects = $this->_getController()->search($filter, null, false, false, 'sync');
153         
154         $children = array();
155         
156         foreach ($objects as $object) {
157             $children[] = $this->getChild($object);
158         }
159
160         return $children;
161     }
162     
163     /**
164      * return etag
165      * 
166      * @return string
167      */
168     public function getETag()
169     {
170         return '"' . $this->_container->seq . '"';
171     }
172     
173     /**
174      * Returns a group principal
175      *
176      * This must be a url to a principal, or null if there's no owner
177      *
178      * @return string|null
179      */
180     public function getGroup()
181     {
182         return null;
183     }
184     
185     /**
186      * Returns a list of ACE's for this node.
187      *
188      * Each ACE has the following properties:
189      *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
190      *     currently the only supported privileges
191      *   * 'principal', a url to the principal who owns the node
192      *   * 'protected' (optional), indicating that this ACE is not allowed to
193      *      be updated.
194      *      
195      * @todo implement real logic
196      * @return array
197      */
198     public function getACL() 
199     {
200         $acl    = array();
201         
202         $grants = Tinebase_Container::getInstance()->getGrantsOfContainer($this->_container, true);
203         
204         foreach ($grants as $grant) {
205             switch ($grant->account_type) {
206                 case Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE:
207                     $principal = 'principals/users/' . Tinebase_Core::getUser()->contact_id;
208                     break;
209                     
210                 case Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP:
211                     try {
212                         $group = Tinebase_Group::getInstance()->getGroupById($grant->account_id);
213                     } catch (Tinebase_Exception_Record_NotDefined $ternd) {
214                         // skip group
215                         continue 2;
216                     } catch (Tinebase_Exception_NotFound $tenf) {
217                         // skip group
218                         continue 2;
219                     }
220                     
221                     $principal = 'principals/groups/' . $group->list_id;
222                     
223                     break;
224                     
225                 case Tinebase_Acl_Rights::ACCOUNT_TYPE_USER:
226                     try {
227                         $fulluser = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountId', $grant->account_id, 'Tinebase_Model_FullUser');
228                     } catch (Tinebase_Exception_Record_NotDefined $ternd) {
229                         // skip group
230                         continue 2;
231                     } catch (Tinebase_Exception_NotFound $tenf) {
232                         // skip user
233                         continue 2;
234                     }
235                     
236                     $principal = 'principals/users/' . $fulluser->contact_id;
237                     
238                     break;
239                     
240                 default:
241                     throw new Tinebase_Exception_UnexpectedValue('unsupported account type');
242             }
243             
244             if($grant[Tinebase_Model_Grants::GRANT_READ] == true) {
245                 $acl[] = array(
246                     'privilege' => '{DAV:}read',
247                     'principal' => $principal,
248                     'protected' => true,
249                 );
250             }
251             if($grant[Tinebase_Model_Grants::GRANT_EDIT] == true) {
252                 $acl[] = array(
253                     'privilege' => '{DAV:}write-content',
254                     'principal' => $principal,
255                     'protected' => true,
256                 );
257             }
258             if($grant[Tinebase_Model_Grants::GRANT_ADD] == true) {
259                 $acl[] = array(
260                     'privilege' => '{DAV:}bind',
261                     'principal' => $principal,
262                     'protected' => true,
263                 );
264             }
265             if($grant[Tinebase_Model_Grants::GRANT_DELETE] == true) {
266                 $acl[] = array(
267                     'privilege' => '{DAV:}unbind',
268                     'principal' => $principal,
269                     'protected' => true,
270                 );
271             }
272             if($grant[Tinebase_Model_Grants::GRANT_ADMIN] == true) {
273                 $acl[] = array(
274                     'privilege' => '{DAV:}write-properties',
275                     'principal' => $principal,
276                     'protected' => true,
277                 );
278             }
279         }
280
281         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) 
282             Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' webdav acl ' . print_r($acl, true));
283         
284         return $acl;
285     }
286     
287     /**
288      * Returns the last modification date as a unix timestamp
289      *
290      * @return time
291      */
292     public function getLastModified() 
293     {
294         if ($this->_container->last_modified_time instanceof Tinebase_DateTime) {
295             return $this->_container->last_modified_time->getTimestamp();
296         }
297         
298         if ($this->_container->creation_time instanceof Tinebase_DateTime) {
299             return $this->_container->creation_time->getTimestamp();
300         }
301         
302         return Tinebase_DateTime::now()->getTimestamp();
303     }
304     
305     /**
306      * Returns the name of the node
307      *
308      * @return string
309      */
310     public function getName()
311     {
312         if ($this->_useIdAsName == true) {
313             if ($this->_container->uuid) {
314                 return $this->_container->uuid;
315             } else {
316                 return $this->_container->getId();
317             }
318         } 
319         
320         return $this->_container->name;
321     }
322     
323     /**
324      * Returns the owner principal
325      *
326      * This must be a url to a principal, or null if there's no owner
327      * 
328      * @todo implement real logic
329      * @return string|null
330      */
331     public function getOwner()
332     {
333         if (! Tinebase_Container::getInstance()->hasGrant(
334             Tinebase_Core::getUser(), 
335             $this->_container, 
336             Tinebase_Model_Grants::GRANT_ADMIN)
337         ) {
338             return null;
339         }
340         
341         return 'principals/users/' . Tinebase_Core::getUser()->contact_id;
342     }
343     
344     /**
345      * Returns the list of properties
346      *
347      * @param array $requestedProperties
348      * @return array
349      */
350     public function getProperties($requestedProperties) 
351     {
352         $properties = array();
353         
354         $response = array();
355         
356         foreach ($requestedProperties as $prop) {
357             switch($prop) {
358                 case '{DAV:}getetag':
359                     $response[$prop] = $this->getETag();
360                     break;
361                     
362                 default:
363                     if (isset($properties[$prop])) $response[$prop] = $properties[$prop];
364                     break;
365             }
366         }
367         
368         return $response;
369     }
370     
371     /**
372      * Updates the ACL
373      *
374      * This method will receive a list of new ACE's.
375      *
376      * @param array $acl
377      * @return void
378      */
379     public function setACL(array $acl)
380     {
381         throw new Sabre\DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
382     }
383     
384     /**
385      * Updates properties on this node,
386      *
387      * The properties array uses the propertyName in clark-notation as key,
388      * and the array value for the property value. In the case a property
389      * should be deleted, the property value will be null.
390      *
391      * This method must be atomic. If one property cannot be changed, the
392      * entire operation must fail.
393      *
394      * If the operation was successful, true can be returned.
395      * If the operation failed, false can be returned.
396      *
397      * Deletion of a non-existant property is always succesful.
398      *
399      * Lastly, it is optional to return detailed information about any
400      * failures. In this case an array should be returned with the following
401      * structure:
402      *
403      * array(
404      *   403 => array(
405      *      '{DAV:}displayname' => null,
406      *   ),
407      *   424 => array(
408      *      '{DAV:}owner' => null,
409      *   )
410      * )
411      *
412      * In this example it was forbidden to update {DAV:}displayname. 
413      * (403 Forbidden), which in turn also caused {DAV:}owner to fail
414      * (424 Failed Dependency) because the request needs to be atomic.
415      *
416      * @param array $mutations 
417      * @return bool|array 
418      */
419     public function updateProperties($mutations) 
420     {
421         if (!Tinebase_Core::getUser()->hasGrant($this->_container, Tinebase_Model_Grants::GRANT_ADMIN)) {
422             throw new \Sabre\DAV\Exception\Forbidden('permission to update container denied');
423         }
424         
425         $result = array(
426             200 => array(),
427             403 => array()
428         );
429         
430         foreach ($mutations as $key => $value) {
431             switch ($key) {
432                 case '{DAV:}displayname':
433                     if ($value === $this->_container->uuid || $value === $this->_container->getId()) {
434                         if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ 
435                             . ' It is not allowed to overwrite the name with the uuid/id');
436                         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
437                             . ' ' . print_r(array(
438                                 'useIdAsName' => $this->_useIdAsName,
439                                 'container'   => $this->_container->toArray(),
440                                 'new value'   => $value
441                             ), true));
442                     } else {
443                         $this->_container->name = $value;
444                     }
445                     $result['200'][$key] = null;
446                     break;
447                     
448                 case '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}calendar-description':
449                 case '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}calendar-timezone':
450                     // fake success
451                     $result['200'][$key] = null;
452                     break;
453                     
454                 case '{http://apple.com/ns/ical/}calendar-color':
455                     $this->_container->color = substr($value, 0, 7);
456                     $result['200'][$key] = null;
457                     break;
458                 
459                 default:
460                     $result['403'][$key] = null;
461             }
462         }
463         
464         Tinebase_Container::getInstance()->update($this->_container);
465         
466         return $result;
467     }
468     
469     /**
470      * 
471      * @return Tinebase_Controller_Record_Interface
472      */
473     protected function _getController()
474     {
475         if ($this->_controller === null) {
476             $this->_controller = Tinebase_Core::getApplicationInstance($this->_application->name, $this->_model);
477         }
478         
479         return $this->_controller;
480     }
481     
482     /**
483      * get id from name => strip of everything after last dot
484      * 
485      * @param  string  $_name  the name for example vcard.vcf
486      * @return string
487      */
488     protected function _getIdFromName($_name)
489     {
490         $id = ($pos = strrpos($_name, '.')) === false ? $_name : substr($_name, 0, $pos);
491         $id = strlen($id) > 40 ? sha1($id) : $id;
492         
493         return $id;
494     }
495     
496     /**
497      * generate VTimezone for given folder
498      * 
499      * @param  string|Tinebase_Model_Application  $applicationName
500      * @return string
501      */
502     public static function getCalendarVTimezone($applicationName)
503     {
504         $timezone = Tinebase_Core::getPreference()->getValueForUser(Tinebase_Preference::TIMEZONE, Tinebase_Core::getUser()->getId());
505         
506         $application = $applicationName instanceof Tinebase_Model_Application 
507             ? $applicationName 
508             : Tinebase_Application::getInstance()->getApplicationByName($applicationName); 
509         
510         // create vcalendar object with timezone information
511         $vcalendar = new \Sabre\VObject\Component\VCalendar(array(
512             'PRODID'   => "-//tine20.org//Tine 2.0 {$application->name} V{$application->version}//EN",
513             'VERSION'  => '2.0',
514             'CALSCALE' => 'GREGORIAN'
515         ));
516         $vcalendar->add(new Sabre_VObject_Component_VTimezone($timezone));
517         
518         // Taking out \r to not screw up the xml output
519         return str_replace("\r","", $vcalendar->serialize());
520     }
521     
522     /**
523      * 
524      */
525     public function getSupportedPrivilegeSet()
526     {
527         return null;
528     }
529 }