Merge branch '2015.11-develop' into 2016.11
[tine20] / tine20 / Tinebase / WebDav / Collection / AbstractContainerTree.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) 2014-2014 Metaways Infosystems GmbH (http://www.metaways.de)
10  */
11
12 /**
13  * class to handle top level folders for an application
14  *
15  * @package     Tinebase
16  * @subpackage  WebDAV
17  */
18 abstract class Tinebase_WebDav_Collection_AbstractContainerTree extends \Sabre\DAV\Collection implements \Sabre\DAV\IProperties, \Sabre\DAVACL\IACL, \Sabre\DAV\IExtendedCollection
19 {
20     /**
21      * the current application object
22      * 
23      * @var Tinebase_Model_Application
24      */
25     protected $_application;
26     
27     /**
28      * application name
29      *
30      * @var string
31      */
32     protected $_applicationName;
33     
34     /**
35      * app has personal folders
36      *
37      * @var string
38      */
39     protected $_hasPersonalFolders = true;
40     
41     /**
42      * app has records folder
43      *
44      * @var string
45      */
46     protected $_hasRecordFolder = true;
47     
48     /**
49      * the current path
50      * 
51      * @var string
52      */
53     protected $_path;
54     
55     /**
56      * @var array
57      */
58     protected $_pathParts;
59     
60     /**
61      * 
62      * @var boolean
63      */
64     protected $_useIdAsName;
65     
66     protected static $_classCache = array (
67         '_getContact' => array()
68     );
69     
70     /**
71      * contructor
72      * 
73      * @param string $path         the current path
74      * @param bool   $useIdAsName  use name or id as node name
75      */
76     public function __construct($path, $useIdAsName = false)
77     {
78         $this->_path        = $path;
79         $this->_useIdAsName = $useIdAsName;
80     }
81     
82     /**
83      * (non-PHPdoc)
84      * @see \Sabre\DAV\Collection::createDirectory()
85      */
86     public function createDirectory($name) 
87     {
88         return $this->_createContainer(array(
89             'name' => $name
90         ));
91     }
92     
93     /**
94      * (non-PHPdoc)
95      * @see \Sabre\DAV\IExtendedCollection::createExtendedCollection()
96      */
97     function createExtendedCollection($name, array $resourceType, array $properties)
98     {
99         return $this->_createContainer(array(
100             'name'  => isset($properties['{DAV:}displayname']) ? $properties['{DAV:}displayname'] : $name,
101             'uuid'  => $name,
102             'color' => isset($properties['{http://apple.com/ns/ical/}calendar-color']) ? substr($properties['{http://apple.com/ns/ical/}calendar-color'], 0, 7) : null
103         ));
104     }
105     
106     /**
107      * (non-PHPdoc)
108      * @see Sabre\DAV\Collection::getChild()
109      */
110     public function getChild($name)
111     {
112         switch (count($this->_getPathParts())) {
113             # path == /<applicationPrefix> (for example calendars)
114             # return folders for currentuser, other users and 'shared' folder
115             # $name can be
116             # * contact_id of user
117             # * 'shared'
118             case 1:
119                 if ($name === Tinebase_Model_Container::TYPE_SHARED ||
120                     ($this->_hasRecordFolder && $name === Tinebase_FileSystem::FOLDER_TYPE_RECORDS)) {
121                     $path = $this->_path . '/' . $name;
122                     
123                 } elseif ($this->_hasPersonalFolders) {
124                     if ($name === '__currentuser__') {
125                         $path = $this->_path . '/__currentuser__';
126                         
127                     } else {
128                         try {
129                             $contact = $this->_getContact($name);
130                             
131                         } catch (Tinebase_Exception_NotFound $tenf) {
132                             throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
133                         }
134                         
135                         $path = $this->_path . '/' . ($this->_useIdAsName ? $contact->getId() : $contact->n_fileas);
136                     }
137                     
138                 } else {
139                     throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
140                 }
141                 
142                 $className = $this->_getApplicationName() . '_Frontend_WebDAV';
143                 
144                 return new $className($path, $this->_useIdAsName);
145                 
146                 break;
147                 
148             # path == /<applicationPrefix>/<contactid>|'shared'
149             # list container
150             case 2:
151                 if (Tinebase_Helper::array_value(1, $this->_getPathParts()) == Tinebase_Model_Container::TYPE_SHARED) {
152                     try { 
153                         if ($name instanceof Tinebase_Model_Container) {
154                             $container = $name;
155                         } elseif ($this->_useIdAsName) {
156                             // first try to fetch by uuid ...
157                             try {
158                                 $container = Tinebase_Container::getInstance()->getByProperty((string) $name, 'uuid');
159                             } catch (Tinebase_Exception_NotFound $tenf) {
160                                 // ... if that fails by id
161                                 $container = Tinebase_Container::getInstance()->getContainerById($name);
162                             }
163                         } else {
164                             $container = Tinebase_Container::getInstance()->getContainerByName(
165                                 $this->_getApplicationName(),
166                                 (string) $name,
167                                 Tinebase_Model_Container::TYPE_SHARED
168                             );
169                         }
170                         
171                     } catch (Tinebase_Exception_NotFound $tenf) {
172                         throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
173                         
174                     } catch (Tinebase_Exception_InvalidArgument $teia) {
175                         // invalid container id provided
176                         throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
177                     }
178                 } elseif ($this->_hasRecordFolder && Tinebase_Helper::array_value(1, $this->_getPathParts()) == Tinebase_FileSystem::FOLDER_TYPE_RECORDS) {
179                     
180                     return new Tinebase_Frontend_WebDAV_RecordCollection($this->_path . '/' . $name);
181                     
182                 } elseif ($this->_hasPersonalFolders) {
183                     if (Tinebase_Helper::array_value(1, $this->_getPathParts()) === '__currentuser__') {
184                         $accountId = Tinebase_Core::getUser()->accountId;
185                         
186                     } else {
187                         try {
188                             $accountId = $this->_getContact(Tinebase_Helper::array_value(1, $this->_getPathParts()))->account_id;
189                             
190                         } catch (Tinebase_Exception_NotFound $tenf) {
191                             throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
192                         }
193                     }
194                     
195                     try {
196                         if ($name instanceof Tinebase_Model_Container) {
197                             $container = $name;
198                         } elseif ($this->_useIdAsName) {
199                             // first try to fetch by uuid ...
200                             try {
201                                 $container = Tinebase_Container::getInstance()->getByProperty((string) $name, 'uuid');
202                             } catch (Tinebase_Exception_NotFound $tenf) {
203                                 // ... if that fails by id
204                                 $container = Tinebase_Container::getInstance()->getContainerById($name);
205                             }
206                             
207                         } else { 
208                             $container = Tinebase_Container::getInstance()->getContainerByName(
209                                 $this->_getApplicationName(),
210                                 (string) $name,
211                                 Tinebase_Model_Container::TYPE_PERSONAL, 
212                                 $accountId
213                             );
214                         }
215                         
216                     } catch (Tinebase_Exception_NotFound $tenf) {
217                         throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
218                         
219                     } catch (Tinebase_Exception_InvalidArgument $teia) {
220                         // invalid container id provided
221                         throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
222                     }
223                     
224                 } else {
225                     throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
226                 }
227                 
228                 if (!Tinebase_Core::getUser()->hasGrant($container, Tinebase_Model_Grants::GRANT_READ) ||
229                     !Tinebase_Core::getUser()->hasGrant($container, Tinebase_Model_Grants::GRANT_SYNC)) {
230                     throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
231                 }
232                 
233                 $objectClass = Tinebase_Application::getInstance()->getApplicationById($container->application_id)->name . '_Frontend_WebDAV_Container';
234                 
235                 if (! class_exists($objectClass)) {
236                     throw new \Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
237                 }
238                  
239                 return new $objectClass($container, $this->_useIdAsName);
240                 
241                 break;
242                 
243             default:
244                 throw new Sabre\DAV\Exception\NotFound("Directory $this->_path/$name not found");
245             
246                 break;
247         }
248     }
249     
250     /**
251      * Returns an array with all the child nodes
252      * 
253      * the records subtree is not returned as child here. It's only available via getChild().
254      *
255      * @return \Sabre\DAV\INode[]
256      */
257     function getChildren()
258     {
259         $children = array();
260         
261         switch (count($this->_getPathParts())) {
262             # path == /<applicationPrefix> (for example calendars)
263             # return folders for currentuser, other users and 'shared' folder
264             case 1:
265                 $children[] = $this->getChild(Tinebase_Model_Container::TYPE_SHARED);
266                 
267                 if ($this->_hasPersonalFolders) {
268                     $children[] = $this->getChild(
269                         $this->_useIdAsName ? Tinebase_Core::getUser()->contact_id :
270                         Addressbook_Controller_Contact::getInstance()->get(Tinebase_Core::getUser()->contact_id)->n_fileas
271                     );
272                     
273                     $otherUsers = Tinebase_Container::getInstance()->getOtherUsers(Tinebase_Core::getUser(), $this->_getApplicationName(), array(
274                         Tinebase_Model_Grants::GRANT_READ,
275                         Tinebase_Model_Grants::GRANT_SYNC
276                     ));
277                     
278                     foreach ($otherUsers as $user) {
279                         if ($user->contact_id && $user->visibility === Tinebase_Model_User::VISIBILITY_DISPLAYED) {
280                             try {
281                                 $folderId = $this->_useIdAsName ? $user->contact_id :
282                                     Addressbook_Controller_Contact::getInstance()->get($user->contact_id)->n_fileas;
283
284                                 $children[] = $this->getChild($folderId);
285                             } catch (\Sabre\DAV\Exception\NotFound $sdavenf) {
286                                 // ignore contacts not found
287                             }
288                         }
289                     }
290                 }
291         
292                 break;
293             
294             # path == /<applicationPrefix>/<contactid>|'shared'
295             # list container
296             case 2:
297                 if (Tinebase_Helper::array_value(1, $this->_getPathParts()) == Tinebase_Model_Container::TYPE_SHARED) {
298                     $containers = Tinebase_Container::getInstance()->getSharedContainer(
299                         Tinebase_Core::getUser(),
300                         $this->_getApplicationName(),
301                         array(
302                             Tinebase_Model_Grants::GRANT_READ,
303                             Tinebase_Model_Grants::GRANT_SYNC
304                         )
305                     );
306                     
307                 } elseif ($this->_hasPersonalFolders) {
308                     if (Tinebase_Helper::array_value(1, $this->_getPathParts()) === '__currentuser__') {
309                         $accountId = Tinebase_Core::getUser()->accountId;
310                         
311                     } else {
312                         try {
313                             $accountId = $this->_getContact(Tinebase_Helper::array_value(1, $this->_getPathParts()))->account_id;
314                         } catch (Tinebase_Exception_NotFound $tenf) {
315                             throw new \Sabre\DAV\Exception\NotFound("Path $this->_path not found");
316                         }
317                     }
318                     
319                     try {
320                         if ($this->_getApplicationName() === 'Filemanager' || $this->_clientSupportsDelegations()) {
321                             $containers = Tinebase_Container::getInstance()->getPersonalContainer(
322                                 Tinebase_Core::getUser(),
323                                 $this->_getApplicationName(),
324                                 $accountId,
325                                 array(
326                                     Tinebase_Model_Grants::GRANT_READ, 
327                                     Tinebase_Model_Grants::GRANT_SYNC
328                                 )
329                             ); 
330                         } else {
331                             $containers = Tinebase_Container::getInstance()->getContainerByACL(Tinebase_Core::getUser(), $this->_getApplicationName(),  array(
332                                 Tinebase_Model_Grants::GRANT_READ, 
333                                 Tinebase_Model_Grants::GRANT_SYNC
334                             ));
335                         }
336                     } catch (Tinebase_Exception_AccessDenied $tead) {
337                         throw new Sabre\DAV\Exception\NotFound("Could not find path (" . $tead->getMessage() . ")");
338                     }
339                     
340                 } else {
341                     throw new Sabre\DAV\Exception\NotFound("Path $this->_path not found");
342                 }
343                 
344                 foreach ($containers as $container) {
345                     try {
346                         $children[] = $this->getChild($this->_useIdAsName ? $container->getId() : $container->name);
347                     } catch (\Sabre\DAV\Exception\NotFound $sdavenf) {
348                         // ignore containers not found
349                     }
350                 }
351                 
352                 break;
353                 
354             default:
355                 throw new Sabre\DAV\Exception\NotFound("Path $this->_path not found");
356                 
357                 break;
358         }
359         
360         return $children;
361     }
362     
363     /**
364      * checks if client supports delegations
365      * 
366      * @return boolean
367      * 
368      * @todo don't use $_SERVER to fetch user agent
369      * @todo move user agent parsing to Tinebase
370      */
371     protected function _clientSupportsDelegations()
372     {
373         if (isset($_SERVER['HTTP_USER_AGENT'])) {
374             list($backend, $version) = Calendar_Convert_Event_VCalendar_Factory::parseUserAgent($_SERVER['HTTP_USER_AGENT']);
375             $clientSupportsDelegations = ($backend === Calendar_Convert_Event_VCalendar_Factory::CLIENT_MACOSX);
376         } else {
377             $clientSupportsDelegations = false;
378         }
379         
380         return $clientSupportsDelegations;
381     }
382     
383     /**
384      * return etag
385      * 
386      * @return string
387      */
388     public function getETag()
389     {
390         $etags = array();
391         
392         foreach ($this->getChildren() as $child) {
393             $etags[] = $child->getETag();
394         }
395         
396         return '"' . sha1(implode(null, $etags)) . '"';
397     }
398     
399     /**
400      * Returns a group principal
401      *
402      * This must be a url to a principal, or null if there's no owner
403      *
404      * @return string|null
405      */
406     public function getGroup()
407     {
408         return null;
409     }
410     
411     /**
412      * (non-PHPdoc)
413      * @see \Sabre\DAV\Node::getLastModified()
414      */
415     public function getLastModified()
416     {
417         $lastModified = 1;
418         
419         foreach ($this->getChildren() as $child) {
420             $lastModified = $child->getLastModified() > $lastModified ? $child->getLastModified() : $lastModified;
421         }
422         
423         return $lastModified;
424     }
425     
426     /**
427      * Returns a list of ACE's for this node.
428      *
429      * Each ACE has the following properties:
430      *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
431      *     currently the only supported privileges
432      *   * 'principal', a url to the principal who owns the node
433      *   * 'protected' (optional), indicating that this ACE is not allowed to
434      *      be updated.
435      *      
436      * @todo implement real logic
437      * @return array
438      */
439     public function getACL() 
440     {
441         $principal = 'principals/users/' . Tinebase_Core::getUser()->contact_id;
442         
443         return array(
444             array(
445                 'privilege' => '{DAV:}read',
446                 'principal' => $principal,
447                 'protected' => true,
448             ),
449             array(
450                 'privilege' => '{DAV:}write',
451                 'principal' => $principal,
452                 'protected' => true,
453             )
454         );
455     }
456     
457     /**
458      * Returns the name of the node
459      *
460      * @return string
461      */
462     public function getName()
463     {
464         if (count($this->_getPathParts()) === 2 && 
465             Tinebase_Helper::array_value(1, $this->_getPathParts()) !== Tinebase_Model_Container::TYPE_SHARED &&
466             !$this->_useIdAsName
467         ) {
468             try {
469                 $contact = $this->_getContact(Tinebase_Helper::array_value(1, $this->_getPathParts()));
470                 
471                 $name = $contact->n_fileas;
472                 
473             } catch (Tinebase_Exception_NotFound $tenf) {
474                 list(,$name) = Sabre\DAV\URLUtil::splitPath($this->_path);
475             }
476             
477         } else {
478             list(,$name) = Sabre\DAV\URLUtil::splitPath($this->_path);
479         }
480         
481         return $name;
482     }
483     
484     /**
485      * Returns the owner principal
486      *
487      * This must be a url to a principal, or null if there's no owner
488      * 
489      * @return string|null
490      */
491     public function getOwner()
492     {
493         if (count($this->_getPathParts()) === 2 && $this->getName() !== Tinebase_Model_Container::TYPE_SHARED) {
494             try {
495                 $contact = $this->_getContact(Tinebase_Helper::array_value(1, $this->_getPathParts()));
496             } catch (Tinebase_Exception_NotFound $tenf) {
497                 return null;
498             }
499             
500             return 'principals/users/' . $contact->getId();
501         }
502         
503         return null;
504     }
505     
506     /**
507      * Returns the list of properties
508      *
509      * @param array $requestedProperties
510      * @return array
511      */
512     public function getProperties($requestedProperties) 
513     {
514         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
515             __METHOD__ . '::' . __LINE__ . ' path: ' . $this->_path . ' ' . print_r($requestedProperties, true));
516         
517         $response = array();
518     
519         foreach ($requestedProperties as $property) {
520             switch ($property) {
521                 case '{DAV:}displayname':
522                     if (count($this->_getPathParts()) === 2 && $this->getName() !== Tinebase_Model_Container::TYPE_SHARED) {
523                         try {
524                             $contact = $this->_getContact(Tinebase_Helper::array_value(1, $this->_getPathParts()));
525                         } catch (Tinebase_Exception_NotFound $tenf) {
526                             continue;
527                         }
528                         
529                         $response[$property] = $contact->n_fileas;
530                     }
531                     
532                     break;
533                     
534                 case '{DAV:}owner':
535                     if ($this->getOwner()) {
536                         $response[$property] = new \Sabre\DAVACL\Property\Principal(
537                             \Sabre\DAVACL\Property\Principal::HREF, $this->getOwner()
538                         );
539                     }
540                     
541                     break;
542                     
543                 case '{DAV:}getetag':
544                     $response[$property] = $this->getETag();
545                     
546                     break;
547             }
548         }
549         
550         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
551             __METHOD__ . '::' . __LINE__ . ' path: ' . $this->_path . ' ' . print_r($response, true));
552         
553         return $response;
554     }
555     
556     /**
557      * Updates the ACL
558      *
559      * This method will receive a list of new ACE's.
560      *
561      * @param array $acl
562      * @return void
563      */
564     public function setACL(array $acl)
565     {
566         throw new Sabre\DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
567     }
568     
569     /**
570      * Updates properties on this node,
571      *
572      * The properties array uses the propertyName in clark-notation as key,
573      * and the array value for the property value. In the case a property
574      * should be deleted, the property value will be null.
575      *
576      * This method must be atomic. If one property cannot be changed, the
577      * entire operation must fail.
578      *
579      * If the operation was successful, true can be returned.
580      * If the operation failed, false can be returned.
581      *
582      * Deletion of a non-existant property is always succesful.
583      *
584      * Lastly, it is optional to return detailed information about any
585      * failures. In this case an array should be returned with the following
586      * structure:
587      *
588      * array(
589      *   403 => array(
590      *      '{DAV:}displayname' => null,
591      *   ),
592      *   424 => array(
593      *      '{DAV:}owner' => null,
594      *   )
595      * )
596      *
597      * In this example it was forbidden to update {DAV:}displayname. 
598      * (403 Forbidden), which in turn also caused {DAV:}owner to fail
599      * (424 Failed Dependency) because the request needs to be atomic.
600      *
601      * @param array $mutations 
602      * @return bool|array 
603      */
604     public function updateProperties($mutations) 
605     {
606         $result = array(
607             200 => array(),
608             403 => array()
609         );
610
611         foreach ($mutations as $key => $value) {
612             switch ($key) {
613                 // once iCal tried to set default-alarm config with a negative feedback
614                 // it doesn't send default-alarms to the server any longer. So we fake
615                 // success here as workaround to let the client send its default alarms
616                 case '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}default-alarm-vevent-datetime':
617                 case '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}default-alarm-vevent-date':
618                 case '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}default-alarm-vtodo-datetime':
619                 case '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}default-alarm-vtodo-date':
620                     // fake success
621                     $result['200'][$key] = null;
622                     break;
623
624                 default:
625                     $result['403'][$key] = null;
626             }
627         }
628
629         return $result;
630     }
631     
632     /**
633      * 
634      */
635     public function getSupportedPrivilegeSet()
636     {
637         return null;
638     }
639     
640     /**
641      * return application object
642      * 
643      * @return Tinebase_Model_Application
644      */
645     protected function _getApplication()
646     {
647         if (!$this->_application) {
648             $this->_application = Tinebase_Application::getInstance()->getApplicationByName($this->_getApplicationName());
649         }
650         
651         return $this->_application;
652     }
653     
654     /**
655      * creates a new container
656      * 
657      * @todo allow to create personal folders only when in currents users own path
658      * 
659      * @param  array  $properties  properties for new container
660      * @throws \Sabre\DAV\Exception\Forbidden
661      * @return Tinebase_Model_Container
662      */
663     protected function _createContainer(array $properties) 
664     {
665         if (count($this->_getPathParts()) !== 2) {
666             throw new \Sabre\DAV\Exception\Forbidden('Permission denied to create directory ' . $properties['name']);
667         }
668         
669         $containerType = Tinebase_Helper::array_value(1, $this->_getPathParts()) == Tinebase_Model_Container::TYPE_SHARED ?
670             Tinebase_Model_Container::TYPE_SHARED :
671             Tinebase_Model_Container::TYPE_PERSONAL;
672         
673         $newContainer = new Tinebase_Model_Container(array_merge($properties, array(
674             'type'              => $containerType,
675             'backend'           => 'Sql',
676             'application_id'    => $this->_getApplication()->getId(),
677             'model'             => Tinebase_Core::getApplicationInstance($this->_applicationName)->getDefaultModel()
678         )));
679
680         try {
681             $container = Tinebase_Container::getInstance()->addContainer($newContainer);
682         } catch (Tinebase_Exception_AccessDenied $tead) {
683             throw new \Sabre\DAV\Exception\Forbidden('Permission denied to create directory ' . $properties['name']);
684         }
685         
686         return $container;
687     }
688     
689     /**
690      * return application name
691      * 
692      * @return string
693      */
694     protected function _getApplicationName()
695     {
696         if (!$this->_applicationName) {
697             $this->_applicationName = Tinebase_Helper::array_value(0, explode('_', get_class($this)));
698         }
699         
700         return $this->_applicationName;
701     }
702     
703     /**
704      * get path parts
705      * 
706      * @return array
707      */
708     protected function _getPathParts()
709     {
710         if (!$this->_pathParts) {
711             $this->_pathParts = $this->_parsePath($this->_path);
712         }
713         
714         return $this->_pathParts;
715     }
716     
717     /**
718      * split path into parts
719      * 
720      * @param  string  $_path
721      * @return array
722      */
723     protected function _parsePath($_path)
724     {
725         $pathParts = explode('/', trim($this->_path, '/'));
726         
727         return $pathParts;
728     }
729     
730     /**
731      * resolve contact_id to Addressbook_Model_Contact
732      * 
733      * @return Addressbook_Model_Contact
734      */
735     protected function _getContact($contactId)
736     {
737         $classCacheId = ($this->_useIdAsName ? 'id' : 'n_fileas') . $contactId;
738         
739         if (isset(self::$_classCache[__FUNCTION__][$classCacheId])) {
740             return self::$_classCache[__FUNCTION__][$classCacheId];
741         }
742         
743         $filter = new Addressbook_Model_ContactFilter(array(
744             array(
745                 'field'     => 'type',
746                 'operator'  => 'equals',
747                 'value'     => Addressbook_Model_Contact::CONTACTTYPE_USER
748             ),
749             array(
750                 'field'     => $this->_useIdAsName ? 'id' : 'n_fileas',
751                 'operator'  => 'equals',
752                 'value'     => $contactId
753             ),
754         ));
755         
756         $contact = Addressbook_Controller_Contact::getInstance()->search($filter)->getFirstRecord();
757         
758         if (!$contact) {
759             throw new Tinebase_Exception_NotFound('contact not found');
760         }
761         
762         self::$_classCache[__FUNCTION__][$classCacheId] = $contact;
763         
764         return $contact;
765     }
766 }