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