Merge branch '2013.10' into 2014.11
[tine20] / tine20 / Tinebase / WebDav / PrincipalBackend.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  * @copyright   Copyright (c) 2011-2014 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Lars Kneschke <l.kneschke@metaways.de>
10  */
11
12 /**
13  * principal backend class
14  * 
15  * @package     Tinebase
16  * @subpackage  WebDav
17  */
18 class Tinebase_WebDav_PrincipalBackend implements \Sabre\DAVACL\PrincipalBackend\BackendInterface
19 {
20     const PREFIX_USERS  = 'principals/users';
21     const PREFIX_GROUPS = 'principals/groups';
22     const SHARED        = 'shared';
23     
24     /**
25      * (non-PHPdoc)
26      * @see Sabre\DAVACL\IPrincipalBackend::getPrincipalsByPrefix()
27      */
28     public function getPrincipalsByPrefix($prefixPath) 
29     {
30         $principals = array();
31         
32         switch ($prefixPath) {
33             case self::PREFIX_GROUPS:
34                 $filter = new Addressbook_Model_ListFilter(array(
35                     array(
36                         'field'     => 'type',
37                         'operator'  => 'equals',
38                         'value'     => Addressbook_Model_List::LISTTYPE_GROUP
39                     )
40                 ));
41                 
42                 $lists = Addressbook_Controller_List::getInstance()->search($filter);
43                 
44                 foreach ($lists as $list) {
45                     $principals[] = $this->_listToPrincipal($list);
46                 }
47                 
48                 break;
49                 
50             case self::PREFIX_USERS:
51                 $filter = $this->_getContactFilterForUserContact();
52                 
53                 $contacts = Addressbook_Controller_Contact::getInstance()->search($filter);
54                 
55                 foreach ($contacts as $contact) {
56                     $principals[] = $this->_contactToPrincipal($contact);
57                 }
58                 
59                 $principals[] = $this->_contactForSharedPrincipal();
60                 
61                 break;
62         }
63         
64         return $principals;
65     }
66     
67     /**
68      * (non-PHPdoc)
69      * @see Sabre\DAVACL\IPrincipalBackend::getPrincipalByPath()
70      * @todo resolve real $path
71      */
72     public function getPrincipalByPath($path) 
73     {
74         // any user has to lookup the data at least once
75         $cacheId = Tinebase_Helper::convertCacheId('getPrincipalByPath' . Tinebase_Core::getUser()->getId() . $path);
76         
77         $principal = Tinebase_Core::getCache()->load($cacheId);
78         if ($principal !== false) {
79             return $principal;
80         }
81         
82         $principal = null;
83         
84         list($prefix, $id) = \Sabre\DAV\URLUtil::splitPath($path);
85         
86         // special handling for calendar proxy principals
87         // they are groups in the user namespace
88         if (in_array($id, array('calendar-proxy-read', 'calendar-proxy-write'))) {
89             $path = $prefix;
90             
91             // set prefix to calendar-proxy-read or calendar-proxy-write
92             $prefix = $id;
93             
94             list(, $id) = \Sabre\DAV\URLUtil::splitPath($path);
95         }
96         
97         switch ($prefix) {
98             case 'calendar-proxy-read':
99                 return null;
100                 
101                 break;
102                 
103             case 'calendar-proxy-write':
104                 // does the account exist
105                 $contactPrincipal = $this->getPrincipalByPath(self::PREFIX_USERS . '/' . $id);
106                 
107                 if (! $contactPrincipal) {
108                     if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(
109                             __METHOD__ . '::' . __LINE__ . ' Account principal does not exist: ' . $id);
110                     return null;
111                 }
112                 
113                 $principal = array(
114                     'uri'                     => $contactPrincipal['uri'] . '/' . $prefix,
115                     '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}calendar-user-type'  => 'GROUP',
116                     '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}record-type' => 'groups'
117                 );
118                 
119                 break;
120                 
121             case self::PREFIX_GROUPS:
122                 $filter = new Addressbook_Model_ListFilter(array(
123                     array(
124                         'field'     => 'type',
125                         'operator'  => 'equals',
126                         'value'     => Addressbook_Model_List::LISTTYPE_GROUP
127                     ),
128                     array(
129                         'field'     => 'id',
130                         'operator'  => 'equals',
131                         'value'     => $id
132                     ),
133                 ));
134                 
135                 $list = Addressbook_Controller_List::getInstance()->search($filter)->getFirstRecord();
136                 
137                 if (! $list) {
138                     if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(
139                             __METHOD__ . '::' . __LINE__ . ' Group/list principal does not exist: ' . $id);
140                     return null;
141                 }
142                 
143                 $principal = $this->_listToPrincipal($list);
144                 
145                 break;
146                 
147             case self::PREFIX_USERS:
148                 if ($id === self::SHARED) {
149                     $principal = $this->_contactForSharedPrincipal();
150                     
151                 } else {
152                     $filter = $this->_getContactFilterForUserContact($id);
153                     
154                     $contact = Addressbook_Controller_Contact::getInstance()->search($filter)->getFirstRecord();
155                     
156                     if (! $contact) {
157                         if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(
158                             __METHOD__ . '::' . __LINE__ . ' Contact principal does not exist: ' . $id);
159                         return null;
160                     }
161                     
162                     $principal = $this->_contactToPrincipal($contact);
163                 }
164                 
165                 break;
166         }
167         
168         Tinebase_Core::getCache()->save($principal, $cacheId, array(), /* 1 minute */ 60);
169         
170         return $principal;
171     }
172     
173     /**
174      * get contact filter
175      * 
176      * @param string $id
177      * @return Addressbook_Model_ContactFilter
178      */
179     protected function _getContactFilterForUserContact($id = null)
180     {
181         $filterData = array(array(
182             'field'     => 'type',
183             'operator'  => 'equals',
184             'value'     => Addressbook_Model_Contact::CONTACTTYPE_USER
185         ));
186         
187         if ($id !== null) {
188             $filterData[] = array(
189                 'field'     => 'id',
190                 'operator'  => 'equals',
191                 'value'     => $id
192             );
193         }
194         
195         return new Addressbook_Model_ContactFilter($filterData);
196     }
197     
198     /**
199      * (non-PHPdoc)
200      * @see Sabre\DAVACL\IPrincipalBackend::getGroupMemberSet()
201      */
202     public function getGroupMemberSet($principal) 
203     {
204         $result = array();
205         
206         list($prefix, $id) = \Sabre\DAV\URLUtil::splitPath($principal);
207         
208         // special handling for calendar proxy principals
209         // they are groups in the user namespace
210         if (in_array($id, array('calendar-proxy-read', 'calendar-proxy-write'))) {
211             $path = $prefix;
212             
213             // set prefix to calendar-proxy-read or calendar-proxy-write
214             $prefix = $id;
215             
216             list(, $id) = \Sabre\DAV\URLUtil::splitPath($path);
217         }
218         
219         switch ($prefix) {
220             case 'calendar-proxy-read':
221                 return array();
222                 
223                 break;
224                 
225             case 'calendar-proxy-write':
226                 $result = array();
227                 
228                 $applications = array(
229                     'Calendar' => 'Calendar_Model_Event',
230                     'Tasks'    => 'Tasks_Model_Task'
231                 );
232                 
233                 foreach ($applications as $application => $model) {
234                     if ($id === self::SHARED) {
235                         // check if account has the right to run the calendar at all
236                         if (!Tinebase_Acl_Roles::getInstance()->hasRight($application, Tinebase_Core::getUser(), Tinebase_Acl_Rights::RUN)) {
237                             continue;
238                         }
239                         
240                         // collect all users which have access to any of the calendars of this user
241                         $sharedContainerSync = Tinebase_Container::getInstance()->getSharedContainer(Tinebase_Core::getUser(), $model, Tinebase_Model_Grants::GRANT_SYNC);
242                         
243                         if ($sharedContainerSync->count() > 0) {
244                             $sharedContainerRead = Tinebase_Container::getInstance()->getSharedContainer(Tinebase_Core::getUser(), $model, Tinebase_Model_Grants::GRANT_READ);
245                             
246                             $sharedContainerIds = array_intersect($sharedContainerSync->getArrayOfIds(), $sharedContainerRead->getArrayOfIds());
247                             
248                             $result = array_merge(
249                                 $result,
250                                 $this->_containerGrantsToPrincipals($sharedContainerSync->filter('id', $sharedContainerIds)));
251                         }
252                     } else {
253                         $filter = $this->_getContactFilterForUserContact($id);
254                         
255                         $contact = Addressbook_Controller_Contact::getInstance()->search($filter)->getFirstRecord();
256                         
257                         if (!$contact instanceof Addressbook_Model_Contact || !$contact->account_id) {
258                             continue;
259                         }
260                         
261                         // check if account has the right to run the calendar at all
262                         if (!Tinebase_Acl_Roles::getInstance()->hasRight($application, $contact->account_id, Tinebase_Acl_Rights::RUN)) {
263                             continue;
264                         }
265                         
266                         // collect all users which have access to any of the calendars of this user
267                         $personalContainerSync = Tinebase_Container::getInstance()->getPersonalContainer(Tinebase_Core::getUser(), $model, $contact->account_id, Tinebase_Model_Grants::GRANT_SYNC);
268                         
269                         if ($personalContainerSync->count() > 0) {
270                             $personalContainerRead = Tinebase_Container::getInstance()->getPersonalContainer(Tinebase_Core::getUser(), $model, $contact->account_id, Tinebase_Model_Grants::GRANT_READ);
271                             
272                             $personalContainerIds = array_intersect($personalContainerSync->getArrayOfIds(), $personalContainerRead->getArrayOfIds());
273                             
274                             $result = array_merge(
275                                 $result,
276                                 $this->_containerGrantsToPrincipals($personalContainerSync->filter('id', $personalContainerIds))
277                             );
278                         }
279                     }
280                 }
281                 
282                 break;
283                 
284             case self::PREFIX_GROUPS:
285                 $filter = new Addressbook_Model_ListFilter(array(
286                     array(
287                         'field'     => 'type',
288                         'operator'  => 'equals',
289                         'value'     => Addressbook_Model_List::LISTTYPE_GROUP
290                     ),
291                     array(
292                         'field'     => 'id',
293                         'operator'  => 'equals',
294                         'value'     => $id
295                     ),
296                 ));
297                 
298                 $list = Addressbook_Controller_List::getInstance()->search($filter)->getFirstRecord();
299                 
300                 if (!$list) {
301                     return array();
302                 }
303                 
304                 foreach ($list->members as $member) {
305                     $result[] = self::PREFIX_USERS . '/' . $member;
306                 }
307                 
308                 break;
309         }
310         
311         return $result;
312     }
313     
314     /**
315      * (non-PHPdoc)
316      * @see Sabre\DAVACL\IPrincipalBackend::getGroupMembership()
317      */
318     public function getGroupMembership($principal)
319     {
320         $result = array();
321         
322         list($prefix, $contactId) = \Sabre\DAV\URLUtil::splitPath($principal);
323         
324         switch ($prefix) {
325             case self::PREFIX_GROUPS:
326                 // @TODO implement?
327                 break;
328         
329             case self::PREFIX_USERS:
330                 if ($contactId !== self::SHARED) {
331                     $classCacheId = $principal;
332                     
333                     try {
334                         return Tinebase_Cache_PerRequest::getInstance()->load(__CLASS__, __FUNCTION__, $classCacheId);
335                     } catch (Tinebase_Exception_NotFound $tenf) {
336                         // continue...
337                     }
338                     
339                     $cacheId = __FUNCTION__ . sha1($classCacheId);
340                     
341                     // try to load from cache
342                     $cache  = Tinebase_Core::getCache();
343                     $result = $cache->load($cacheId);
344                     
345                     if ($result !== FALSE) {
346                         Tinebase_Cache_PerRequest::getInstance()->save(__CLASS__, __FUNCTION__, $classCacheId, $result);
347                         
348                         return $result;
349                     }
350                     $result = array();
351                     
352                     $user = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('contactId', $contactId);
353                     
354                     $groupIds = Tinebase_Group::getInstance()->getGroupMemberships($user);
355                     $groups   = Tinebase_Group::getInstance()->getMultiple($groupIds);
356                     
357                     foreach ($groups as $group) {
358                         if ($group->list_id && $group->visibility == Tinebase_Model_Group::VISIBILITY_DISPLAYED) {
359                             $result[] = self::PREFIX_GROUPS . '/' . $group->list_id;
360                         }
361                     }
362                     
363                     if (Tinebase_Core::getUser()->hasRight('Calendar', Tinebase_Acl_Rights::RUN)) {
364                         // return user only, if the containers have the sync AND read grant set
365                         $otherUsersSync = Tinebase_Container::getInstance()->getOtherUsers($user, 'Calendar', Tinebase_Model_Grants::GRANT_SYNC);
366                         
367                         if ($otherUsersSync->count() > 0) {
368                             $otherUsersRead = Tinebase_Container::getInstance()->getOtherUsers($user, 'Calendar', Tinebase_Model_Grants::GRANT_READ);
369                             
370                             $otherUsersIds = array_intersect($otherUsersSync->getArrayOfIds(), $otherUsersRead->getArrayOfIds());
371                             
372                             foreach ($otherUsersIds as $userId) {
373                                 if ($otherUsersSync->getById($userId)->contact_id && $otherUsersSync->getById($userId)->visibility == Tinebase_Model_User::VISIBILITY_DISPLAYED) {
374                                     $result[] = self::PREFIX_USERS . '/' . $otherUsersSync->getById($userId)->contact_id . '/calendar-proxy-write';
375                                 }
376                             }
377                         }
378                         
379                         // return user only, if the containers have the sync AND read grant set
380                         $sharedContainersSync = Tinebase_Container::getInstance()->getSharedContainer($user, 'Calendar', Tinebase_Model_Grants::GRANT_SYNC);
381                         
382                         if ($sharedContainersSync->count() > 0) {
383                             $sharedContainersRead = Tinebase_Container::getInstance()->getSharedContainer($user, 'Calendar', Tinebase_Model_Grants::GRANT_READ);
384                             
385                             $sharedContainerIds = array_intersect($sharedContainersSync->getArrayOfIds(), $sharedContainersRead->getArrayOfIds());
386                             
387                             if (count($sharedContainerIds) > 0) {
388                                 $result[] = self::PREFIX_USERS . '/' . self::SHARED . '/calendar-proxy-write';
389                             }
390                         }
391                     }
392                     Tinebase_Cache_PerRequest::getInstance()->save(__CLASS__, __FUNCTION__, $classCacheId, $result);
393                     $cache->save($result, $cacheId, array(), 60 * 3);
394                 }
395                 
396                 break;
397         }
398         
399         return $result;
400     }
401     
402     public function setGroupMemberSet($principal, array $members) 
403     {
404         // do nothing
405     }
406     
407     public function updatePrincipal($path, $mutations)
408     {
409         return false;
410     }
411     
412     /**
413      * This method is used to search for principals matching a set of
414      * properties.
415      *
416      * This search is specifically used by RFC3744's principal-property-search
417      * REPORT. You should at least allow searching on
418      * http://sabredav.org/ns}email-address.
419      *
420      * The actual search should be a unicode-non-case-sensitive search. The
421      * keys in searchProperties are the WebDAV property names, while the values
422      * are the property values to search on.
423      *
424      * If multiple properties are being searched on, the search should be
425      * AND'ed.
426      *
427      * This method should simply return an array with full principal uri's.
428      *
429      * If somebody attempted to search on a property the backend does not
430      * support, you should simply return 0 results.
431      *
432      * You can also just return 0 results if you choose to not support
433      * searching at all, but keep in mind that this may stop certain features
434      * from working.
435      *
436      * @param string $prefixPath
437      * @param array $searchProperties
438      * @todo implement handling for shared pseudo user
439      * @return array
440      */
441     public function searchPrincipals($prefixPath, array $searchProperties)
442     {
443         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
444             __METHOD__ . '::' . __LINE__ . ' path: ' . $prefixPath . ' properties: ' . print_r($searchProperties, true));
445         
446         $principalUris = array();
447         
448         switch ($prefixPath) {
449             case self::PREFIX_GROUPS:
450                 $filter = new Addressbook_Model_ListFilter(array(
451                     array(
452                         'field'     => 'type',
453                         'operator'  => 'equals',
454                         'value'     => Addressbook_Model_List::LISTTYPE_GROUP
455                     )
456                 ));
457                 
458                 if (!empty($searchProperties['{http://calendarserver.org/ns/}search-token'])) {
459                     $filter->addFilter($filter->createFilter(array(
460                         'field'     => 'query',
461                         'operator'  => 'contains',
462                         'value'     => $searchProperties['{http://calendarserver.org/ns/}search-token']
463                     )));
464                 }
465                 
466                 if (!empty($searchProperties['{DAV:}displayname'])) {
467                     $filter->addFilter($filter->createFilter(array(
468                         'field'     => 'name',
469                         'operator'  => 'contains',
470                         'value'     => $searchProperties['{DAV:}displayname']
471                     )));
472                 }
473                 
474                 $result = Addressbook_Controller_List::getInstance()->search($filter, null, false, true);
475                 
476                 foreach ($result as $listId) {
477                     $principalUris[] = $prefixPath . '/' . $listId;
478                 }
479                 
480                 break;
481                 
482             case self::PREFIX_USERS:
483                 $filter = $this->_getContactFilterForUserContact();
484                 
485                 if (!empty($searchProperties['{http://calendarserver.org/ns/}search-token'])) {
486                     $filter->addFilter($filter->createFilter(array(
487                         'field'     => 'query',
488                         'operator'  => 'contains',
489                         'value'     => $searchProperties['{http://calendarserver.org/ns/}search-token']
490                     )));
491                 }
492                 
493                 if (!empty($searchProperties['{http://sabredav.org/ns}email-address'])) {
494                     $filter->addFilter($filter->createFilter(array(
495                         'field'     => 'email_query',
496                         'operator'  => 'contains',
497                         'value'     => $searchProperties['{http://sabredav.org/ns}email-address']
498                     )));
499                 }
500                 
501                 if (!empty($searchProperties['{DAV:}displayname'])) {
502                     $filter->addFilter($filter->createFilter(array(
503                         'field'     => 'query',
504                         'operator'  => 'contains',
505                         'value'     => $searchProperties['{DAV:}displayname']
506                     )));
507                 }
508                 
509                 if (!empty($searchProperties['{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}first-name'])) {
510                     $filter->addFilter($filter->createFilter(array(
511                         'field'     => 'n_given',
512                         'operator'  => 'contains',
513                         'value'     => $searchProperties['{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}first-name']
514                     )));
515                 }
516                 
517                 if (!empty($searchProperties['{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}last-name'])) {
518                     $filter->addFilter($filter->createFilter(array(
519                         'field'     => 'n_family',
520                         'operator'  => 'contains',
521                         'value'     => $searchProperties['{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}last-name']
522                     )));
523                 }
524                 
525                 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ .
526                     ' path: ' . $prefixPath . ' properties: ' . print_r($filter->toArray(), true));
527                 
528                 $result = Addressbook_Controller_Contact::getInstance()->search($filter, null, false, true);
529                 
530                 foreach ($result as $contactId) {
531                     $principalUris[] = $prefixPath . '/' . $contactId;
532                 }
533                 
534                 break;
535         }
536         
537         return $principalUris;
538     }
539     
540     /**
541      * return shared pseudo principal (principal for the shared containers) 
542      */
543     protected function _contactForSharedPrincipal()
544     {
545         $translate = Tinebase_Translation::getTranslation('Tinebase');
546         
547         $principal = array(
548             'uri'                     => self::PREFIX_USERS . '/' . self::SHARED,
549             '{DAV:}displayname'       => $translate->_('Shared folders'),
550             
551             '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}calendar-user-type'  => 'INDIVIDUAL',
552             
553             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}record-type' => 'users',
554             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}first-name'  => 'Folders',
555             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}last-name'   => 'Shared'
556         );
557         
558         return $principal;
559         
560     }
561     
562     /**
563      * convert contact model to principal array
564      * 
565      * @param Addressbook_Model_Contact $contact
566      * @return array
567      */
568     protected function _contactToPrincipal(Addressbook_Model_Contact $contact)
569     {
570         $principal = array(
571             'uri'                     => self::PREFIX_USERS . '/' . $contact->getId(),
572             '{DAV:}displayname'       => $contact->n_fileas,
573             '{DAV:}alternate-URI-set' => array('urn:uuid:' . $contact->getId()),
574             
575             '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}calendar-user-type'  => 'INDIVIDUAL',
576             
577             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}record-type' => 'users',
578             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}first-name'  => $contact->n_given,
579             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}last-name'   => $contact->n_family
580         );
581         
582         if (!empty(Tinebase_Core::getUser()->accountEmailAddress)) {
583             $principal['{http://sabredav.org/ns}email-address'] = $contact->email;
584         }
585         
586         return $principal;
587     }
588     
589     /**
590      * convert container grants to principals 
591      * 
592      * @param Tinebase_Record_RecordSet $containers
593      * @return array
594      * 
595      * @todo improve algorithm to fetch all contact/list_ids at once
596      */
597     protected function _containerGrantsToPrincipals(Tinebase_Record_RecordSet $containers)
598     {
599         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
600                 __METHOD__ . '::' . __LINE__ . ' Converting grants to principals for ' . count($containers) . ' containers.');
601         
602         $result = array();
603         
604         foreach ($containers as $container) {
605             $cacheId = Tinebase_Helper::convertCacheId('_containerGrantsToPrincipals' . $container->getId() . $container->seq);
606             
607             $containerPrincipals = Tinebase_Core::getCache()->load($cacheId);
608             
609             if ($containerPrincipals === false) {
610                 $containerPrincipals = array();
611                 
612                 $grants = Tinebase_Container::getInstance()->getGrantsOfContainer($container);
613                 
614                 foreach ($grants as $grant) {
615                     switch ($grant->account_type) {
616                         case 'group':
617                             $group = Tinebase_Group::getInstance()->getGroupById($grant->account_id);
618                             if ($group->list_id) {
619                                 $containerPrincipals[] = self::PREFIX_GROUPS . '/' . $group->list_id;
620                             }
621                             break;
622                             
623                         case 'user':
624                             // skip if grant belongs to the owner of the calendar
625                             if ($contact->account_id == $grant->account_id) {
626                                 continue;
627                             }
628                             $user = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountId', $grant->account_id);
629                             if ($user->contact_id) {
630                                 $containerPrincipals[] = self::PREFIX_USERS . '/' . $user->contact_id;
631                             }
632                             
633                             break;
634                     }
635                 }
636                 
637                 Tinebase_Core::getCache()->save($containerPrincipals, $cacheId, array(), /* 1 day */ 24 * 60 * 60);
638             }
639             
640             $result = array_merge($result, $containerPrincipals);
641         }
642         
643         // users and groups can be duplicate
644         $result = array_unique($result);
645         
646         return $result;
647     }
648     
649     /**
650      * convert list model to principal array
651      * 
652      * @param Addressbook_Model_List $list
653      * @return array
654      */
655     protected function _listToPrincipal(Addressbook_Model_List $list)
656     {
657         $principal = array(
658             'uri'                     => self::PREFIX_GROUPS . '/' . $list->getId(),
659             '{DAV:}displayname'       => $list->name . ' (Group)',
660             '{DAV:}alternate-URI-set' => array('urn:uuid:' . $list->getId()),
661             
662             '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}calendar-user-type'  => 'GROUP',
663             
664             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}record-type' => 'groups',
665             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}first-name'  => 'Group',
666             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}last-name'   => $list->name,
667         );
668         
669         return $principal;
670     }
671 }