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