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