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