extend usage of in-class cache in Tinebase_Container
[tine20] / tine20 / Tinebase / Container.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  Container
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Lars Kneschke <l.kneschke@metaways.de>
10  * 
11  * @todo        refactor that: remove code duplication, remove Zend_Db_Table_Abstract usage, use standard record controller/backend functions
12  *              -> make use of Tinebase_Backend_Sql_Grants
13  * @todo        move (or replace from) functions to backend
14  * @todo        switch containers to hash ids
15  */
16
17 /**
18  * this class handles access rights(grants) to containers
19  * 
20  * any record in Tine 2.0 is tied to a container. the rights of an account on a record gets 
21  * calculated by the grants given to this account on the container holding the record (if you know what i mean ;-))
22  * 
23  * @package     Tinebase
24  * @subpackage  Acl
25  */
26 class Tinebase_Container extends Tinebase_Backend_Sql_Abstract
27 {
28     /**
29      * Table name without prefix
30      *
31      * @var string
32      */
33     protected $_tableName = 'container';
34     
35     /**
36      * Model name
37      *
38      * @var string
39      */
40     protected $_modelName = 'Tinebase_Model_Container';
41     
42     /**
43      * if modlog is active, we add 'is_deleted = 0' to select object in _getSelect()
44      *
45      * @var boolean
46      */
47     protected $_modlogActive = TRUE;
48     
49     /**
50      * the table object for the container_acl table
51      *
52      * @var Zend_Db_Table_Abstract
53      */
54     protected $_containerAclTable;
55     
56     /**
57      * container content history backend
58      * 
59      * @var Tinebase_Backend_Sql
60      */
61     protected $_contentBackend = NULL;
62     
63     /**
64      * don't clone. Use the singleton.
65      *
66      */
67     private function __clone() {}
68     
69     /**
70      * holds the instance of the singleton
71      *
72      * @var Tinebase_Container
73      */
74     private static $_instance = NULL;
75     
76     /**
77      * default column(s) for count
78      *
79      * @var string
80      */
81     protected $_defaultCountCol = 'id';
82     
83     /**
84      * cache timeout for ACL related cache entries (in seconds)
85      * 
86      * @see 0007266: make groups / group memberships cache cleaning more efficient
87      * @var integer
88      */
89     const ACL_CACHE_TIMEOUT = 30;
90     
91     /**
92      * the singleton pattern
93      *
94      * @return Tinebase_Container
95      */
96     public static function getInstance() 
97     {
98         if (self::$_instance === NULL) {
99             self::$_instance = new Tinebase_Container();
100         }
101         
102         return self::$_instance;
103     }
104     
105     /**
106      * get content backend
107      * 
108      * @return Tinebase_Backend_Sql
109      * 
110      * @todo move this to constructor when this no longer extends Tinebase_Backend_Sql_Abstract
111      */
112     protected function _getContentBackend()
113     {
114         if ($this->_contentBackend === NULL) {
115             $this->_contentBackend  = new Tinebase_Backend_Sql(array(
116                 'modelName' => 'Tinebase_Model_ContainerContent', 
117                 'tableName' => 'container_content',
118             ));
119         }
120         
121         return $this->_contentBackend;
122     }
123     
124     /**
125      * creates a new container
126      *
127      * @param   Tinebase_Model_Container $_container the new container
128      * @param   Tinebase_Record_RecordSet $_grants the grants for the new folder 
129      * @param   bool  $_ignoreAcl
130      * @return  Tinebase_Model_Container the newly created container
131      * @throws  Tinebase_Exception_Record_Validation
132      * @throws  Tinebase_Exception_AccessDenied
133      */
134     public function addContainer(Tinebase_Model_Container $_container, $_grants = NULL, $_ignoreAcl = FALSE)
135     {
136         $_container->isValid(TRUE);
137         
138         if($_ignoreAcl !== TRUE) {
139             switch($_container->type) {
140                 case Tinebase_Model_Container::TYPE_PERSONAL:
141                     // is the user allowed to create personal container?
142                     break;
143                     
144                 case Tinebase_Model_Container::TYPE_SHARED:
145                     $application = Tinebase_Application::getInstance()->getApplicationById($_container->application_id);
146                     $appName = (string) $application;
147                     $manageRight = FALSE;
148                     
149                     // check for MANAGE_SHARED_FOLDERS right
150                     $appAclClassName = $appName . '_Acl_Rights';
151                     if (@class_exists($appAclClassName)) {
152                         $appAclObj = call_user_func(array($appAclClassName, 'getInstance'));
153                         $allRights = $appAclObj->getAllApplicationRights();
154                         if (in_array(Tinebase_Acl_Rights::MANAGE_SHARED_FOLDERS, $allRights)) {
155                             $manageRight = Tinebase_Core::getUser()->hasRight($appName, Tinebase_Acl_Rights::MANAGE_SHARED_FOLDERS);
156                         }
157                     }
158                     
159
160                     if(!$manageRight && !Tinebase_Core::getUser()->hasRight($appName, Tinebase_Acl_Rights::ADMIN)) {
161                         throw new Tinebase_Exception_AccessDenied('Permission to add shared container denied.');
162                     }
163                     break;
164                     
165                 default:
166                     throw new Tinebase_Exception_InvalidArgument('Can add personal or shared folders only when ignoring ACL.');
167                     break;
168             }
169         }
170         
171         if (!empty($_container->owner_id)) {
172             $accountId = $_container->owner_id instanceof Tinebase_Model_User ? $_container->owner_id->getId() : $_container->owner_id;
173         } else {
174             $accountId = (is_object(Tinebase_Core::getUser())) ? Tinebase_Core::getUser()->getId() : NULL;
175         }
176         
177         if($_grants === NULL || count($_grants) == 0) {
178             $creatorGrants = array(
179                 'account_id'     => $accountId,
180                 'account_type'   => Tinebase_Acl_Rights::ACCOUNT_TYPE_USER,
181                 Tinebase_Model_Grants::GRANT_READ      => true,
182                 Tinebase_Model_Grants::GRANT_ADD       => true,
183                 Tinebase_Model_Grants::GRANT_EDIT      => true,
184                 Tinebase_Model_Grants::GRANT_DELETE    => true,
185                 Tinebase_Model_Grants::GRANT_EXPORT    => true,
186                 Tinebase_Model_Grants::GRANT_SYNC      => true,
187                 Tinebase_Model_Grants::GRANT_ADMIN     => true,
188             );
189             
190             if (    $_container->type === Tinebase_Model_Container::TYPE_SHARED 
191                  && ! Tinebase_Config::getInstance()->get(Tinebase_Config::ANYONE_ACCOUNT_DISABLED)) {
192     
193                 // add all grants to creator and
194                 // add read grants to any other user
195                 $grants = new Tinebase_Record_RecordSet('Tinebase_Model_Grants', array(
196                     $creatorGrants,
197                     array(
198                         'account_id'      => '0',
199                         'account_type'    => Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE,
200                         Tinebase_Model_Grants::GRANT_READ    => true,
201                         Tinebase_Model_Grants::GRANT_EXPORT  => true,
202                         Tinebase_Model_Grants::GRANT_SYNC    => true,
203                     )
204                 ), TRUE);
205             } else {
206                 // add all grants to creator only
207                 $grants = new Tinebase_Record_RecordSet('Tinebase_Model_Grants', array(
208                     $creatorGrants
209                 ), TRUE);
210             }
211         } else {
212             $grants = $_grants;
213         }
214         
215         $event = new Tinebase_Event_Container_BeforeCreate();
216         $event->accountId = $accountId;
217         $event->container = $_container;
218         $event->grants = $grants;
219         Tinebase_Event::fireEvent($event);
220         
221         Tinebase_Timemachine_ModificationLog::setRecordMetaData($_container, 'create');
222         $container = $this->create($_container);
223         $this->setGrants($container->getId(), $grants, TRUE, FALSE);
224         
225         return $container;
226     }
227     
228     /**
229      * add grants to container
230      *
231      * @todo    check that grant is not already given to container/type/accout combi
232      * @param   int|Tinebase_Model_Container $_containerId
233      * @param   int $_accountId
234      * @param   array $_grants list of grants to add
235      * @return  boolean
236      * @throws  Tinebase_Exception_AccessDenied
237      */
238     public function addGrants($_containerId, $_accountType, $_accountId, array $_grants, $_ignoreAcl = FALSE)
239     {
240         $containerId = Tinebase_Model_Container::convertContainerIdToInt($_containerId);
241         
242         if($_ignoreAcl !== TRUE and !$this->hasGrant(Tinebase_Core::getUser(), $_containerId, Tinebase_Model_Grants::GRANT_ADMIN)) {
243             throw new Tinebase_Exception_AccessDenied('Permission to manage grants on container denied.');
244         }
245         
246         switch($_accountType) {
247             case Tinebase_Acl_Rights::ACCOUNT_TYPE_USER:
248                 $accountId = Tinebase_Model_User::convertUserIdToInt($_accountId);
249                 break;
250             case Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP:
251                 $accountId = Tinebase_Model_Group::convertGroupIdToInt($_accountId);
252                 break;
253             case Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE:
254                 $accountId = '0';
255                 break;
256             default:
257                 throw new Tinebase_Exception_InvalidArgument('invalid $_accountType');
258                 break;
259         }
260         
261         $containerGrants = $this->getGrantsOfContainer($containerId, TRUE);
262         $containerGrants->addIndices(array('account_type', 'account_id'));
263         $existingGrants = $containerGrants->filter('account_type', $_accountType)->filter('account_id', $_accountId)->getFirstRecord();
264         
265         $id = Tinebase_Record_Abstract::generateUID();
266         
267         foreach($_grants as $grant) {
268             if ($existingGrants === NULL || ! $existingGrants->{$grant}) {
269                 $data = array(
270                     'id'            => $id,
271                     'container_id'  => $containerId,
272                     'account_type'  => $_accountType,
273                     'account_id'    => $accountId,
274                     'account_grant' => $grant
275                 );
276                 $this->_getContainerAclTable()->insert($data);
277             }
278         }
279         
280         $this->_setRecordMetaDataAndUpdate($containerId, 'update');
281         
282         return true;
283     }
284     
285     /**
286      * resolves the $_recordClass argument for legacy handling: $_recordClass was before $_application
287      * in getDefaultContainer and getPersonalContainer
288      * @param string|Tinebase_Record_Interface $_recordClass
289      * @throws Tinebase_Exception_InvalidArgument
290      */
291     protected function _resolveRecordClassArgument($_recordClass)
292     {
293         $ret = array();
294         if(is_string($_recordClass)) {
295             $split = explode('_', $_recordClass);
296             switch(count($split)) {
297                 case 1:
298                     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Using application name is deprecated. Please use the classname of the model or the class itself.');
299                     $ret['appName'] = $_recordClass;
300                     if(! $ret['recordClass'] = Tinebase_Core::getApplicationInstance($_recordClass)->getDefaultModel()) {
301                         throw new Tinebase_Exception_NotFound('A default model could not be found for application ' . $_recordClass);
302                     }
303                     break;
304                 case 3: 
305                     $ret['appName'] = $split[0];
306                     $ret['recordClass'] = $_recordClass;
307                     break;
308                 default: throw new Tinebase_Exception_InvalidArgument('Invalid value as recordClass given: ' . print_r($_recordClass, 1));
309             }
310         } elseif (in_array('Tinebase_Record_Interface', class_implements($_recordClass))) {
311             $ret['appName'] = $_recordClass->getApplication();
312             $ret['recordClass'] = get_class($_recordClass);
313         } else {
314             throw new Tinebase_Exception_InvalidArgument('Invalid value as recordClass given: ' . print_r($_recordClass, 1));
315         }
316         
317         return $ret;
318     }
319     
320     /**
321      * set modified timestamp for container
322      * 
323      * @param int|Tinebase_Model_Container $container
324      * @param string                       $action    one of {create|update|delete}
325      * @return Tinebase_Model_Container
326      */
327     protected function _setRecordMetaDataAndUpdate($container, $action)
328     {
329         if (! $container instanceof Tinebase_Model_Container) {
330             $container = $this->getContainerById($container);
331         }
332         Tinebase_Timemachine_ModificationLog::getInstance()->setRecordMetaData($container, $action, $container);
333         
334         $this->_clearCache($container);
335         
336         return $this->update($container);
337     }
338     
339     /**
340     * get the basic select object to fetch records from the database
341     *
342     * @param array|string $_cols columns to get, * per default
343     * @param boolean $_getDeleted get deleted records (if modlog is active)
344     * @return Zend_Db_Select
345     */
346     protected function _getSelect($_cols = '*', $_getDeleted = FALSE)
347     {
348         $select = parent::_getSelect($_cols, $_getDeleted);
349         
350         if ($_cols === '*') {
351             $select->joinLeft(
352                 /* table  */ array('owner' => SQL_TABLE_PREFIX . 'container_acl'),
353                 /* on     */ "{$this->_db->quoteIdentifier('owner.container_id')} = {$this->_db->quoteIdentifier('container.id')} AND ".
354                              "{$this->_db->quoteIdentifier('container.type')} = {$this->_db->quote(Tinebase_Model_Container::TYPE_PERSONAL)} AND " .
355                              "{$this->_db->quoteIdentifier('owner.account_type')} = {$this->_db->quote('user')} AND " .
356                              "{$this->_db->quoteIdentifier('owner.account_grant')} = {$this->_db->quote(Tinebase_Model_Grants::GRANT_ADMIN)}",
357                 /* select */ array('owner_id' => 'account_id')
358             );
359         }
360         
361         return $select;
362     }
363     
364     /**
365      * get containers with bad names (name == uuid)
366      * 
367      * @return Tinebase_Record_RecordSet
368      */
369     public function getContainersWithBadNames()
370     {
371         $select = $this->_getSelect();
372         $select->where($this->_db->quoteIdentifier('uuid') . ' = ' . $this->_db->quoteIdentifier('name'));
373         $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
374         $rows = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
375         
376         $result = new Tinebase_Record_RecordSet('Tinebase_Model_Container', $rows, TRUE);
377         return $result;
378     }
379     
380     /**
381      * return all container, which the user has the requested right for
382      *
383      * used to get a list of all containers accesssible by the current user
384      * 
385      * @param   string|Tinebase_Model_User          $accountId
386      * @param   string                              $applicationName
387      * @param   array|string                        $grant
388      * @param   bool                                $onlyIds return only ids
389      * @param   bool                                $ignoreACL
390      * @return  Tinebase_Record_RecordSet|array
391      * @throws  Tinebase_Exception_NotFound
392      */
393     public function getContainerByACL($accountId, $applicationName, $grant, $onlyIds = FALSE, $ignoreACL = FALSE)
394     {
395         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
396             . ' app: ' . $applicationName . ' / account: ' . $accountId . ' / grant: ' . implode('/', (array)$grant));
397         
398         $accountId     = Tinebase_Model_User::convertUserIdToInt($accountId);
399         $applicationId = Tinebase_Application::getInstance()->getApplicationByName($applicationName)->getId();
400         $grant         = $ignoreACL ? '*' : $grant;
401         
402         // always bring values in the same order for $classCacheId 
403         if (is_array($grant)) {
404             sort($grant);
405         }
406         
407         $classCacheId = convertCacheId($accountId . $applicationId . implode('', (array)$grant) . (int)$onlyIds . (int)$ignoreACL);
408         
409         try {
410             return $this->loadFromClassCache(__FUNCTION__, $classCacheId);
411         } catch (Tinebase_Exception_NotFound $tenf) {
412             // continue...
413         }
414         
415         $select = $this->_getSelect($onlyIds ? 'id' : '*')
416             ->distinct()
417             ->join(array(
418                 /* table  */ 'container_acl' => SQL_TABLE_PREFIX . 'container_acl'), 
419                 /* on     */ "{$this->_db->quoteIdentifier('container_acl.container_id')} = {$this->_db->quoteIdentifier('container.id')}",
420                 /* select */ array()
421             )
422             ->where("{$this->_db->quoteIdentifier('container.application_id')} = ?", $applicationId);
423             
424         if (!$onlyIds) {
425             // we only need to order by name if we fetch all container data (legacy, maybe this can be removed)
426             $select->order('container.name');
427         }
428         
429         $this->addGrantsSql($select, $accountId, $grant);
430         
431         $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
432         
433         if ($onlyIds) {
434             $result = $stmt->fetchAll(Zend_Db::FETCH_COLUMN);
435         } else {
436             $result = new Tinebase_Record_RecordSet('Tinebase_Model_Container', $stmt->fetchAll(Zend_Db::FETCH_ASSOC), true);
437         }
438         
439         // any account should have at least one personal folder
440         // @todo add test for empty case
441         if (empty($result)) {
442             $personalContainer = $this->getDefaultContainer($applicationName, $accountId);
443             if ($personalContainer instanceof Tinebase_Model_Container) {
444                 $result = ($onlyIds) ? 
445                     array($personalContainer->getId()) : 
446                     new Tinebase_Record_RecordSet('Tinebase_Model_Container', array($personalContainer), TRUE);
447             }
448         }
449         
450         $this->saveInClassCache(__FUNCTION__, $classCacheId, $result);
451         
452         return $result;
453     }
454     
455     /**
456      * return a container by containerId
457      * - cache the results because this function is called very often
458      *
459      * @todo what about grant checking here???
460      * 
461      * @param   int|Tinebase_Model_Container $_containerId the id of the container
462      * @param   bool                         $_getDeleted get deleted records
463      * @return  Tinebase_Model_Container
464      * @throws  Tinebase_Exception_NotFound
465      */
466     public function getContainerById($_containerId, $_getDeleted = FALSE)
467     {
468         $containerId = Tinebase_Model_Container::convertContainerIdToInt($_containerId);
469         
470         $classCacheId = $containerId . 'd' . (int)$_getDeleted;
471         $cacheId      = 'getContainerById' . $classCacheId;
472         
473         try {
474             return $this->loadFromClassCache(__FUNCTION__, $classCacheId);
475         } catch (Tinebase_Exception_NotFound $tenf) {
476             // continue...
477         }
478         
479         // load from cache
480         $cache = Tinebase_Core::getCache();
481         $result = $cache->load($cacheId);
482
483         if ($result === FALSE) {
484             $result = $this->get($containerId, $_getDeleted);
485             $cache->save($result, $cacheId, array('container'));
486         }
487         
488         $this->saveInClassCache(__FUNCTION__, $classCacheId, $result);
489         
490         return $result;
491     }
492     
493     /**
494      * return a container identified by path
495      *
496      * @param   string  $_path        the path to the container
497      * @param   bool    $_getDeleted  get deleted records
498      * @return  Tinebase_Model_Container
499      * @throws  Tinebase_Exception_NotFound
500      */
501     public function getByPath($_path, $_getDeleted = FALSE)
502     {
503         if (($containerId = Tinebase_Model_Container::pathIsContainer($_path) === false)) {
504             throw new Tinebase_Exception_UnexpectedValue ("Invalid path $_path supplied.");
505         }
506     
507         return $this->getContainerById($containerId, $_getDeleted);
508     }
509     
510     /**
511      * return a container by container name
512      *
513      * @param   string $appName app name
514      * @param   int|Tinebase_Model_Container $containerName
515      * @param   string $type
516      * @param   string $ownerId
517      * @return  Tinebase_Model_Container
518      * @throws  Tinebase_Exception_NotFound
519      * @throws  Tinebase_Exception_UnexpectedValue
520      */
521     public function getContainerByName($appName, $containerName, $type, $ownerId = NULL)
522     {
523         if (! in_array($type, array(Tinebase_Model_Container::TYPE_PERSONAL, Tinebase_Model_Container::TYPE_SHARED))) {
524             throw new Tinebase_Exception_UnexpectedValue ("Invalid type $type supplied.");
525         }
526         
527         if ($type == Tinebase_Model_Container::TYPE_PERSONAL && empty($ownerId)) {
528             throw new Tinebase_Exception_UnexpectedValue ('$ownerId can not be empty for personal folders');
529         }
530         
531         $ownerId = $ownerId instanceof Tinebase_Model_User ? $ownerId->getId() : $ownerId;
532         
533         $applicationId = Tinebase_Application::getInstance()->getApplicationByName($appName)->getId();
534
535         $select = $this->_getSelect()
536             ->where("{$this->_db->quoteIdentifier('container.application_id')} = ?", $applicationId)
537             ->where("{$this->_db->quoteIdentifier('container.name')} = ?", $containerName)
538             ->where("{$this->_db->quoteIdentifier('container.type')} = ?", $type)
539             ->where("{$this->_db->quoteIdentifier('container.is_deleted')} = ?", 0, Zend_Db::INT_TYPE);
540
541         if ($type == Tinebase_Model_Container::TYPE_PERSONAL) {
542             $select->where("{$this->_db->quoteIdentifier('owner.account_id')} = ?", $ownerId);
543         }
544         
545         $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
546         $containersData = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
547         
548         if (count($containersData) == 0) {
549             throw new Tinebase_Exception_NotFound("Container $containerName not found.");
550         }
551         if (count($containersData) > 1) {
552             throw new Tinebase_Exception_Duplicate("Container $containerName name duplicate.");
553         }
554
555         $container = new Tinebase_Model_Container($containersData[0]);
556         
557         return $container;
558     }
559     
560     /**
561      * returns the personal container of a given account accessible by a another given account
562      *
563      * @param   string|Tinebase_Model_User          $_accountId
564      * @param   string|Tinebase_Record_Interface    $_recordClass
565      * @param   int|Tinebase_Model_User             $_owner
566      * @param   array|string                        $_grant
567      * @param   bool                                $_ignoreACL
568      * @return  Tinebase_Record_RecordSet of subtype Tinebase_Model_Container
569      * @throws  Tinebase_Exception_NotFound
570      */
571     public function getPersonalContainer($_accountId, $_recordClass, $_owner, $_grant, $_ignoreACL = false)
572     {
573         $meta = $this->_resolveRecordClassArgument($_recordClass);
574         
575         $accountId   = Tinebase_Model_User::convertUserIdToInt($_accountId);
576         $ownerId     = Tinebase_Model_User::convertUserIdToInt($_owner);
577         $grant       = $_ignoreACL ? '*' : $_grant;
578         $application = Tinebase_Application::getInstance()->getApplicationByName($meta['appName']);
579         
580         $classCacheId = convertCacheId(
581             $accountId .
582             $application->getId() .
583             ($meta['recordClass'] ? $meta['recordClass'] : null) .
584             $ownerId .
585             implode('', (array)$grant) .
586             (int)$_ignoreACL
587         );
588         
589         try {
590             return $this->loadFromClassCache(__FUNCTION__, $classCacheId);
591         } catch (Tinebase_Exception_NotFound $tenf) {
592             // continue...
593         }
594         
595         $select = $this->_db->select()
596             ->distinct()
597             ->from(array('owner' => SQL_TABLE_PREFIX . 'container_acl'), array())
598             ->join(array(
599                 /* table  */ 'user' => SQL_TABLE_PREFIX . 'container_acl'), 
600                 /* on     */ "{$this->_db->quoteIdentifier('owner.container_id')} = {$this->_db->quoteIdentifier('user.container_id')}",
601                 /* select */ array()
602             )
603             ->join(array(
604                 /* table  */ 'container' => SQL_TABLE_PREFIX . 'container'), 
605                 /* on     */ "{$this->_db->quoteIdentifier('owner.container_id')} = {$this->_db->quoteIdentifier('container.id')}"
606             )
607             
608             ->where("{$this->_db->quoteIdentifier('owner.account_id')} = ?", $ownerId)
609             ->where("{$this->_db->quoteIdentifier('owner.account_grant')} = ?", Tinebase_Model_Grants::GRANT_ADMIN)
610             
611             ->where("{$this->_db->quoteIdentifier('container.application_id')} = ?", $application->getId())
612             ->where("{$this->_db->quoteIdentifier('container.type')} = ?", Tinebase_Model_Container::TYPE_PERSONAL)
613             ->where("{$this->_db->quoteIdentifier('container.is_deleted')} = ?", 0, Zend_Db::INT_TYPE)
614             
615             ->order('container.name');
616             
617         $this->addGrantsSql($select, $accountId, $grant, 'user');
618         
619         if ($meta['recordClass']) {
620             $select->where("{$this->_db->quoteIdentifier('container.model')} = ?", $meta['recordClass']);
621         }
622         
623         $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
624         $containersData = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
625         
626         // if no containers where found, maybe something went wrong when creating the initial folder
627         // let's check if the controller of the application has a function to create the needed folders
628         if (empty($containersData) and $accountId === $ownerId) {
629             $application = Tinebase_Core::getApplicationInstance($application->name);
630             
631             if ($application instanceof Tinebase_Container_Interface) {
632                 return $application->createPersonalFolder($accountId);
633             }
634         }
635
636         $containers = new Tinebase_Record_RecordSet('Tinebase_Model_Container', $containersData, TRUE);
637         
638         $this->saveInClassCache(__FUNCTION__, $classCacheId, $containers);
639
640         return $containers;
641     }
642     
643     /**
644      * appends container_acl sql 
645      * 
646      * @param  Zend_Db_Select    $_select
647      * @param  String            $_accountId
648      * @param  Array|String      $_grant
649      * @param  String            $_aclTableName
650      * @return void
651      */
652     public static function addGrantsSql($_select, $_accountId, $_grant, $_aclTableName = 'container_acl')
653     {
654         $accountId = $_accountId instanceof Tinebase_Record_Abstract
655             ? $_accountId->getId()
656             : $_accountId;
657         
658         $db = $_select->getAdapter();
659         
660         $grants = is_array($_grant) ? $_grant : array($_grant);
661         
662         // admin grant includes all other grants
663         if (! in_array(Tinebase_Model_Grants::GRANT_ADMIN, $grants)) {
664             $grants[] = Tinebase_Model_Grants::GRANT_ADMIN;
665         }
666
667         $groupMemberships   = Tinebase_Group::getInstance()->getGroupMemberships($accountId);
668         
669         $quotedActId   = $db->quoteIdentifier("{$_aclTableName}.account_id");
670         $quotedActType = $db->quoteIdentifier("{$_aclTableName}.account_type");
671         
672         $accountSelect = new Tinebase_Backend_Sql_Filter_GroupSelect($_select);
673         $accountSelect
674             ->orWhere("{$quotedActId} = ? AND {$quotedActType} = " . $db->quote(Tinebase_Acl_Rights::ACCOUNT_TYPE_USER), $accountId)
675             ->orWhere("{$quotedActId} IN (?) AND {$quotedActType} = " . $db->quote(Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP), empty($groupMemberships) ? ' ' : $groupMemberships);
676         
677         if (! Tinebase_Config::getInstance()->get(Tinebase_Config::ANYONE_ACCOUNT_DISABLED)) {
678             $accountSelect->orWhere("{$quotedActType} = ?", Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE);
679         }
680         $accountSelect->appendWhere(Zend_Db_Select::SQL_AND);
681         
682         // we only need to filter, if the filter does not contain %
683         if (!in_array('*', $grants)) {
684             // @todo fetch wildcard from specific db adapter
685             $grants = str_replace('*', '%', $grants);
686         
687             $quotedGrant   = $db->quoteIdentifier("{$_aclTableName}.account_grant");
688             
689             $grantsSelect = new Tinebase_Backend_Sql_Filter_GroupSelect($_select);
690             foreach ($grants as $grant) {
691                 $grantsSelect->orWhere("{$quotedGrant} LIKE ?", $grant);
692             }
693             $grantsSelect->appendWhere(Zend_Db_Select::SQL_AND);
694         }
695     }
696
697     /**
698      * gets default container of given user for given app
699      *  - did and still does return personal first container by using the application name instead of the recordClass name
700      *  - allows now to use different models with default container in one application
701      *
702      * @param   string|Tinebase_Record_Interface    $recordClass
703      * @param   string|Tinebase_Model_User          $accountId
704      * @param   string                              $defaultContainerPreferenceName
705      * @return  Tinebase_Model_Container
706      * 
707      * @todo get default container name from app/translations?
708      */
709     public function getDefaultContainer($recordClass, $accountId = NULL, $defaultContainerPreferenceName = NULL)
710     {
711         // legacy handling
712         $meta = $this->_resolveRecordClassArgument($recordClass);
713         
714         if ($defaultContainerPreferenceName !== NULL) {
715             $defaultContainerId = Tinebase_Core::getPreference($meta['appName'])->getValue($defaultContainerPreferenceName);
716             try {
717                 $result = $this->getContainerById($defaultContainerId);
718                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
719                     . ' Got default container from preferences: ' . $result->name);
720                 return $result;
721             } catch (Tinebase_Exception $te) {
722                 Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Default container not found (' . $te->getMessage() . ')');
723                 // default may be gone -> remove default adb pref
724                 $appPref = Tinebase_Core::getPreference($this->_applicationName);
725                 if ($appPref) {
726                     $appPref->deleteUserPref($defaultContainerPreferenceName);
727                 }
728             }
729         }
730         
731         $account = ($accountId !== NULL) ? Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountId', $accountId) : Tinebase_Core::getUser();
732         $result = $this->getPersonalContainer($account, $recordClass, $account, Tinebase_Model_Grants::GRANT_ADD)->getFirstRecord();
733         
734         if ($result === NULL) {
735             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ 
736                 . ' Creating new default container for ' . $account->accountFullName . ' in application ' . $meta['appName']);
737             
738             $translation = Tinebase_Translation::getTranslation($meta['appName']);
739             $result = $this->addContainer(new Tinebase_Model_Container(array(
740                 'name'              => sprintf($translation->_("%s's personal container"), $account->accountFullName),
741                 'type'              => Tinebase_Model_Container::TYPE_PERSONAL,
742                 'owner_id'          => $account->getId(),
743                 'backend'           => 'Sql',
744                 'model'             => $meta['recordClass'],
745                 'application_id'    => Tinebase_Application::getInstance()->getApplicationByName($meta['appName'])->getId() 
746             )));
747         }
748         
749         if ($defaultContainerPreferenceName !== NULL) {
750             // save as new pref
751             Tinebase_Core::getPreference($meta['appName'])->setValue($defaultContainerPreferenceName, $result->getId());
752         }
753
754         return $result;
755     }
756     
757     /**
758      * returns the shared container for a given application accessible by the current user
759      *
760      * @param   string|Tinebase_Model_User          $_accountId
761      * @param   string|Tinebase_Model_Application   $_application
762      * @param   array|string                        $_grant
763      * @param   bool                                $_ignoreACL
764      * @return  Tinebase_Record_RecordSet set of Tinebase_Model_Container
765      * @throws  Tinebase_Exception_NotFound
766      */
767     public function getSharedContainer($_accountId, $_application, $_grant, $_ignoreACL = FALSE)
768     {
769         $accountId   = Tinebase_Model_User::convertUserIdToInt($_accountId);
770         $application = Tinebase_Application::getInstance()->getApplicationByName($_application);
771         $grant       = $_ignoreACL ? '*' : $_grant;
772         
773         $classCacheId = convertCacheId(
774             $accountId .
775             $application->getId() .
776             implode('', (array)$grant) .
777             (int)$_ignoreACL
778         );
779         
780         try {
781             return $this->loadFromClassCache(__FUNCTION__, $classCacheId);
782         } catch (Tinebase_Exception_NotFound $tenf) {
783             // continue...
784         }
785         
786         $select = $this->_getSelect()
787             ->distinct()
788             ->join(array(
789                 /* table  */ 'container_acl' => SQL_TABLE_PREFIX . 'container_acl'), 
790                 /* on     */ "{$this->_db->quoteIdentifier('container_acl.container_id')} = {$this->_db->quoteIdentifier('container.id')}",
791                 array()
792             )
793             
794             ->where("{$this->_db->quoteIdentifier('container.application_id')} = ?", $application->getId())
795             ->where("{$this->_db->quoteIdentifier('container.type')} = ?", Tinebase_Model_Container::TYPE_SHARED)
796             
797             ->order('container.name');
798         
799         $this->addGrantsSql($select, $accountId, $grant);
800         
801         $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
802         
803         $containers = new Tinebase_Record_RecordSet('Tinebase_Model_Container', $stmt->fetchAll(Zend_Db::FETCH_ASSOC), TRUE);
804         
805         $this->saveInClassCache(__FUNCTION__, $classCacheId, $containers);
806         
807         Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
808             . ' Found ' . count($containers) . ' shared container(s) in application ' . $application->name);
809         
810         return $containers;
811     }
812     
813     /**
814      * return users which made personal containers accessible to given account
815      *
816      * @param   string|Tinebase_Model_User          $_accountId
817      * @param   string|Tinebase_Model_Application   $_application
818      * @param   array|string                        $_grant
819      * @param   bool                                $_ignoreACL
820      * @return  Tinebase_Record_RecordSet set of Tinebase_Model_User
821      */
822     public function getOtherUsers($_accountId, $_application, $_grant, $_ignoreACL = FALSE)
823     {
824         $userIds = $this->_getOtherAccountIds($_accountId, $_application, $_grant, $_ignoreACL);
825         
826         $users = Tinebase_User::getInstance()->getMultiple($userIds);
827         $users->sort('accountDisplayName');
828         
829         return $users;
830     }
831     
832     /**
833      * return account ids of accounts which made personal container accessible to given account
834      *
835      * @param   string|Tinebase_Model_User          $_accountId
836      * @param   string|Tinebase_Model_Application   $_application
837      * @param   array|string                        $_grant
838      * @param   bool                                $_ignoreACL
839      * @return  array of array of containerData
840      */
841     protected function _getOtherAccountIds($_accountId, $_application, $_grant, $_ignoreACL = FALSE)
842     {
843         $accountId   = Tinebase_Model_User::convertUserIdToInt($_accountId);
844         $application = Tinebase_Application::getInstance()->getApplicationByName($_application);
845         $grant       = $_ignoreACL ? '*' : $_grant;
846         
847         // first grab all container ids ...
848         $select = $this->_db->select()
849             ->distinct()
850             ->from(array('container_acl' => SQL_TABLE_PREFIX . 'container_acl'), array())
851             ->join(array(
852                 /* table  */ 'container' => SQL_TABLE_PREFIX . 'container'), 
853                 /* on     */ "{$this->_db->quoteIdentifier('container_acl.container_id')} = {$this->_db->quoteIdentifier('container.id')}",
854                 /* select */ array('container_id' => 'container.id')
855             )
856             ->where("{$this->_db->quoteIdentifier('container.application_id')} = ?", $application->getId())
857             ->where("{$this->_db->quoteIdentifier('container.type')} = ?", Tinebase_Model_Container::TYPE_PERSONAL)
858             ->where("{$this->_db->quoteIdentifier('container.is_deleted')} = ?", 0, Zend_Db::INT_TYPE);
859             
860         $this->addGrantsSql($select, $accountId, $grant);
861         
862         $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
863         $containerIds = $stmt->fetchAll(Zend_Db::FETCH_COLUMN);
864         
865         // no container ids found / can stop here
866         if (empty($containerIds)) {
867             return $containerIds;
868         }
869         
870         // ... now get the owners of the containers 
871         $select = $this->_db->select()
872             ->distinct()
873             ->from(array('container_acl' => SQL_TABLE_PREFIX . 'container_acl'), array('account_id'))
874             ->join(array(
875                 /* table  */ 'container' => SQL_TABLE_PREFIX . 'container'), 
876                 /* on     */ "{$this->_db->quoteIdentifier('container_acl.container_id')} = {$this->_db->quoteIdentifier('container.id')}",
877                 /* select */ array()
878             )
879             ->join(array(
880                 /* table  */ 'accounts' => SQL_TABLE_PREFIX . 'accounts'),
881                 /* on     */ "{$this->_db->quoteIdentifier('container_acl.account_id')} = {$this->_db->quoteIdentifier('accounts.id')}",
882                 /* select */ array()
883             )
884             ->where("{$this->_db->quoteIdentifier('container.id')} IN (?)", $containerIds)
885             ->where("{$this->_db->quoteIdentifier('container_acl.account_id')} != ?", $accountId)
886             ->where("{$this->_db->quoteIdentifier('container_acl.account_grant')} = ?", Tinebase_Model_Grants::GRANT_ADMIN)
887             ->where("{$this->_db->quoteIdentifier('accounts.status')} = ?", 'enabled');
888             
889         $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
890         $accountIds = $stmt->fetchAll(Zend_Db::FETCH_COLUMN);
891         
892         return $accountIds;
893     }
894     
895     /**
896      * return set of all personal container of other users made accessible to the given account 
897      *
898      * @param   string|Tinebase_Model_User          $_accountId
899      * @param   string|Tinebase_Model_Application   $_application
900      * @param   array|string                        $_grant
901      * @param   bool                                $_ignoreACL
902      * @return  Tinebase_Record_RecordSet set of Tinebase_Model_Container
903      */
904     public function getOtherUsersContainer($_accountId, $_application, $_grant, $_ignoreACL = FALSE)
905     {
906         $result = $this->_getOtherUsersContainerData($_accountId, $_application, $_grant, $_ignoreACL);
907         
908         return $result;
909     }
910     
911     /**
912      * return containerData of containers which made personal accessible to given account
913      *
914      * @param   string|Tinebase_Model_User          $_accountId
915      * @param   string|Tinebase_Model_Application   $_application
916      * @param   array|string                        $_grant
917      * @param   bool                                $_ignoreACL
918      * @return  Tinebase_Record_RecordSet set of Tinebase_Model_Container
919      */
920     protected function _getOtherUsersContainerData($_accountId, $_application, $_grant, $_ignoreACL = FALSE)
921     {
922         $accountId   = Tinebase_Model_User::convertUserIdToInt($_accountId);
923         $application = Tinebase_Application::getInstance()->getApplicationByName($_application);
924         $grant       = $_ignoreACL ? '*' : $_grant;
925         
926         $classCacheId = convertCacheId(
927             $accountId .
928             $application->getId() .
929             implode('', (array)$grant) .
930             (int)$_ignoreACL
931         );
932         
933         try {
934             return $this->loadFromClassCache(__FUNCTION__, $classCacheId);
935         } catch (Tinebase_Exception_NotFound $tenf) {
936             // continue...
937         }
938         
939         $select = $this->_db->select()
940             ->from(array('owner' => SQL_TABLE_PREFIX . 'container_acl'), array('account_id'))
941             ->join(array(
942                 /* table  */ 'user' => SQL_TABLE_PREFIX . 'container_acl'), 
943                 /* on     */ "{$this->_db->quoteIdentifier('owner.container_id')} = {$this->_db->quoteIdentifier('user.container_id')}",
944                 /* select */ array()
945             )
946             ->join(array(
947                 /* table  */ 'container' => SQL_TABLE_PREFIX . 'container'), 
948                 /* on     */ "{$this->_db->quoteIdentifier('owner.container_id')} = {$this->_db->quoteIdentifier('container.id')}"
949             )
950             ->join(array(
951                 /* table  */ 'accounts' => SQL_TABLE_PREFIX . 'accounts'),
952                 /* on     */ "{$this->_db->quoteIdentifier('owner.account_id')} = {$this->_db->quoteIdentifier('accounts.id')}",
953                 /* select */ array()
954             )
955             #->join(array(
956             #    /* table  */ 'contacts' => SQL_TABLE_PREFIX . 'addressbook'),
957             #    /* on     */ "{$this->_db->quoteIdentifier('owner.account_id')} = {$this->_db->quoteIdentifier('contacts.account_id')}",
958             #    /* select */ array()
959             #)
960             ->where("{$this->_db->quoteIdentifier('owner.account_id')} != ?", $accountId)
961             ->where("{$this->_db->quoteIdentifier('owner.account_grant')} = ?", Tinebase_Model_Grants::GRANT_ADMIN)
962             
963             ->where("{$this->_db->quoteIdentifier('container.application_id')} = ?", $application->getId())
964             ->where("{$this->_db->quoteIdentifier('container.type')} = ?", Tinebase_Model_Container::TYPE_PERSONAL)
965             ->where("{$this->_db->quoteIdentifier('container.is_deleted')} = ?", 0, Zend_Db::INT_TYPE)
966             ->where("{$this->_db->quoteIdentifier('accounts.status')} = ?", 'enabled')
967             
968             ->order('accounts.display_name')
969             ->group('owner.account_id');
970         
971         $this->addGrantsSql($select, $accountId, $grant, 'user');
972         
973         Tinebase_Backend_Sql_Abstract::traitGroup($select);
974         
975         $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
976         
977         $containers = new Tinebase_Record_RecordSet('Tinebase_Model_Container', $stmt->fetchAll(Zend_Db::FETCH_ASSOC), TRUE);
978         
979         $this->saveInClassCache(__FUNCTION__, $classCacheId, $containers);
980         
981         return $containers;
982     }
983     
984     /**
985      * delete container if user has the required right
986      *
987      * @param   int|Tinebase_Model_Container $_containerId
988      * @param   boolean $_ignoreAcl
989      * @param   boolean $_tryAgain
990      * @throws  Tinebase_Exception_AccessDenied
991      * @throws  Tinebase_Exception_InvalidArgument
992      * 
993      * @todo move records in deleted container to personal container?
994      */
995     public function deleteContainer($_containerId, $_ignoreAcl = FALSE, $_tryAgain = TRUE)
996     {
997         $containerId = Tinebase_Model_Container::convertContainerIdToInt($_containerId);
998         $container = ($_containerId instanceof Tinebase_Model_Container) ? $_containerId : $this->getContainerById($containerId);
999         $this->checkSystemContainer($containerId);
1000         
1001         $tm = Tinebase_TransactionManager::getInstance();   
1002         $myTransactionId = $tm->startTransaction(Tinebase_Core::getDb());
1003         
1004         $deletedContainer = NULL;
1005         
1006         try {
1007             if($_ignoreAcl !== TRUE) {
1008                 if(!$this->hasGrant(Tinebase_Core::getUser(), $containerId, Tinebase_Model_Grants::GRANT_ADMIN)) {
1009                     throw new Tinebase_Exception_AccessDenied('Permission to delete container denied.');
1010                 }
1011                 
1012                 if($container->type !== Tinebase_Model_Container::TYPE_PERSONAL and $container->type !== Tinebase_Model_Container::TYPE_SHARED) {
1013                     throw new Tinebase_Exception_InvalidArgument('Can delete personal or shared containers only.');
1014                 }
1015             }
1016             $this->deleteContainerContents($container);
1017             $deletedContainer = $this->_setRecordMetaDataAndUpdate($container, 'delete');
1018             
1019         } catch (Exception $e) {
1020             $tm->rollBack();
1021             throw $e;
1022         }
1023         
1024         $tm->commitTransaction($myTransactionId);
1025         
1026         return $deletedContainer;
1027         /*
1028         // move all contained objects to next available personal container and try again to delete container
1029         $app = Tinebase_Application::getApplicationById($container->application_id);
1030
1031         // get personal containers
1032         $personalContainers = $this->getPersonalContainer(
1033             Tinebase_Core::getUser(),
1034             $app->name,
1035             $container->owner,
1036             Tinebase_Model_Grants::GRANT_ADD
1037         );
1038         
1039         //-- determine first matching personal container (or create new one)
1040         // $personalContainer =
1041         
1042         //-- move all records to personal container
1043         
1044         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
1045             . ' Moving all records from container ' . $containerId . ' to personal container ' . $personalContainer->getId()
1046         );
1047         */
1048     }
1049     
1050     /**
1051      * set delete flag of records belonging to the given container
1052      * 
1053      * @param Tinebase_Model_Container $container
1054      */
1055     public function deleteContainerContents($container)
1056     {
1057         // set records belonging to this container to deleted
1058         $model = $container->model;
1059         if ($model) {
1060             $controller = Tinebase_Core::getApplicationInstance($model);
1061             $filterName = $model . 'Filter';
1062             if ($controller && class_exists($filterName)) {
1063                 $filter = new $filterName(array(
1064                     array(
1065                         'field'    => 'container_id',
1066                         'operator' => 'equals',
1067                         'value'    => intval($container->id)
1068                     ),
1069                     array(
1070                         'field'    => 'is_deleted',
1071                         'operator' => 'equals',
1072                         'value'    => 0
1073                     )),
1074                     'AND');
1075                 $controller::getInstance()->deleteByFilter($filter);
1076             }
1077         }
1078     }
1079     
1080     /**
1081      * delete container by application id
1082      * 
1083      * @param string $_applicationId
1084      * @return integer numer of deleted containers 
1085      */
1086     public function deleteContainerByApplicationId($_applicationId)
1087     {
1088         $this->resetClassCache();
1089         
1090         return $this->deleteByProperty($_applicationId, 'application_id');
1091     }
1092     
1093     /**
1094      * set container name, if the user has the required right
1095      *
1096      * @param   int|Tinebase_Model_Container $_containerId
1097      * @param   string $_containerName the new name
1098      * @return  Tinebase_Model_Container
1099      * @throws  Tinebase_Exception_AccessDenied
1100      */
1101     public function setContainerName($_containerId, $_containerName)
1102     {
1103         $container = ($_containerId instanceof Tinebase_Model_Container) ? $_containerId : $this->getContainerById($_containerId);
1104         
1105         if (!$this->hasGrant(Tinebase_Core::getUser(), $container, Tinebase_Model_Grants::GRANT_ADMIN)) {
1106             throw new Tinebase_Exception_AccessDenied('Permission to rename container denied.');
1107         }
1108         
1109         $container->name = $_containerName;
1110         
1111         return $this->_setRecordMetaDataAndUpdate($container, 'update');
1112     }
1113     
1114     /**
1115      * set container color, if the user has the required right
1116      *
1117      * @param   int|Tinebase_Model_Container $_containerId
1118      * @param   string $_color the new color
1119      * @return  Tinebase_Model_Container
1120      * @throws  Tinebase_Exception_AccessDenied
1121      */
1122     public function setContainerColor($_containerId, $_color)
1123     {
1124         $container = ($_containerId instanceof Tinebase_Model_Container) ? $_containerId : $this->getContainerById($_containerId);
1125
1126         if (! $this->hasGrant(Tinebase_Core::getUser(), $container, Tinebase_Model_Grants::GRANT_ADMIN)) {
1127             throw new Tinebase_Exception_AccessDenied('Permission to set color of container denied.');
1128         }
1129         
1130         if (! preg_match('/^#[0-9a-fA-F]{6}$/', $_color)) {
1131             throw new Tinebase_Exception_UnexpectedValue('color is not valid');
1132         }
1133         
1134         $container->color = $_color;
1135         
1136         return $this->_setRecordMetaDataAndUpdate($container, 'update');
1137     }
1138     
1139     /**
1140      * check if the given user user has a certain grant
1141      *
1142      * @param   string|Tinebase_Model_User          $_accountId
1143      * @param   int|Tinebase_Model_Container        $_containerId
1144      * @param   array|string                        $_grant
1145      * @return  boolean
1146      */
1147     public function hasGrant($_accountId, $_containerId, $_grant)
1148     {
1149         $accountId = Tinebase_Model_User::convertUserIdToInt($_accountId);
1150         
1151         try {
1152             $containerId = Tinebase_Model_Container::convertContainerIdToInt($_containerId);
1153         } catch (Tinebase_Exception_InvalidArgument $teia) {
1154             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . $teia->getMessage());
1155             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . $teia->getTraceAsString());
1156             return false;
1157         }
1158         
1159         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
1160             . ' account: ' . $accountId . ' / containerId: ' . $containerId . ' / grant:' . implode('/', (array)$_grant));
1161         
1162         $classCacheId = convertCacheId($accountId . $containerId);
1163         
1164         try {
1165             $allGrants = $this->loadFromClassCache(__FUNCTION__, $classCacheId);
1166         } catch (Tinebase_Exception_NotFound $tenf) {
1167             // NOTE: some tests ask for already deleted container ;-)
1168             $select = $this->_getSelect(array(), true)
1169                 ->distinct()
1170                 ->where("{$this->_db->quoteIdentifier('container.id')} = ?", $containerId)
1171                 ->join(array(
1172                     /* table  */ 'container_acl' => SQL_TABLE_PREFIX . 'container_acl'), 
1173                     /* on     */ "{$this->_db->quoteIdentifier('container_acl.container_id')} = {$this->_db->quoteIdentifier('container.id')}",
1174                     /* select */ array('container_acl.account_grant')
1175                 );
1176                 
1177             $this->addGrantsSql($select, $accountId, '*');
1178             
1179             $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
1180             
1181             $allGrants = $stmt->fetchAll(Zend_Db::FETCH_COLUMN);
1182             $this->saveInClassCache(__FUNCTION__, $classCacheId, $allGrants);
1183         }
1184         
1185         $matchingGrants = array_intersect((array)$_grant, $allGrants);
1186         
1187         return !!count($matchingGrants);
1188     }
1189     
1190     /**
1191      * get all grants assigned to this container
1192      *
1193      * @param   int|Tinebase_Model_Container $_containerId
1194      * @param   bool                         $_ignoreAcl
1195      * @param   string                       $_grantModel
1196      * @return  Tinebase_Record_RecordSet subtype Tinebase_Model_Grants
1197      * @throws  Tinebase_Exception_AccessDenied
1198      */
1199     public function getGrantsOfContainer($_containerId, $_ignoreAcl = FALSE, $_grantModel = 'Tinebase_Model_Grants') 
1200     {
1201         $grants = new Tinebase_Record_RecordSet('Tinebase_Model_Grants');
1202         
1203         $containerId = Tinebase_Model_Container::convertContainerIdToInt($_containerId);
1204         
1205         $select = $this->_getAclSelectByContainerId($containerId)
1206             ->group(array('container_acl.container_id', 'container_acl.account_type', 'container_acl.account_id'));
1207         
1208         Tinebase_Backend_Sql_Abstract::traitGroup($select);
1209         
1210         $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
1211
1212         $grantsData = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
1213         
1214         foreach($grantsData as $grantData) {
1215             $givenGrants = explode(',', $grantData['account_grants']);
1216             foreach($givenGrants as $grant) {
1217                 $grantData[$grant] = TRUE;
1218             }
1219             
1220             $containerGrant = new $_grantModel($grantData, TRUE);
1221
1222             $grants->addRecord($containerGrant);
1223         }
1224         
1225         if ($_ignoreAcl !== TRUE) {
1226             if (TRUE !== $this->hasGrant(Tinebase_Core::getUser()->getId(), $containerId, Tinebase_Model_Grants::GRANT_ADMIN)) {
1227                 throw new Tinebase_Exception_AccessDenied('Permission to get grants of container denied.');
1228             }
1229         }
1230         return $grants;
1231     }
1232     
1233     /**
1234      * get select with acl (grants) by container ID
1235      * 
1236      * @param integer $containerId
1237      * @return Zend_Db_Select
1238      */
1239     protected function _getAclSelectByContainerId($containerId)
1240     {
1241          $select = $this->_db->select()
1242             ->from(
1243                 array('container_acl' => SQL_TABLE_PREFIX . 'container_acl'),
1244                 array('*', 'account_grants' => $this->_dbCommand->getAggregate('container_acl.account_grant'))
1245             )
1246             ->where("{$this->_db->quoteIdentifier('container_acl.container_id')} = ?", $containerId);
1247          return $select;
1248     }
1249     
1250     /**
1251      * get grants assigned to one account of one container
1252      *
1253      * @param   string|Tinebase_Model_User          $_accountId
1254      * @param   int|Tinebase_Model_Container        $_containerId
1255      * @param   string                              $_grantModel
1256      * @return Tinebase_Model_Grants
1257      */
1258     public function getGrantsOfAccount($_accountId, $_containerId, $_grantModel = 'Tinebase_Model_Grants') 
1259     {
1260         $accountId          = Tinebase_Model_User::convertUserIdToInt($_accountId);
1261         $containerId        = Tinebase_Model_Container::convertContainerIdToInt($_containerId);
1262         $container          = ($_containerId instanceof Tinebase_Model_Container) ? $_containerId : $this->getContainerById($_containerId);
1263         
1264         $cacheKey = convertCacheId('getGrantsOfAccount' . $containerId . $accountId . $container->seq);
1265         $cache = Tinebase_Core::getCache();
1266         $grants = $cache->load($cacheKey);
1267         if ($grants === FALSE) {
1268             $select = $this->_getAclSelectByContainerId($containerId)
1269                 ->group('container_acl.account_grant');
1270             
1271             $this->addGrantsSql($select, $accountId, '*');
1272             
1273             Tinebase_Backend_Sql_Abstract::traitGroup($select);
1274             
1275             $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
1276             $rows = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
1277             $grants = $this->_getGrantsFromArray($rows, $accountId, $_grantModel);
1278             
1279             $cache->save($grants, $cacheKey, array('container'), self::ACL_CACHE_TIMEOUT);
1280         }
1281         
1282         return $grants;
1283     }
1284     
1285     /**
1286      * get grants assigned to given account of multiple records
1287      *
1288      * @param   Tinebase_Record_RecordSet   $_records records to get the grants for
1289      * @param   string|Tinebase_Model_User  $_accountId the account to get the grants for
1290      * @param   string                      $_containerProperty container property
1291      * @param   string                      $_grantModel
1292      * @throws  Tinebase_Exception_NotFound
1293      */
1294     public function getGrantsOfRecords(Tinebase_Record_RecordSet $_records, $_accountId, $_containerProperty = 'container_id', $_grantModel = 'Tinebase_Model_Grants')
1295     {
1296         $containers = $this->getContainerGrantsOfRecords($_records, $_accountId, $_containerProperty, $_grantModel);
1297         
1298         if (!$containers) {
1299             return;
1300         }
1301         
1302         // add container & grants to records
1303         foreach ($_records as &$record) {
1304             if (!$containerId = $record->$_containerProperty) {
1305                 continue;
1306             }
1307             
1308             if (! is_array($containerId) && ! $containerId instanceof Tinebase_Record_Abstract && isset($containers[$containerId])) {
1309                 if (isset($containers[$containerId]->path)) {
1310                     $record->$_containerProperty = $containers[$containerId];
1311                 } else {
1312                     // if path is not determinable, skip this container
1313                     // @todo is it correct to remove record from recordSet???
1314                     $_records->removeRecord($record);
1315                 }
1316             }
1317         }
1318     }
1319     
1320     /**
1321      * get grants for containers assigned to given account of multiple records
1322      *
1323      * @param   Tinebase_Record_RecordSet   $_records records to get the grants for
1324      * @param   string|Tinebase_Model_User  $_accountId the account to get the grants for
1325      * @param   string                      $_containerProperty container property
1326      * @param   string                      $_grantModel
1327      * @throws  Tinebase_Exception_NotFound
1328      * @return  array of containers|void
1329      */
1330     public function getContainerGrantsOfRecords(Tinebase_Record_RecordSet $_records, $_accountId, $_containerProperty = 'container_id', $_grantModel = 'Tinebase_Model_Grants')
1331     {
1332         $containerIds = array();
1333         foreach ($_records as $record) {
1334             if (isset($record[$_containerProperty]) && !isset($containerIds[Tinebase_Model_Container::convertContainerIdToInt($record[$_containerProperty])])) {
1335                 $containerIds[Tinebase_Model_Container::convertContainerIdToInt($record[$_containerProperty])] = null;
1336             }
1337         }
1338         
1339         if (empty($containerIds)) {
1340             return array();
1341         }
1342         
1343         $accountId = $_accountId instanceof Tinebase_Record_Abstract
1344             ? $_accountId->getId()
1345             : $_accountId;
1346         
1347         $select = $this->_getSelect('*', TRUE)
1348             ->where("{$this->_db->quoteIdentifier('container.id')} IN (?)", array_keys($containerIds))
1349             ->join(array(
1350                 /* table  */ 'container_acl' => SQL_TABLE_PREFIX . 'container_acl'), 
1351                 /* on     */ "{$this->_db->quoteIdentifier('container_acl.container_id')} = {$this->_db->quoteIdentifier('container.id')}",
1352                 /* select */ array('*', 'account_grants' => $this->_dbCommand->getAggregate('container_acl.account_grant'))
1353             )
1354             ->group('container.id', 'container_acl.account_type', 'container_acl.account_id');
1355             
1356         $this->addGrantsSql($select, $accountId, '*');
1357         
1358         Tinebase_Backend_Sql_Abstract::traitGroup($select);
1359         
1360         $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
1361         $rows = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
1362         
1363         $containers = array();
1364         // add results to container ids and get grants array
1365         foreach ($rows as $row) {
1366             // NOTE id is non-ambiguous
1367             $row['id']   = $row['container_id'];
1368             
1369             $grantsArray = array_unique(explode(',', $row['account_grants']));
1370             $row['account_grants'] = $this->_getGrantsFromArray($grantsArray, $accountId, $_grantModel)->toArray();
1371             
1372             $containers[$row['id']] = new Tinebase_Model_Container($row, TRUE);
1373             
1374             try {
1375                 $containers[$row['id']]->path = $containers[$row['id']]->getPath();
1376             } catch (Exception $e) {
1377                 // @todo is it correct to catch all exceptions here?
1378                 Tinebase_Exception::log($e);
1379             }
1380         }
1381         
1382         return $containers;
1383     }
1384     
1385     /**
1386      * set all grants for given container
1387      *
1388      * @param   int|Tinebase_Model_Container $_containerId
1389      * @param   Tinebase_Record_RecordSet $_grants
1390      * @param   boolean $_ignoreAcl
1391      * @param   boolean $_failSafe don't allow to remove all admin grants for container
1392      * @return  Tinebase_Record_RecordSet subtype Tinebase_Model_Grants
1393      * @throws  Tinebase_Exception_AccessDenied
1394      * @throws  Tinebase_Exception_Backend
1395      * @throws  Tinebase_Exception_Record_NotAllowed
1396      */
1397     public function setGrants($_containerId, Tinebase_Record_RecordSet $_grants, $_ignoreAcl = FALSE, $_failSafe = TRUE) 
1398     {
1399         $containerId = Tinebase_Model_Container::convertContainerIdToInt($_containerId);
1400         
1401         if($_ignoreAcl !== TRUE) {
1402             if(!$this->hasGrant(Tinebase_Core::getUser(), $containerId, Tinebase_Model_Grants::GRANT_ADMIN)) {
1403                 throw new Tinebase_Exception_AccessDenied('Permission to set grants of container denied.');
1404             }
1405         }
1406         
1407         // do failsafe check
1408         if ($_failSafe) {
1409             $adminGrant = FALSE;
1410             foreach ($_grants as $recordGrants) {
1411                 if ($recordGrants->{Tinebase_Model_Grants::GRANT_ADMIN}) {
1412                     $adminGrant = TRUE;
1413                 }
1414             }
1415             if (count($_grants) == 0 || ! $adminGrant) {
1416                 throw new Tinebase_Exception_UnexpectedValue('You are not allowed to remove all (admin) grants for this container.');
1417             }
1418         }
1419         
1420         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Setting grants for container id ' . $containerId . ' ...');
1421         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_grants->toArray(), TRUE));
1422         
1423         try {
1424
1425             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
1426             
1427             $where = $this->_getContainerAclTable()->getAdapter()->quoteInto($this->_db->quoteIdentifier('container_id') . ' = ?', $containerId);
1428             $this->_getContainerAclTable()->delete($where);
1429             
1430             foreach ($_grants as $recordGrants) {
1431                 $data = array(
1432                     'id'            => $recordGrants->getId(),
1433                     'container_id'  => $containerId,
1434                     'account_id'    => $recordGrants['account_id'],
1435                     'account_type'  => $recordGrants['account_type'],
1436                 );
1437                 if (empty($data['id'])) {
1438                     $data['id'] = $recordGrants->generateUID();
1439                 }
1440                 
1441                 foreach ($recordGrants as $grantName => $grant) {
1442                     if (in_array($grantName, $recordGrants->getAllGrants()) && $grant === TRUE) {
1443                         $this->_getContainerAclTable()->insert($data + array('account_grant' => $grantName));
1444                     }
1445                 }
1446             }
1447             
1448             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
1449             
1450             $this->_setRecordMetaDataAndUpdate($containerId, 'update');
1451             
1452         } catch (Exception $e) {
1453             Tinebase_TransactionManager::getInstance()->rollBack();
1454             throw new Tinebase_Exception_Backend($e->getMessage());
1455         }
1456         
1457         return $this->getGrantsOfContainer($containerId, $_ignoreAcl);
1458     }
1459     
1460     /**
1461      * get owner (account_id) of container
1462      * 
1463      * @param Tinebase_Model_Container $_container
1464      * @return string|boolean
1465      */
1466     public function getContainerOwner(Tinebase_Model_Container $_container)
1467     {
1468         if ($_container->type !== Tinebase_Model_Container::TYPE_PERSONAL) {
1469             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
1470                 . ' Only personal containers have an owner.');
1471             return FALSE;
1472         }
1473         
1474         $grants = (! $_container->account_grants) ? $this->getGrantsOfContainer($_container, true) : $_container->account_grants;
1475         
1476         if (count($grants) === 0) {
1477             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
1478                 . ' Container ' . $_container->name . ' has no account grants.');
1479             return FALSE;
1480         }
1481         
1482         // return first admin user
1483         foreach ($grants as $grant) {
1484             if ($grant->{Tinebase_Model_Grants::GRANT_ADMIN} && $grant->account_type == Tinebase_Acl_Rights::ACCOUNT_TYPE_USER) {
1485                 return $grant->account_id;
1486             }
1487         }
1488         
1489         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
1490             __METHOD__ . '::' . __LINE__ . ' Container ' . $_container->name . ' has no owner.');
1491         
1492         return FALSE;
1493     }
1494     
1495     /**
1496      * only one admin is allowed for personal containers
1497      * 
1498      * @param $_container
1499      * @throws Tinebase_Exception_Record_NotAllowed
1500      * @throws Tinebase_Exception_UnexpectedValue
1501      */
1502     public function checkContainerOwner(Tinebase_Model_Container $_container)
1503     {
1504         if ($_container->type !== Tinebase_Model_Container::TYPE_PERSONAL || empty($_container->account_grants)) {
1505             return;
1506         }
1507         
1508         if (! $_container->account_grants instanceof Tinebase_Record_RecordSet) {
1509             throw new Tinebase_Exception_UnexpectedValue('RecordSet of grants expected.');
1510         }
1511
1512         $_container->account_grants->addIndices(array(Tinebase_Model_Grants::GRANT_ADMIN));
1513         $adminGrants = $_container->account_grants->filter(Tinebase_Model_Grants::GRANT_ADMIN, TRUE);
1514         if (count($adminGrants) > 1) {
1515             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Multiple admin grants detected in container "' . $_container->name . '"');
1516             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($adminGrants->toArray(), TRUE));
1517             throw new Tinebase_Exception_Record_NotAllowed('Personal containers can have only one owner!', 403);
1518         }
1519     }
1520     
1521     /**
1522      * remove all container related entries from cache
1523      * 
1524      * @param int|Tinebase_Model_Container $containerId
1525      */
1526     protected function _clearCache($containerId) 
1527     {
1528         $containerId = Tinebase_Model_Container::convertContainerIdToInt($containerId);
1529         $cache = Tinebase_Core::getCache();
1530         
1531         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
1532             . ' Removing all cache entries for container id ' . $containerId);
1533         
1534         $idsToDelete = array(
1535             'getContainerById' . $containerId . 'd0',
1536             'getContainerById' . $containerId . 'd1',
1537         );
1538         
1539         foreach ($idsToDelete as $id) {
1540             $cache->remove($id);
1541         }
1542         
1543         $this->resetClassCache();
1544     }
1545
1546     /**
1547      * lazy loading for containerACLTable
1548      * 
1549      * @return Tinebase_Db_Table
1550      */
1551     protected function _getContainerAclTable()
1552     {
1553         if (! $this->_containerAclTable) {
1554             $this->_containerAclTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'container_acl'));
1555         }
1556         
1557         return $this->_containerAclTable;
1558     }
1559     
1560     /**
1561      * get grants record from an array with grant values
1562      *
1563      * @param array $_grantsArray
1564      * @param int $_accountId
1565      * @param string $_grantModel
1566      * @return Tinebase_Model_Grants (or child class)
1567      */
1568     protected function _getGrantsFromArray(array $_grantsArray, $_accountId, $_grantModel = 'Tinebase_Model_Grants')
1569     {
1570         $grants = array();
1571         foreach($_grantsArray as $key => $value) {
1572             $grantValue = (is_array($value)) ? $value['account_grant'] : $value;
1573             $grants[$grantValue] = TRUE;
1574         }
1575         $grantsFields = array(
1576             'account_id'     => $_accountId,
1577             'account_type'   => Tinebase_Acl_Rights::ACCOUNT_TYPE_USER,
1578         );
1579         $grantsFields = array_merge($grantsFields, $grants);
1580         
1581         $grants = new $_grantModel($grantsFields, TRUE);
1582
1583         return $grants;
1584     }
1585
1586     /**
1587      * increase content sequence of container
1588      * - should be increased for each create/update/delete operation in this container
1589      * 
1590      * @param integer|Tinebase_Model_Container $containerId
1591      * @param string $action
1592      * @param string $recordId
1593      * @return integer new content seq
1594      * 
1595      * @todo clear cache? perhaps not, we have getContentSequence() for that
1596      */
1597     public function increaseContentSequence($containerId, $action = NULL, $recordId = NULL)
1598     {
1599         $containerId = Tinebase_Model_Container::convertContainerIdToInt($containerId);
1600
1601         $newContentSeq = NULL;
1602         try {
1603             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($this->_db);
1604         
1605             $quotedIdentifier = $this->_db->quoteIdentifier('content_seq');
1606             $data = array(
1607                 'content_seq' => new Zend_Db_Expr('(CASE WHEN ' . $quotedIdentifier . ' >= 1 THEN ' . $quotedIdentifier . ' + 1 ELSE 1 END)')
1608             );
1609             $where = array(
1610                 $this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', $containerId)
1611             );
1612             $this->_db->update($this->_tablePrefix . $this->_tableName, $data, $where);
1613             
1614             $newContentSeq = $this->getContentSequence($containerId);
1615             if ($newContentSeq === NULL) {
1616                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
1617                     . ' Something strange happend: content seq of NULL has been detected for container ' . $containerId . ' . Setting it to 0.');
1618                 $newContentSeq = 0;
1619             } else {
1620                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
1621                     . ' Increased content seq of container ' . $containerId . ' to ' . $newContentSeq);
1622             }
1623             
1624             // create new entry in container_content table
1625             if ($action !== NULL && $recordId !== NULL) {
1626                 $contentRecord = new Tinebase_Model_ContainerContent(array(
1627                     'container_id' => $containerId,
1628                     'action'       => $action,
1629                     'record_id'    => $recordId,
1630                     'time'         => Tinebase_DateTime::now(),
1631                     'content_seq'  => $newContentSeq,
1632                 ));
1633                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
1634                     . ' Creating "' . $action . '" action content history record for record id ' . $recordId);
1635                 $this->_getContentBackend()->create($contentRecord);
1636             }
1637             
1638             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
1639             
1640             $this->resetClassCache();
1641             
1642         } catch (Exception $e) {
1643             Tinebase_TransactionManager::getInstance()->rollBack();
1644             throw $e;
1645         }
1646         
1647         return $newContentSeq;
1648     }
1649     
1650     /**
1651      * get content history since given content_seq 
1652      * 
1653      * @param integer|Tinebase_Model_Container $containerId
1654      * @param integer $lastContentSeq
1655      * @return Tinebase_Record_RecordSet
1656      */
1657     public function getContentHistory($containerId, $lastContentSeq = 0)
1658     {
1659         $containerId = Tinebase_Model_Container::convertContainerIdToInt($containerId);
1660         $filter = new Tinebase_Model_ContainerContentFilter(array(
1661             array('field' => 'container_id', 'operator' => 'equals',  'value' => $containerId),
1662             array('field' => 'content_seq',  'operator' => 'greater', 'value' => $lastContentSeq),
1663         ));
1664         $pagination = new Tinebase_Model_Pagination(array(
1665             'sort' => 'content_seq'
1666         ));
1667         $result = $this->_getContentBackend()->search($filter, $pagination);
1668         return $result;
1669     }
1670
1671     /**
1672      * get content sequences for single container or array of ids
1673      * 
1674      * @param array|integer|Tinebase_Model_Container $containerIds
1675      * @return array with key = container id / value = content seq number | integer
1676      */
1677     public function getContentSequence($containerIds)
1678     {
1679         if (empty($containerIds)) {
1680             return NULL;
1681         }
1682         
1683         $containerIds = (! is_array($containerIds)) ? Tinebase_Model_Container::convertContainerIdToInt($containerIds) : $containerIds;
1684         
1685         $select = $this->_getSelect(array('id', 'content_seq'), TRUE);
1686         $select->where($this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' IN (?)', (array) $containerIds));
1687         $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
1688         $result = $stmt->fetchAll();
1689         foreach ($result as $key => $value) {
1690             $result[$value['id']] = $value['content_seq'];
1691         }
1692         
1693         $result = (is_array($containerIds)) ? $result : ((isset($result[$containerIds])) ? $result[$containerIds] : NULL);
1694         return $result;
1695     }
1696     
1697     /**
1698      * checks if container to delete is a "system" container 
1699      * 
1700      * @param array|integer|Tinebase_Model_Container $containerIds
1701      * @throws Tinebase_Exception_Record_SystemContainer
1702      * 
1703      * @TODO: generalize when there are more "system" containers
1704      * @todo move Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK to adb config
1705      */
1706     public function checkSystemContainer($containerIds)
1707     {
1708         if (!is_array($containerIds)) $containerIds = array($containerIds);
1709         $appConfigDefaults = Admin_Controller::getInstance()->getConfigSettings();
1710         // at the moment, just the internal addressbook is checked
1711         try {
1712             $defaultAddressbook = $this->get($appConfigDefaults[Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK])->toArray();
1713         } catch (Tinebase_Exception_NotFound $e) {
1714             $defaultAddressbook = null;
1715         }
1716
1717         if ($defaultAddressbook && in_array($defaultAddressbook['id'], $containerIds)) {
1718             // _('You are not allowed to delete this Container. Please define another container as the default addressbook for internal contacts!')
1719             throw new Tinebase_Exception_Record_SystemContainer('You are not allowed to delete this Container. Please define another container as the default addressbook for internal contacts!');
1720         }
1721     }
1722     
1723     /**
1724      * create a new system container
1725      * - by default user group gets READ grant
1726      * - by default admin group gets all grants
1727      * 
1728      * @param Tinebase_Model_Application|string $application app record, app id or app name
1729      * @param string $name
1730      * @param string $idConfig save id in config if given
1731      * @param Tinebase_Record_RecordSet $grants use this to overwrite default grants
1732      * @param string $model the model the container contains
1733      * @return Tinebase_Model_Container
1734      */
1735     public function createSystemContainer($application, $name, $configId = NULL, Tinebase_Record_RecordSet $grants = NULL, $model = NULL)
1736     {
1737         $application = ($application instanceof Tinebase_Model_Application) ? $application : Tinebase_Application::getInstance()->getApplicationById($application);
1738         if ($model === null) {
1739             $controller = Tinebase_Core::getApplicationInstance($application->name);
1740             $model = $controller->getDefaultModel();
1741         }
1742         
1743         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
1744             . ' Creating system container for model ' . $model);
1745         
1746         $newContainer = new Tinebase_Model_Container(array(
1747             'name'              => $name,
1748             'type'              => Tinebase_Model_Container::TYPE_SHARED,
1749             'backend'           => 'Sql',
1750             'application_id'    => $application->getId(),
1751             'model'             => $model
1752         ));
1753         $groupsBackend = Tinebase_Group::getInstance();
1754         $grants = ($grants) ? $grants : new Tinebase_Record_RecordSet('Tinebase_Model_Grants', array(
1755             array(
1756                 'account_id'      => $groupsBackend->getDefaultGroup()->getId(),
1757                 'account_type'    => Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP,
1758                 Tinebase_Model_Grants::GRANT_READ    => true,
1759                 Tinebase_Model_Grants::GRANT_EXPORT  => true,
1760                 Tinebase_Model_Grants::GRANT_SYNC    => true,
1761             ),
1762             array(
1763                 'account_id'      => $groupsBackend->getDefaultAdminGroup()->getId(),
1764                 'account_type'    => Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP,
1765                 Tinebase_Model_Grants::GRANT_READ    => true,
1766                 Tinebase_Model_Grants::GRANT_ADD     => true,
1767                 Tinebase_Model_Grants::GRANT_EDIT    => true,
1768                 Tinebase_Model_Grants::GRANT_DELETE  => true,
1769                 Tinebase_Model_Grants::GRANT_ADMIN   => true,
1770                 Tinebase_Model_Grants::GRANT_EXPORT  => true,
1771                 Tinebase_Model_Grants::GRANT_SYNC    => true,
1772             ),
1773         ), TRUE);
1774         
1775         $newContainer = Tinebase_Container::getInstance()->addContainer($newContainer, $grants, TRUE);
1776
1777         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
1778             . ' Created new system container ' . $name . ' for application ' . $application->name);
1779         
1780         if ($configId !== NULL) {
1781             $configClass = $application->name . '_Config';
1782             if (@class_exists($configClass)) {
1783                 $config = call_user_func(array($configClass, 'getInstance'));
1784                 
1785                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
1786                     . ' Setting system container config "' . $configId . '" = ' . $newContainer->getId());
1787                 
1788                 $config->set($configId, $newContainer->getId());
1789             } else {
1790                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
1791                     . ' Could not find preferences class ' . $configClass);
1792             }
1793         }
1794         
1795         $this->resetClassCache();
1796         
1797         return $newContainer;
1798     }
1799
1800     /**
1801      * Updates existing container and clears the cache entry of the container
1802      *
1803      * @param Tinebase_Record_Interface $_record
1804      * @return Tinebase_Record_Interface Record|NULL
1805      */
1806     public function update(Tinebase_Record_Interface $_record)
1807     {
1808         $this->_clearCache($_record);
1809         
1810         return parent::update($_record);
1811     }
1812 }