35e64d20d0e06cb6ea808e5da3a98c7eb16771ac
[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(array(
1106                     'field' => 'container_id',
1107                     'operator' => 'equals',
1108                     'value' => intval($container->id)
1109                 )), Tinebase_Model_Filter_FilterGroup::CONDITION_AND, array('ignoreAcl' => $_ignoreAcl));
1110
1111                 if ($_ignoreAcl) {
1112
1113                     $idsToDelete = $backend->search($filter, null, /* $_onlyIds */
1114                         true);
1115                     $controller::getInstance()->delete($idsToDelete);
1116                 } else {
1117                     $controller::getInstance()->deleteByFilter($filter);
1118                 }
1119             }
1120         }
1121
1122         if ($_ignoreAcl === TRUE && method_exists($controller, 'doContainerACLChecks')) {
1123             $controller->doContainerACLChecks($acl);
1124         }
1125     }
1126     
1127     /**
1128      * delete container by application id
1129      * 
1130      * @param string $_applicationId
1131      * @return integer numer of deleted containers 
1132      */
1133     public function deleteContainerByApplicationId($_applicationId)
1134     {
1135         $this->resetClassCache();
1136         
1137         return $this->deleteByProperty($_applicationId, 'application_id');
1138     }
1139     
1140     /**
1141      * set container name, if the user has the required right
1142      *
1143      * @param   int|Tinebase_Model_Container $_containerId
1144      * @param   string $_containerName the new name
1145      * @return  Tinebase_Model_Container
1146      * @throws  Tinebase_Exception_AccessDenied
1147      */
1148     public function setContainerName($_containerId, $_containerName)
1149     {
1150         $container = ($_containerId instanceof Tinebase_Model_Container) ? $_containerId : $this->getContainerById($_containerId);
1151         
1152         if (!$this->hasGrant(Tinebase_Core::getUser(), $container, Tinebase_Model_Grants::GRANT_ADMIN)) {
1153             throw new Tinebase_Exception_AccessDenied('Permission to rename container denied.');
1154         }
1155         
1156         $container->name = $_containerName;
1157         
1158         return $this->_setRecordMetaDataAndUpdate($container, 'update');
1159     }
1160     
1161     /**
1162      * set container color, if the user has the required right
1163      *
1164      * @param   int|Tinebase_Model_Container $_containerId
1165      * @param   string $_color the new color
1166      * @return  Tinebase_Model_Container
1167      * @throws  Tinebase_Exception_AccessDenied
1168      */
1169     public function setContainerColor($_containerId, $_color)
1170     {
1171         $container = ($_containerId instanceof Tinebase_Model_Container) ? $_containerId : $this->getContainerById($_containerId);
1172
1173         if (! $this->hasGrant(Tinebase_Core::getUser(), $container, Tinebase_Model_Grants::GRANT_ADMIN)) {
1174             throw new Tinebase_Exception_AccessDenied('Permission to set color of container denied.');
1175         }
1176         
1177         if (! preg_match('/^#[0-9a-fA-F]{6}$/', $_color)) {
1178             throw new Tinebase_Exception_UnexpectedValue('color is not valid');
1179         }
1180         
1181         $container->color = $_color;
1182         
1183         return $this->_setRecordMetaDataAndUpdate($container, 'update');
1184     }
1185     
1186     /**
1187      * check if the given user user has a certain grant
1188      *
1189      * @param   string|Tinebase_Model_User          $_accountId
1190      * @param   int|Tinebase_Model_Container        $_containerId
1191      * @param   array|string                        $_grant
1192      * @return  boolean
1193      */
1194     public function hasGrant($_accountId, $_containerId, $_grant)
1195     {
1196         $accountId = Tinebase_Model_User::convertUserIdToInt($_accountId);
1197         
1198         try {
1199             $containerId = Tinebase_Model_Container::convertContainerIdToInt($_containerId);
1200         } catch (Tinebase_Exception_InvalidArgument $teia) {
1201             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . $teia->getMessage());
1202             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . $teia->getTraceAsString());
1203             return false;
1204         }
1205         
1206         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
1207             . ' account: ' . $accountId . ' / containerId: ' . $containerId . ' / grant:' . implode('/', (array)$_grant));
1208         
1209         $classCacheId = $accountId . $containerId . implode('', (array)$_grant);
1210         
1211         try {
1212             $allGrants = $this->loadFromClassCache(__FUNCTION__, $classCacheId);
1213         } catch (Tinebase_Exception_NotFound $tenf) {
1214             // NOTE: some tests ask for already deleted container ;-)
1215             $select = $this->_getSelect(array(), true)
1216                 ->distinct()
1217                 ->where("{$this->_db->quoteIdentifier('container.id')} = ?", $containerId)
1218                 ->join(array(
1219                     /* table  */ 'container_acl' => SQL_TABLE_PREFIX . 'container_acl'), 
1220                     /* on     */ "{$this->_db->quoteIdentifier('container_acl.container_id')} = {$this->_db->quoteIdentifier('container.id')}",
1221                     /* select */ array('container_acl.account_grant')
1222                 );
1223                 
1224             $this->addGrantsSql($select, $accountId, '*');
1225             
1226             $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
1227             
1228             $allGrants = $stmt->fetchAll(Zend_Db::FETCH_COLUMN);
1229             $this->saveInClassCache(__FUNCTION__, $classCacheId, $allGrants);
1230         }
1231         
1232         $matchingGrants = array_intersect((array)$_grant, $allGrants);
1233         
1234         return !!count($matchingGrants);
1235     }
1236     
1237     /**
1238      * get all grants assigned to this container
1239      *
1240      * @param   int|Tinebase_Model_Container $_containerId
1241      * @param   bool                         $_ignoreAcl
1242      * @param   string                       $_grantModel
1243      * @return  Tinebase_Record_RecordSet subtype Tinebase_Model_Grants
1244      * @throws  Tinebase_Exception_AccessDenied
1245      */
1246     public function getGrantsOfContainer($_containerId, $_ignoreAcl = FALSE, $_grantModel = 'Tinebase_Model_Grants') 
1247     {
1248         $grants = new Tinebase_Record_RecordSet('Tinebase_Model_Grants');
1249         
1250         $containerId = Tinebase_Model_Container::convertContainerIdToInt($_containerId);
1251         
1252         $select = $this->_getAclSelectByContainerId($containerId)
1253             ->group(array('container_acl.container_id', 'container_acl.account_type', 'container_acl.account_id'));
1254         
1255         Tinebase_Backend_Sql_Abstract::traitGroup($select);
1256         
1257         $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
1258
1259         $grantsData = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
1260         
1261         foreach($grantsData as $grantData) {
1262             $givenGrants = explode(',', $grantData['account_grants']);
1263             foreach($givenGrants as $grant) {
1264                 $grantData[$grant] = TRUE;
1265             }
1266             
1267             $containerGrant = new $_grantModel($grantData, TRUE);
1268
1269             $grants->addRecord($containerGrant);
1270         }
1271         
1272         if ($_ignoreAcl !== TRUE) {
1273             if (TRUE !== $this->hasGrant(Tinebase_Core::getUser()->getId(), $containerId, Tinebase_Model_Grants::GRANT_ADMIN)) {
1274                 throw new Tinebase_Exception_AccessDenied('Permission to get grants of container denied.');
1275             }
1276         }
1277         return $grants;
1278     }
1279     
1280     /**
1281      * get select with acl (grants) by container ID
1282      * 
1283      * @param integer $containerId
1284      * @return Zend_Db_Select
1285      */
1286     protected function _getAclSelectByContainerId($containerId)
1287     {
1288          $select = $this->_db->select()
1289             ->from(
1290                 array('container_acl' => SQL_TABLE_PREFIX . 'container_acl'),
1291                 array('*', 'account_grants' => $this->_dbCommand->getAggregate('container_acl.account_grant'))
1292             )
1293             ->where("{$this->_db->quoteIdentifier('container_acl.container_id')} = ?", $containerId);
1294          return $select;
1295     }
1296     
1297     /**
1298      * get grants assigned to one account of one container
1299      *
1300      * @param   string|Tinebase_Model_User          $_accountId
1301      * @param   int|Tinebase_Model_Container        $_containerId
1302      * @param   string                              $_grantModel
1303      * @return Tinebase_Model_Grants
1304      */
1305     public function getGrantsOfAccount($_accountId, $_containerId, $_grantModel = 'Tinebase_Model_Grants') 
1306     {
1307         $accountId          = Tinebase_Model_User::convertUserIdToInt($_accountId);
1308         $containerId        = Tinebase_Model_Container::convertContainerIdToInt($_containerId);
1309         $container          = ($_containerId instanceof Tinebase_Model_Container) ? $_containerId : $this->getContainerById($_containerId);
1310         
1311         $classCacheId = $accountId . $containerId . $container->seq . $_grantModel;
1312         
1313         try {
1314             $grants = $this->loadFromClassCache(__FUNCTION__, $classCacheId, Tinebase_Cache_PerRequest::VISIBILITY_SHARED);
1315             if ($grants instanceof Tinebase_Model_Grants) {
1316                 return $grants;
1317             } else {
1318                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ .
1319                     ' Invalid data in cache ... fetching fresh data from DB');
1320             }
1321         } catch (Tinebase_Exception_NotFound $tenf) {
1322             // not found in cache
1323         }
1324         
1325         $select = $this->_getAclSelectByContainerId($containerId)
1326             ->group('container_acl.account_grant');
1327         
1328         $this->addGrantsSql($select, $accountId, '*');
1329         
1330         Tinebase_Backend_Sql_Abstract::traitGroup($select);
1331         
1332         $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
1333         
1334         $rows = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
1335         
1336         $grants = $this->_getGrantsFromArray($rows, $accountId, $_grantModel);
1337         
1338         $this->saveInClassCache(__FUNCTION__, $classCacheId, $grants, Tinebase_Cache_PerRequest::VISIBILITY_SHARED, self::ACL_CACHE_TIMEOUT);
1339         
1340         return $grants;
1341     }
1342     
1343     /**
1344      * get grants assigned to given account of multiple records
1345      *
1346      * @param   Tinebase_Record_RecordSet   $_records records to get the grants for
1347      * @param   string|Tinebase_Model_User  $_accountId the account to get the grants for
1348      * @param   string                      $_containerProperty container property
1349      * @param   string                      $_grantModel
1350      * @throws  Tinebase_Exception_NotFound
1351      */
1352     public function getGrantsOfRecords(Tinebase_Record_RecordSet $_records, $_accountId, $_containerProperty = 'container_id', $_grantModel = 'Tinebase_Model_Grants')
1353     {
1354         $containers = $this->getContainerGrantsOfRecords($_records, $_accountId, $_containerProperty, $_grantModel);
1355         
1356         if (!$containers) {
1357             return;
1358         }
1359         
1360         // add container & grants to records
1361         foreach ($_records as &$record) {
1362             if (!$containerId = $record->$_containerProperty) {
1363                 continue;
1364             }
1365             
1366             if (! is_array($containerId) && ! $containerId instanceof Tinebase_Record_Abstract && isset($containers[$containerId])) {
1367                 if (isset($containers[$containerId]->path)) {
1368                     $record->$_containerProperty = $containers[$containerId];
1369                 } else {
1370                     // if path is not determinable, skip this container
1371                     // @todo is it correct to remove record from recordSet???
1372                     $_records->removeRecord($record);
1373                 }
1374             }
1375         }
1376     }
1377     
1378     /**
1379      * get grants for containers assigned to given account of multiple records
1380      *
1381      * @param   Tinebase_Record_RecordSet   $_records records to get the grants for
1382      * @param   string|Tinebase_Model_User  $_accountId the account to get the grants for
1383      * @param   string                      $_containerProperty container property
1384      * @param   string                      $_grantModel
1385      * @throws  Tinebase_Exception_NotFound
1386      * @return  array of containers|void
1387      */
1388     public function getContainerGrantsOfRecords(Tinebase_Record_RecordSet $_records, $_accountId, $_containerProperty = 'container_id', $_grantModel = 'Tinebase_Model_Grants')
1389     {
1390         $containerIds = array();
1391         foreach ($_records as $record) {
1392             if (isset($record[$_containerProperty]) && !isset($containerIds[Tinebase_Model_Container::convertContainerIdToInt($record[$_containerProperty])])) {
1393                 $containerIds[Tinebase_Model_Container::convertContainerIdToInt($record[$_containerProperty])] = null;
1394             }
1395         }
1396         
1397         if (empty($containerIds)) {
1398             return array();
1399         }
1400         
1401         $accountId = $_accountId instanceof Tinebase_Record_Abstract
1402             ? $_accountId->getId()
1403             : $_accountId;
1404         
1405         $select = $this->_getSelect('*', TRUE)
1406             ->where("{$this->_db->quoteIdentifier('container.id')} IN (?)", array_keys($containerIds))
1407             ->join(array(
1408                 /* table  */ 'container_acl' => SQL_TABLE_PREFIX . 'container_acl'), 
1409                 /* on     */ "{$this->_db->quoteIdentifier('container_acl.container_id')} = {$this->_db->quoteIdentifier('container.id')}",
1410                 /* select */ array('*', 'account_grants' => $this->_dbCommand->getAggregate('container_acl.account_grant'))
1411             )
1412             ->group('container.id', 'container_acl.account_type', 'container_acl.account_id');
1413             
1414         $this->addGrantsSql($select, $accountId, '*');
1415         
1416         Tinebase_Backend_Sql_Abstract::traitGroup($select);
1417         
1418         $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
1419         $rows = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
1420         
1421         $containers = array();
1422         // add results to container ids and get grants array
1423         foreach ($rows as $row) {
1424             // NOTE id is non-ambiguous
1425             $row['id']   = $row['container_id'];
1426             
1427             $grantsArray = array_unique(explode(',', $row['account_grants']));
1428             $row['account_grants'] = $this->_getGrantsFromArray($grantsArray, $accountId, $_grantModel)->toArray();
1429             
1430             $containers[$row['id']] = new Tinebase_Model_Container($row, TRUE);
1431             
1432             try {
1433                 $containers[$row['id']]->path = $containers[$row['id']]->getPath();
1434             } catch (Exception $e) {
1435                 // @todo is it correct to catch all exceptions here?
1436                 Tinebase_Exception::log($e);
1437             }
1438         }
1439         
1440         return $containers;
1441     }
1442     
1443     /**
1444      * set all grants for given container
1445      *
1446      * @param   int|Tinebase_Model_Container $_containerId
1447      * @param   Tinebase_Record_RecordSet $_grants
1448      * @param   boolean $_ignoreAcl
1449      * @param   boolean $_failSafe don't allow to remove all admin grants for container
1450      * @return  Tinebase_Record_RecordSet subtype Tinebase_Model_Grants
1451      * @throws  Tinebase_Exception_AccessDenied
1452      * @throws  Tinebase_Exception_Backend
1453      * @throws  Tinebase_Exception_Record_NotAllowed
1454      */
1455     public function setGrants($_containerId, Tinebase_Record_RecordSet $_grants, $_ignoreAcl = FALSE, $_failSafe = TRUE) 
1456     {
1457         $containerId = Tinebase_Model_Container::convertContainerIdToInt($_containerId);
1458         
1459         if($_ignoreAcl !== TRUE) {
1460             if(!$this->hasGrant(Tinebase_Core::getUser(), $containerId, Tinebase_Model_Grants::GRANT_ADMIN)) {
1461                 throw new Tinebase_Exception_AccessDenied('Permission to set grants of container denied.');
1462             }
1463         }
1464         
1465         // do failsafe check
1466         if ($_failSafe) {
1467             $adminGrant = FALSE;
1468             foreach ($_grants as $recordGrants) {
1469                 if ($recordGrants->{Tinebase_Model_Grants::GRANT_ADMIN}) {
1470                     $adminGrant = TRUE;
1471                 }
1472             }
1473             if (count($_grants) == 0 || ! $adminGrant) {
1474                 throw new Tinebase_Exception_UnexpectedValue('You are not allowed to remove all (admin) grants for this container.');
1475             }
1476         }
1477         
1478         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Setting grants for container id ' . $containerId . ' ...');
1479         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_grants->toArray(), TRUE));
1480         
1481         try {
1482
1483             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
1484             
1485             $where = $this->_getContainerAclTable()->getAdapter()->quoteInto($this->_db->quoteIdentifier('container_id') . ' = ?', $containerId);
1486             $this->_getContainerAclTable()->delete($where);
1487             
1488             foreach ($_grants as $recordGrants) {
1489                 $data = array(
1490                     'id'            => $recordGrants->getId(),
1491                     'container_id'  => $containerId,
1492                     'account_id'    => $recordGrants['account_id'],
1493                     'account_type'  => $recordGrants['account_type'],
1494                 );
1495                 if (empty($data['id'])) {
1496                     $data['id'] = $recordGrants->generateUID();
1497                 }
1498                 
1499                 foreach ($recordGrants as $grantName => $grant) {
1500                     if (in_array($grantName, $recordGrants->getAllGrants()) && $grant === TRUE) {
1501                         $this->_getContainerAclTable()->insert($data + array('account_grant' => $grantName));
1502                     }
1503                 }
1504             }
1505             
1506             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
1507             
1508             $this->_setRecordMetaDataAndUpdate($containerId, 'update');
1509             
1510         } catch (Exception $e) {
1511             Tinebase_TransactionManager::getInstance()->rollBack();
1512             throw new Tinebase_Exception_Backend($e->getMessage());
1513         }
1514         
1515         return $this->getGrantsOfContainer($containerId, $_ignoreAcl);
1516     }
1517     
1518     /**
1519      * get owner (account_id) of container
1520      * 
1521      * @param Tinebase_Model_Container $_container
1522      * @return string|boolean
1523      */
1524     public function getContainerOwner(Tinebase_Model_Container $_container)
1525     {
1526         if ($_container->type !== Tinebase_Model_Container::TYPE_PERSONAL) {
1527             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
1528                 . ' Only personal containers have an owner.');
1529             return FALSE;
1530         }
1531
1532         if ($_container->owner_id) {
1533             return $_container->owner_id;
1534         }
1535         
1536         $grants = (! $_container->account_grants) ? $this->getGrantsOfContainer($_container, true) : $_container->account_grants;
1537         
1538         if (count($grants) === 0) {
1539             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
1540                 . ' Container ' . $_container->name . ' has no account grants.');
1541             return FALSE;
1542         }
1543         
1544         // return first admin user
1545         foreach ($grants as $grant) {
1546             if ($grant->{Tinebase_Model_Grants::GRANT_ADMIN} && $grant->account_type == Tinebase_Acl_Rights::ACCOUNT_TYPE_USER) {
1547                 return $grant->account_id;
1548             }
1549         }
1550         
1551         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
1552             __METHOD__ . '::' . __LINE__ . ' Container ' . $_container->name . ' has no owner.');
1553         
1554         return FALSE;
1555     }
1556     
1557     /**
1558      * only one admin is allowed for personal containers
1559      * 
1560      * @param $_container
1561      * @throws Tinebase_Exception_Record_NotAllowed
1562      * @throws Tinebase_Exception_UnexpectedValue
1563      *
1564      * @deprecated: could be removed because we have an owner property and could have multiple admins for a personal container now
1565      */
1566     public function checkContainerOwner(Tinebase_Model_Container $_container)
1567     {
1568         if ($_container->type !== Tinebase_Model_Container::TYPE_PERSONAL || empty($_container->account_grants)) {
1569             return;
1570         }
1571         
1572         if (! $_container->account_grants instanceof Tinebase_Record_RecordSet) {
1573             throw new Tinebase_Exception_UnexpectedValue('RecordSet of grants expected.');
1574         }
1575
1576         $_container->account_grants->addIndices(array(Tinebase_Model_Grants::GRANT_ADMIN));
1577         $adminGrants = $_container->account_grants->filter(Tinebase_Model_Grants::GRANT_ADMIN, TRUE);
1578         if (count($adminGrants) > 1) {
1579             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Multiple admin grants detected in container "' . $_container->name . '"');
1580             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($adminGrants->toArray(), TRUE));
1581             throw new Tinebase_Exception_Record_NotAllowed('Personal containers can have only one owner!', 403);
1582         }
1583     }
1584     
1585     /**
1586      * remove all container related entries from cache
1587      * 
1588      * @param int|Tinebase_Model_Container $containerId
1589      */
1590     protected function _clearCache($containerId) 
1591     {
1592         $containerId = Tinebase_Model_Container::convertContainerIdToInt($containerId);
1593         $cache = Tinebase_Core::getCache();
1594         
1595         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
1596             . ' Removing all cache entries for container id ' . $containerId);
1597         
1598         $idsToDelete = array(
1599             'getContainerById' . $containerId . 'd0',
1600             'getContainerById' . $containerId . 'd1',
1601         );
1602         
1603         foreach ($idsToDelete as $id) {
1604             $cache->remove($id);
1605         }
1606         
1607         $this->resetClassCache();
1608     }
1609
1610     /**
1611      * lazy loading for containerACLTable
1612      * 
1613      * @return Tinebase_Db_Table
1614      */
1615     protected function _getContainerAclTable()
1616     {
1617         if (! $this->_containerAclTable) {
1618             $this->_containerAclTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'container_acl'));
1619         }
1620         
1621         return $this->_containerAclTable;
1622     }
1623     
1624     /**
1625      * get grants record from an array with grant values
1626      *
1627      * @param array $_grantsArray
1628      * @param int $_accountId
1629      * @param string $_grantModel
1630      * @return Tinebase_Model_Grants (or child class)
1631      */
1632     protected function _getGrantsFromArray(array $_grantsArray, $_accountId, $_grantModel = 'Tinebase_Model_Grants')
1633     {
1634         $grants = array();
1635         foreach($_grantsArray as $key => $value) {
1636             $grantValue = (is_array($value)) ? $value['account_grant'] : $value;
1637             $grants[$grantValue] = TRUE;
1638         }
1639         $grantsFields = array(
1640             'account_id'     => $_accountId,
1641             'account_type'   => Tinebase_Acl_Rights::ACCOUNT_TYPE_USER,
1642         );
1643         $grantsFields = array_merge($grantsFields, $grants);
1644         
1645         $grants = new $_grantModel($grantsFields, TRUE);
1646
1647         return $grants;
1648     }
1649
1650     /**
1651      * increase content sequence of container
1652      * - should be increased for each create/update/delete operation in this container
1653      * 
1654      * @param integer|Tinebase_Model_Container $containerId
1655      * @param string $action
1656      * @param string $recordId
1657      * @return integer new content seq
1658      */
1659     public function increaseContentSequence($containerId, $action = NULL, $recordId = NULL)
1660     {
1661         $containerId = Tinebase_Model_Container::convertContainerIdToInt($containerId);
1662
1663         $newContentSeq = NULL;
1664         try {
1665             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($this->_db);
1666         
1667             $quotedIdentifier = $this->_db->quoteIdentifier('content_seq');
1668             $data = array(
1669                 'content_seq' => new Zend_Db_Expr('(CASE WHEN ' . $quotedIdentifier . ' >= 1 THEN ' . $quotedIdentifier . ' + 1 ELSE 1 END)')
1670             );
1671             $where = array(
1672                 $this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', $containerId)
1673             );
1674             $this->_db->update($this->_tablePrefix . $this->_tableName, $data, $where);
1675             
1676             $newContentSeq = $this->getContentSequence($containerId);
1677             if ($newContentSeq === NULL) {
1678                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
1679                     . ' Something strange happend: content seq of NULL has been detected for container ' . $containerId . ' . Setting it to 0.');
1680                 $newContentSeq = 0;
1681             } else {
1682                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
1683                     . ' Increased content seq of container ' . $containerId . ' to ' . $newContentSeq);
1684             }
1685             
1686             // create new entry in container_content table
1687             if ($action !== NULL && $recordId !== NULL) {
1688                 $contentRecord = new Tinebase_Model_ContainerContent(array(
1689                     'container_id' => $containerId,
1690                     'action'       => $action,
1691                     'record_id'    => $recordId,
1692                     'time'         => Tinebase_DateTime::now(),
1693                     'content_seq'  => $newContentSeq,
1694                 ));
1695                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
1696                     . ' Creating "' . $action . '" action content history record for record id ' . $recordId);
1697                 $this->getContentBackend()->create($contentRecord);
1698             }
1699             
1700             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
1701             
1702             $this->resetClassCache();
1703             
1704         } catch (Exception $e) {
1705             Tinebase_TransactionManager::getInstance()->rollBack();
1706             throw $e;
1707         }
1708         
1709         return $newContentSeq;
1710     }
1711     
1712     /**
1713      * get content history since given content_seq 
1714      * 
1715      * @param integer|Tinebase_Model_Container $containerId
1716      * @param integer $lastContentSeq
1717      * @return Tinebase_Record_RecordSet
1718      */
1719     public function getContentHistory($containerId, $lastContentSeq = 0)
1720     {
1721         $filter = new Tinebase_Model_ContainerContentFilter(array(
1722             array('field' => 'container_id', 'operator' => 'equals',  'value' => Tinebase_Model_Container::convertContainerIdToInt($containerId)),
1723             array('field' => 'content_seq',  'operator' => 'greater', 'value' => $lastContentSeq),
1724         ));
1725         $pagination = new Tinebase_Model_Pagination(array(
1726             'sort' => 'content_seq'
1727         ));
1728         $result = $this->getContentBackend()->search($filter, $pagination);
1729         return $result;
1730     }
1731
1732     /**
1733      * get content sequences for single container or array of ids
1734      * 
1735      * @param array|integer|Tinebase_Model_Container $containerIds
1736      * @return array with key = container id / value = content seq number | integer
1737      */
1738     public function getContentSequence($containerIds)
1739     {
1740         if (empty($containerIds)) {
1741             return NULL;
1742         }
1743         
1744         $containerIds = (! is_array($containerIds)) ? Tinebase_Model_Container::convertContainerIdToInt($containerIds) : $containerIds;
1745         
1746         $select = $this->_getSelect(array('id', 'content_seq'), TRUE);
1747         $select->where($this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' IN (?)', (array) $containerIds));
1748         $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
1749         $result = $stmt->fetchAll();
1750         foreach ($result as $key => $value) {
1751             $result[$value['id']] = $value['content_seq'];
1752         }
1753         
1754         $result = (is_array($containerIds)) ? $result : ((isset($result[$containerIds])) ? $result[$containerIds] : NULL);
1755         return $result;
1756     }
1757     
1758     /**
1759      * checks if container to delete is a "system" container 
1760      * 
1761      * @param array|integer|Tinebase_Model_Container $containerIds
1762      * @throws Tinebase_Exception_Record_SystemContainer
1763      * 
1764      * @TODO: generalize when there are more "system" containers
1765      * @todo move Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK to adb config
1766      */
1767     public function checkSystemContainer($containerIds)
1768     {
1769         if (!is_array($containerIds)) $containerIds = array($containerIds);
1770         $appConfigDefaults = Admin_Controller::getInstance()->getConfigSettings();
1771         // at the moment, just the internal addressbook is checked
1772         try {
1773             $defaultAddressbook = $this->get($appConfigDefaults[Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK])->toArray();
1774         } catch (Tinebase_Exception_NotFound $e) {
1775             $defaultAddressbook = null;
1776         }
1777
1778         if ($defaultAddressbook && in_array($defaultAddressbook['id'], $containerIds)) {
1779             // _('You are not allowed to delete this Container. Please define another container as the default addressbook for internal contacts!')
1780             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!');
1781         }
1782     }
1783     
1784     /**
1785      * create a new system container
1786      * - by default user group gets READ grant
1787      * - by default admin group gets all grants
1788      *
1789      * NOTE: this should never be called in user land and only in admin/setup contexts
1790      * 
1791      * @param Tinebase_Model_Application|string $application app record, app id or app name
1792      * @param string $name
1793      * @param string $idConfig save id in config if given
1794      * @param Tinebase_Record_RecordSet $grants use this to overwrite default grants
1795      * @param string $model the model the container contains
1796      * @return Tinebase_Model_Container
1797      */
1798     public function createSystemContainer($application, $name, $configId = NULL, Tinebase_Record_RecordSet $grants = NULL, $model = NULL)
1799     {
1800         $application = ($application instanceof Tinebase_Model_Application) ? $application : Tinebase_Application::getInstance()->getApplicationById($application);
1801         if ($model === null) {
1802             $controller = Tinebase_Core::getApplicationInstance($application->name, /* $_modelName = */ '', /* $_ignoreACL = */ true);
1803             $model = $controller->getDefaultModel();
1804         }
1805         
1806         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
1807             . ' Creating system container for model ' . $model);
1808         
1809         $newContainer = new Tinebase_Model_Container(array(
1810             'name'              => $name,
1811             'type'              => Tinebase_Model_Container::TYPE_SHARED,
1812             'backend'           => 'Sql',
1813             'application_id'    => $application->getId(),
1814             'model'             => $model
1815         ));
1816         $groupsBackend = Tinebase_Group::getInstance();
1817         $grants = ($grants) ? $grants : new Tinebase_Record_RecordSet('Tinebase_Model_Grants', array(
1818             array(
1819                 'account_id'      => $groupsBackend->getDefaultGroup()->getId(),
1820                 'account_type'    => Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP,
1821                 Tinebase_Model_Grants::GRANT_READ    => true,
1822                 Tinebase_Model_Grants::GRANT_EXPORT  => true,
1823                 Tinebase_Model_Grants::GRANT_SYNC    => true,
1824             ),
1825             array(
1826                 'account_id'      => $groupsBackend->getDefaultAdminGroup()->getId(),
1827                 'account_type'    => Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP,
1828                 Tinebase_Model_Grants::GRANT_READ    => true,
1829                 Tinebase_Model_Grants::GRANT_ADD     => true,
1830                 Tinebase_Model_Grants::GRANT_EDIT    => true,
1831                 Tinebase_Model_Grants::GRANT_DELETE  => true,
1832                 Tinebase_Model_Grants::GRANT_ADMIN   => true,
1833                 Tinebase_Model_Grants::GRANT_EXPORT  => true,
1834                 Tinebase_Model_Grants::GRANT_SYNC    => true,
1835             ),
1836         ), TRUE);
1837         
1838         $newContainer = Tinebase_Container::getInstance()->addContainer($newContainer, $grants, TRUE);
1839
1840         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
1841             . ' Created new system container ' . $name . ' for application ' . $application->name);
1842         
1843         if ($configId !== NULL) {
1844             $configClass = $application->name . '_Config';
1845             if (@class_exists($configClass)) {
1846                 $config = call_user_func(array($configClass, 'getInstance'));
1847                 
1848                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
1849                     . ' Setting system container config "' . $configId . '" = ' . $newContainer->getId());
1850                 
1851                 $config->set($configId, $newContainer->getId());
1852             } else {
1853                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
1854                     . ' Could not find preferences class ' . $configClass);
1855             }
1856         }
1857         
1858         $this->resetClassCache();
1859         
1860         return $newContainer;
1861     }
1862
1863     /**
1864      * Updates existing container and clears the cache entry of the container
1865      *
1866      * @param Tinebase_Record_Interface $_record
1867      * @return Tinebase_Record_Interface Record|NULL
1868      */
1869     public function update(Tinebase_Record_Interface $_record)
1870     {
1871         $this->_clearCache($_record);
1872         
1873         return parent::update($_record);
1874     }
1875 }