increase cache lifetime for principal resolving
[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 = 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                     // return user only, if the containers have the sync AND read grant set
364                     $otherUsersSync = Tinebase_Container::getInstance()->getOtherUsers($user, 'Calendar', Tinebase_Model_Grants::GRANT_SYNC);
365                     
366                     if ($otherUsersSync->count() > 0) {
367                         $otherUsersRead = Tinebase_Container::getInstance()->getOtherUsers($user, 'Calendar', Tinebase_Model_Grants::GRANT_READ);
368                         
369                         $otherUsersIds = array_intersect($otherUsersSync->getArrayOfIds(), $otherUsersRead->getArrayOfIds());
370                         
371                         foreach ($otherUsersIds as $userId) {
372                             if ($otherUsersSync->getById($userId)->contact_id && $otherUsersSync->getById($userId)->visibility == Tinebase_Model_User::VISIBILITY_DISPLAYED) {
373                                 $result[] = self::PREFIX_USERS . '/' . $otherUsersSync->getById($userId)->contact_id . '/calendar-proxy-write';
374                             }
375                         }
376                     }
377                     
378                     // return user only, if the containers have the sync AND read grant set
379                     $sharedContainersSync = Tinebase_Container::getInstance()->getSharedContainer($user, 'Calendar', Tinebase_Model_Grants::GRANT_SYNC);
380                     
381                     if ($sharedContainersSync->count() > 0) {
382                         $sharedContainersRead = Tinebase_Container::getInstance()->getSharedContainer($user, 'Calendar', Tinebase_Model_Grants::GRANT_READ);
383                         
384                         $sharedContainerIds = array_intersect($sharedContainersSync->getArrayOfIds(), $sharedContainersRead->getArrayOfIds());
385                         
386                         if (count($sharedContainerIds) > 0) {
387                             $result[] = self::PREFIX_USERS . '/' . self::SHARED . '/calendar-proxy-write';
388                         }
389                     }
390                     Tinebase_Cache_PerRequest::getInstance()->save(__CLASS__, __FUNCTION__, $classCacheId, $result);
391                     $cache->save($result, $cacheId, array(), 60 * 3);
392                 }
393                 
394                 break;
395         }
396         
397         return $result;
398     }
399     
400     public function setGroupMemberSet($principal, array $members) 
401     {
402         // do nothing
403     }
404     
405     public function updatePrincipal($path, $mutations)
406     {
407         return false;
408     }
409     
410     /**
411      * This method is used to search for principals matching a set of
412      * properties.
413      *
414      * This search is specifically used by RFC3744's principal-property-search
415      * REPORT. You should at least allow searching on
416      * http://sabredav.org/ns}email-address.
417      *
418      * The actual search should be a unicode-non-case-sensitive search. The
419      * keys in searchProperties are the WebDAV property names, while the values
420      * are the property values to search on.
421      *
422      * If multiple properties are being searched on, the search should be
423      * AND'ed.
424      *
425      * This method should simply return an array with full principal uri's.
426      *
427      * If somebody attempted to search on a property the backend does not
428      * support, you should simply return 0 results.
429      *
430      * You can also just return 0 results if you choose to not support
431      * searching at all, but keep in mind that this may stop certain features
432      * from working.
433      *
434      * @param string $prefixPath
435      * @param array $searchProperties
436      * @todo implement handling for shared pseudo user
437      * @return array
438      */
439     public function searchPrincipals($prefixPath, array $searchProperties)
440     {
441         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
442             __METHOD__ . '::' . __LINE__ . ' path: ' . $prefixPath . ' properties: ' . print_r($searchProperties, true));
443         
444         $principalUris = array();
445         
446         switch ($prefixPath) {
447             case self::PREFIX_GROUPS:
448                 $filter = new Addressbook_Model_ListFilter(array(
449                     array(
450                         'field'     => 'type',
451                         'operator'  => 'equals',
452                         'value'     => Addressbook_Model_List::LISTTYPE_GROUP
453                     )
454                 ));
455                 
456                 if (!empty($searchProperties['{http://calendarserver.org/ns/}search-token'])) {
457                     $filter->addFilter($filter->createFilter(array(
458                         'field'     => 'query',
459                         'operator'  => 'contains',
460                         'value'     => $searchProperties['{http://calendarserver.org/ns/}search-token']
461                     )));
462                 }
463                 
464                 if (!empty($searchProperties['{DAV:}displayname'])) {
465                     $filter->addFilter($filter->createFilter(array(
466                         'field'     => 'name',
467                         'operator'  => 'contains',
468                         'value'     => $searchProperties['{DAV:}displayname']
469                     )));
470                 }
471                 
472                 $result = Addressbook_Controller_List::getInstance()->search($filter, null, false, true);
473                 
474                 foreach ($result as $listId) {
475                     $principalUris[] = $prefixPath . '/' . $listId;
476                 }
477                 
478                 break;
479                 
480             case self::PREFIX_USERS:
481                 $filter = $this->_getContactFilterForUserContact();
482                 
483                 if (!empty($searchProperties['{http://calendarserver.org/ns/}search-token'])) {
484                     $filter->addFilter($filter->createFilter(array(
485                         'field'     => 'query',
486                         'operator'  => 'contains',
487                         'value'     => $searchProperties['{http://calendarserver.org/ns/}search-token']
488                     )));
489                 }
490                 
491                 if (!empty($searchProperties['{http://sabredav.org/ns}email-address'])) {
492                     $filter->addFilter($filter->createFilter(array(
493                         'field'     => 'email_query',
494                         'operator'  => 'contains',
495                         'value'     => $searchProperties['{http://sabredav.org/ns}email-address']
496                     )));
497                 }
498                 
499                 if (!empty($searchProperties['{DAV:}displayname'])) {
500                     $filter->addFilter($filter->createFilter(array(
501                         'field'     => 'query',
502                         'operator'  => 'contains',
503                         'value'     => $searchProperties['{DAV:}displayname']
504                     )));
505                 }
506                 
507                 if (!empty($searchProperties['{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}first-name'])) {
508                     $filter->addFilter($filter->createFilter(array(
509                         'field'     => 'n_given',
510                         'operator'  => 'contains',
511                         'value'     => $searchProperties['{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}first-name']
512                     )));
513                 }
514                 
515                 if (!empty($searchProperties['{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}last-name'])) {
516                     $filter->addFilter($filter->createFilter(array(
517                         'field'     => 'n_family',
518                         'operator'  => 'contains',
519                         'value'     => $searchProperties['{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}last-name']
520                     )));
521                 }
522                 
523                 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ .
524                     ' path: ' . $prefixPath . ' properties: ' . print_r($filter->toArray(), true));
525                 
526                 $result = Addressbook_Controller_Contact::getInstance()->search($filter, null, false, true);
527                 
528                 foreach ($result as $contactId) {
529                     $principalUris[] = $prefixPath . '/' . $contactId;
530                 }
531                 
532                 break;
533         }
534         
535         return $principalUris;
536     }
537     
538     /**
539      * return shared pseudo principal (principal for the shared containers) 
540      */
541     protected function _contactForSharedPrincipal()
542     {
543         $translate = Tinebase_Translation::getTranslation('Tinebase');
544         
545         $principal = array(
546             'uri'                     => self::PREFIX_USERS . '/' . self::SHARED,
547             '{DAV:}displayname'       => $translate->_('Shared folders'),
548             
549             '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}calendar-user-type'  => 'INDIVIDUAL',
550             
551             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}record-type' => 'users',
552             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}first-name'  => 'Folders',
553             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}last-name'   => 'Shared'
554         );
555         
556         return $principal;
557         
558     }
559     
560     /**
561      * convert contact model to principal array
562      * 
563      * @param Addressbook_Model_Contact $contact
564      * @return array
565      */
566     protected function _contactToPrincipal(Addressbook_Model_Contact $contact)
567     {
568         $principal = array(
569             'uri'                     => self::PREFIX_USERS . '/' . $contact->getId(),
570             '{DAV:}displayname'       => $contact->n_fileas,
571             '{DAV:}alternate-URI-set' => array('urn:uuid:' . $contact->getId()),
572             
573             '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}calendar-user-type'  => 'INDIVIDUAL',
574             
575             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}record-type' => 'users',
576             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}first-name'  => $contact->n_given,
577             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}last-name'   => $contact->n_family
578         );
579         
580         if (!empty(Tinebase_Core::getUser()->accountEmailAddress)) {
581             $principal['{http://sabredav.org/ns}email-address'] = $contact->email;
582         }
583         
584         return $principal;
585     }
586     
587     /**
588      * convert container grants to principals 
589      * 
590      * @param Tinebase_Record_RecordSet $containers
591      * @return array
592      * 
593      * @todo improve algorithm to fetch all contact/list_ids at once
594      */
595     protected function _containerGrantsToPrincipals(Tinebase_Record_RecordSet $containers)
596     {
597         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
598                 __METHOD__ . '::' . __LINE__ . ' Converting grants to principals for ' . count($containers) . ' containers.');
599         
600         $result = array();
601         
602         foreach ($containers as $container) {
603             $cacheId = convertCacheId('_containerGrantsToPrincipals' . $container->getId() . $container->seq);
604             
605             $containerPrincipals = Tinebase_Core::getCache()->load($cacheId);
606             
607             if ($containerPrincipals === false) {
608                 $containerPrincipals = array();
609                 
610                 $grants = Tinebase_Container::getInstance()->getGrantsOfContainer($container);
611                 
612                 foreach ($grants as $grant) {
613                     switch ($grant->account_type) {
614                         case 'group':
615                             $group = Tinebase_Group::getInstance()->getGroupById($grant->account_id);
616                             if ($group->list_id) {
617                                 $containerPrincipals[] = self::PREFIX_GROUPS . '/' . $group->list_id;
618                             }
619                             break;
620                             
621                         case 'user':
622                             // skip if grant belongs to the owner of the calendar
623                             if ($contact->account_id == $grant->account_id) {
624                                 continue;
625                             }
626                             $user = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountId', $grant->account_id);
627                             if ($user->contact_id) {
628                                 $containerPrincipals[] = self::PREFIX_USERS . '/' . $user->contact_id;
629                             }
630                             
631                             break;
632                     }
633                 }
634                 
635                 Tinebase_Core::getCache()->save($containerPrincipals, $cacheId, array(), /* 1 day */ 24 * 60 * 60);
636             }
637             
638             $result = array_merge($result, $containerPrincipals);
639         }
640         
641         // users and groups can be duplicate
642         $result = array_unique($result);
643         
644         return $result;
645     }
646     
647     /**
648      * convert list model to principal array
649      * 
650      * @param Addressbook_Model_List $list
651      * @return array
652      */
653     protected function _listToPrincipal(Addressbook_Model_List $list)
654     {
655         $principal = array(
656             'uri'                     => self::PREFIX_GROUPS . '/' . $list->getId(),
657             '{DAV:}displayname'       => $list->name . ' (Group)',
658             '{DAV:}alternate-URI-set' => array('urn:uuid:' . $list->getId()),
659             
660             '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}calendar-user-type'  => 'GROUP',
661             
662             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}record-type' => 'groups',
663             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}first-name'  => 'Group',
664             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}last-name'   => $list->name,
665         );
666         
667         return $principal;
668     }
669 }