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