add inClassCache for Tinebase_Acl_Roles class
[tine20] / tine20 / Tinebase / Acl / Roles.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  Acl
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2007-2013 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Philipp Schuele <p.schuele@metaways.de>
10  * 
11  * @todo        extend/use sql abstract backend
12  */
13
14 /**
15  * this class handles the roles
16  * 
17  * @package     Tinebase
18  * @subpackage  Acl
19  */
20 class Tinebase_Acl_Roles
21 {
22     /**
23      * @var Zend_Db_Adapter_Pdo_Mysql
24      */
25     protected $_db;
26     
27     /**\r
28      * @var Tinebase_Backend_Sql_Command_Interface\r
29      */\r
30     protected $_dbCommand;\r
31     
32     /**
33      * the Zend_Dd_Table object
34      *
35      * @var Tinebase_Db_Table
36      */
37     protected $_rolesTable;
38     
39     /**
40      * the Zend_Dd_Table object for role members
41      *
42      * @var Tinebase_Db_Table
43      */
44     protected $_roleMembersTable;
45
46     /**
47      * the Zend_Dd_Table object for role rights
48      *
49      * @var Tinebase_Db_Table
50      */
51     protected $_roleRightsTable;
52         
53     protected $_classCache = array(
54         'getRoleMemberships' => array(),
55         'hasRight'           => array(),
56     );
57     
58     /**
59      * holdes the _instance of the singleton
60      *
61      * @var Tinebase_Acl_Roles
62      */
63     private static $_instance = NULL;
64     
65     /**
66      * the clone function
67      *
68      * disabled. use the singleton
69      */
70     private function __clone() 
71     {
72     }
73     
74     /**
75      * the constructor
76      *
77      * disabled. use the singleton
78      * temporarly the constructor also creates the needed tables on demand and fills them with some initial values
79      */
80     private function __construct() {
81
82         $this->_rolesTable       = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'roles'));
83         $this->_roleMembersTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'role_accounts'));
84         $this->_roleRightsTable  = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'role_rights'));
85         
86         $this->_db        = Tinebase_Core::getDb();
87         $this->_dbCommand = Tinebase_Backend_Sql_Command::factory($this->_db);
88     }    
89     
90     /**
91      * the singleton pattern
92      *
93      * @return Tinebase_Acl_Roles
94      */
95     public static function getInstance() 
96     {
97         if (self::$_instance === NULL) {
98             self::$_instance = new Tinebase_Acl_Roles;
99         }
100         
101         return self::$_instance;
102     }        
103
104     /**
105      * check if one of the roles the user is in has a given right for a given application
106      * 
107      * we read all right for the given user at once and cache them in the internal class cache
108      *
109      * @param   string|Tinebase_Model_Application $_application the application (one of: app name, id or record)
110      * @param   int $_accountId the numeric id of a user account
111      * @param   int $_right the right to check for
112      * @return  bool
113      */
114     public function hasRight($_application, $_accountId, $_right)
115     {
116         try {
117             $application = Tinebase_Application::getInstance()->getApplicationById($_application);
118         } catch (Tinebase_Exception_NotFound $tenf) {
119             return false;
120         }
121         
122         if ($application->status !== Tinebase_Application::ENABLED) {
123             return false;
124         }
125         
126         try {
127             $roleMemberships = $this->getRoleMemberships($_accountId);
128         } catch (Tinebase_Exception_NotFound $tenf) {
129             $roleMemberships = array();
130         }
131         
132         if (empty($roleMemberships)) {
133             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' ' . $_accountId . ' has no role/group memberships.');
134             if (is_object(Tinebase_Core::getUser()) && Tinebase_Core::getUser()->getId() === $_accountId) {
135                 // @todo throw exception in this case?
136                 Zend_Session::destroy();
137             }
138             
139             return false;
140         }
141         
142         $classCacheId = convertCacheId(implode('', $roleMemberships));
143         
144         if (!isset($this->_classCache[__FUNCTION__][$classCacheId])) {
145             $select = $this->_db->select()
146                 ->distinct()
147                 ->from(array('role_rights' => SQL_TABLE_PREFIX . 'role_rights'), array('application_id', 'right'))
148                 ->where($this->_db->quoteIdentifier('role_id') . ' IN (?)', $roleMemberships);
149                 
150             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $select->__toString());
151             
152             $stmt = $this->_db->query($select);
153             $rows = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
154             
155             $rights = array();
156             
157             foreach ($rows as $row) {
158                 $rights[$row['application_id']][$row['right']] = true;
159             }
160             
161             $this->_classCache[__FUNCTION__][$classCacheId] = $rights;
162         } else {
163             $rights = $this->_classCache[__FUNCTION__][$classCacheId];
164         }
165         
166         $applicationId = $application->getId();
167         
168         return isset($rights[$applicationId]) && (isset($rights[$applicationId][$_right]) || isset($rights[$applicationId][Tinebase_Acl_Rights::ADMIN]));
169     }
170     
171     /**
172      * returns list of applications the user is able to use
173      *
174      * this function takes group memberships into account. Applications the accounts is able to use
175      * must have any (was: the 'run') right set and the application must be enabled
176      * 
177      * @param   int $_accountId the numeric account id
178      * @param   boolean $_anyRight is any right enough to geht app?
179      * @return  array list of enabled applications for this account
180      * @throws  Tinebase_Exception_AccessDenied if user has no role memberships
181      */
182     public function getApplications($_accountId, $_anyRight = FALSE)
183     {
184         $roleMemberships = $this->getRoleMemberships($_accountId);
185         
186         if (empty($roleMemberships)) {
187             return new Tinebase_Record_RecordSet('Tinebase_Model_Application');
188         }
189
190         $select = $this->_db->select()
191             ->distinct()
192             ->from(array('role_rights' => SQL_TABLE_PREFIX . 'role_rights'), array())
193             ->join(
194                 /* table  */ array('applications' => SQL_TABLE_PREFIX . 'applications'), 
195                 /* on     */ $this->_db->quoteIdentifier('role_rights.application_id') . ' = ' . $this->_db->quoteIdentifier('applications.id')
196             )
197             ->where($this->_db->quoteIdentifier('role_id') . ' IN (?)',          $roleMemberships)
198             ->where($this->_db->quoteIdentifier('applications.status') . ' = ?', Tinebase_Application::ENABLED)
199             ->order('order ASC');
200         
201         if ($_anyRight) {
202             $select->where($this->_db->quoteIdentifier('role_rights.right') . " IS NOT NULL");
203         } else {
204             $select->where($this->_db->quoteIdentifier('role_rights.right') . ' = ?', Tinebase_Acl_Rights::RUN);
205         }
206         
207         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
208             . ' ' . $select);
209         
210         $stmt = $this->_db->query($select);
211         
212         $result = new Tinebase_Record_RecordSet('Tinebase_Model_Application', $stmt->fetchAll(Zend_Db::FETCH_ASSOC));
213         
214         return $result;
215     }
216
217     /**
218      * returns rights for given application and accountId
219      *
220      * @param   string $_application the name of the application
221      * @param   int $_accountId the numeric account id
222      * @return  array list of rights
223      * @throws  Tinebase_Exception_AccessDenied
224      */
225     public function getApplicationRights($_application, $_accountId) 
226     {
227         $application = Tinebase_Application::getInstance()->getApplicationByName($_application);
228         
229         if ($application->status !== Tinebase_Application::ENABLED) {
230             throw new Tinebase_Exception_AccessDenied('User has no rights. the application is disabled.');
231         }
232         
233         $roleMemberships = $this->getRoleMemberships($_accountId);
234         
235         $select = $this->_db->select()
236             ->distinct()
237             ->from(SQL_TABLE_PREFIX . 'role_rights', array('account_rights' => SQL_TABLE_PREFIX . 'role_rights.right'))
238             ->where($this->_db->quoteIdentifier(SQL_TABLE_PREFIX . 'role_rights.application_id') . ' = ?', $application->getId())
239             ->where($this->_db->quoteIdentifier('role_id') . ' IN (?)', $roleMemberships);
240         
241         $stmt = $this->_db->query($select);
242
243         return  $stmt->fetchAll(Zend_Db::FETCH_COLUMN);
244     }
245         
246     /**
247      * Searches roles according to filter and paging
248      * 
249      * @param  Tinebase_Model_RoleFilter  $_filter
250      * @param  Tinebase_Model_Pagination  $_paging
251      * @return Tinebase_Record_RecordSet  Set of Tinebase_Model_Role
252      */
253     public function searchRoles($_filter, $_paging)
254     {
255         $select = $_filter->getSelect();
256         
257         $_paging->appendPaginationSql($select);
258         
259         return new Tinebase_Record_RecordSet('Tinebase_Model_Role', $this->_db->fetchAssoc($select));
260     }
261
262     /**
263      * Returns roles count
264      * 
265      * @param Tinebase_Model_RoleFilter $_filter
266      * @return int
267      */
268     public function searchCount($_filter)
269     {
270         $select = $_filter->getSelect();
271         
272         $roles = new Tinebase_Record_RecordSet('Tinebase_Model_Role', $this->_db->fetchAssoc($select));
273         return count($roles);
274     }
275     
276     /**
277      * Returns role identified by its id
278      * 
279      * @param   int  $_roleId
280      * @return  Tinebase_Model_Role  
281      * @throws  Tinebase_Exception_InvalidArgument
282      * @throws  Tinebase_Exception_NotFound
283      */
284     public function getRoleById($_roleId)
285     {
286         $roleId = (int)$_roleId;
287         if ($roleId != $_roleId && $roleId <= 0) {
288             throw new Tinebase_Exception_InvalidArgument('$_roleId must be integer and greater than 0');
289         }
290         
291         $where = $this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', $roleId);
292         if (!$row = $this->_rolesTable->fetchRow($where)) {
293             throw new Tinebase_Exception_NotFound("role with id $roleId not found");
294         }
295         
296         $result = new Tinebase_Model_Role($row->toArray());
297         
298         return $result;
299     }
300     
301     /**
302      * Returns role identified by its name
303      * 
304      * @param   string $_roleName
305      * @return  Tinebase_Model_Role  
306      * @throws  Tinebase_Exception_NotFound
307      */
308     public function getRoleByName($_roleName)
309     {
310         $where = $this->_db->quoteInto($this->_db->quoteIdentifier('name') . ' = ?', $_roleName);
311
312         if (!$row = $this->_rolesTable->fetchRow($where)) {
313             throw new Tinebase_Exception_NotFound("Role $_roleName not found.");
314         }
315         
316         $result = new Tinebase_Model_Role($row->toArray());
317         
318         return $result;
319     }
320     
321     /**
322      * Get multiple roles
323      *
324      * @param string|array $_ids Ids
325      * @return Tinebase_Record_RecordSet
326      */
327     public function getMultiple($_ids)
328     {
329         $result = new Tinebase_Record_RecordSet('Tinebase_Model_Role');
330         
331         if (! empty($_ids)) {
332             $select = $this->_rolesTable->select();
333             $select->where($this->_db->quoteIdentifier('id') . ' IN (?)', array_unique((array) $_ids));
334             
335             $rows = $this->_rolesTable->fetchAll($select);
336             foreach ($rows as $row) {
337                 $result->addRecord(new Tinebase_Model_Role($row->toArray()));
338             }
339         }
340         
341         return $result;
342     }
343     
344     /**
345      * Creates a single role
346      * 
347      * @param  Tinebase_Model_Role
348      * @return Tinebase_Model_Role
349      */
350     public function createRole(Tinebase_Model_Role $_role)
351     {
352         $data = $_role->toArray();
353         if(is_object(Tinebase_Core::getUser())) {
354             $data['created_by'] = Tinebase_Core::getUser()->getId();
355         }
356         $data['creation_time'] = Tinebase_DateTime::now()->get(Tinebase_Record_Abstract::ISO8601LONG);
357         
358         $newId = $this->_rolesTable->insert($data);
359         
360         if ($newId === NULL) {
361            $newId = $this->_db->lastSequenceId(substr(SQL_TABLE_PREFIX . 'roles', 0,25) . '_s');
362         }
363         
364         $role = $this->getRoleById($newId);
365         
366         $this->resetClassCache();
367         
368         return $role;
369     }
370     
371     /**
372      * updates a single role
373      * 
374      * @param  Tinebase_Model_Role $_role
375      * @return Tinebase_Model_Role
376      */
377     public function updateRole(Tinebase_Model_Role $_role)
378     {
379         $data = $_role->toArray();
380         $data['last_modified_by'] = Tinebase_Core::getUser()->getId();
381         $data['last_modified_time'] = Tinebase_DateTime::now()->get(Tinebase_Record_Abstract::ISO8601LONG);
382         
383         $where = $this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', $_role->getId());
384         $this->_rolesTable->update($data, $where);
385         
386         $this->resetClassCache();
387         
388         $role = $this->getRoleById($_role->getId());
389         
390         return $role;
391     }
392     
393     /**
394      * Deletes roles identified by their identifiers
395      * 
396      * @param   string|array $ids to delete
397      * @return  void
398      * @throws  Tinebase_Exception_Backend
399      */
400     public function deleteRoles($ids)
401     {
402         try {
403             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($this->_db);
404             
405             // delete role acls/members first
406             $this->_roleMembersTable->delete($this->_db->quoteInto($this->_db->quoteIdentifier('role_id') . ' in (?)', (array) $ids));
407             $this->_roleRightsTable->delete($this->_db->quoteInto($this->_db->quoteIdentifier('role_id') . ' in (?)',  (array) $ids));
408             // delete role
409             $this->_rolesTable->delete($this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' in (?)', (array) $ids));
410             
411             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
412             
413             $this->resetClassCache();
414             
415         } catch (Exception $e) {
416             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' error while deleting role ' . $e->__toString());
417             Tinebase_TransactionManager::getInstance()->rollBack();
418             throw new Tinebase_Exception_Backend($e->getMessage());
419         }
420     }
421     
422     /**
423      * Delete all Roles returned by {@see getRoles()} using {@see deleteRoles()}
424      * @return void
425      */
426     public function deleteAllRoles()
427     {
428         $roleIds = array();
429         $roles = $this->_rolesTable->fetchAll();
430         foreach ($roles as $role) {
431           $roleIds[] = $role->id;
432         }
433         
434         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Deleting ' . count($roles) .' roles');
435         
436         if (count($roles) > 0) {
437             $this->deleteRoles($roleIds);
438         }
439     }
440     
441     /**
442      * get list of role members 
443      *
444      * @param   int $_roleId
445      * @return  array of array with account ids & types
446      * @throws  Tinebase_Exception_AccessDenied
447      */
448     public function getRoleMembers($_roleId)
449     {
450         $roleId = (int)$_roleId;
451         if ($roleId != $_roleId && $roleId <= 0) {
452             throw new Tinebase_Exception_AccessDenied('$_roleId must be integer and greater than 0');
453         }
454         
455         $members = array();
456         
457         $select = $this->_roleMembersTable->select();
458         $select->where($this->_db->quoteIdentifier('role_id') . ' = ?', $_roleId);
459         
460         $rows = $this->_roleMembersTable->fetchAll($select)->toArray();
461         
462         return $rows;
463     }
464
465     /**
466      * get list of role memberships
467      *
468      * @param   int $accountId
469      * @param   string $type
470      * @return  array of array with role ids
471      * @throws  Tinebase_Exception_NotFound
472      */
473     public function getRoleMemberships($accountId, $type = Tinebase_Acl_Rights::ACCOUNT_TYPE_USER)
474     {
475         if ($type === Tinebase_Acl_Rights::ACCOUNT_TYPE_USER) {
476             $accountId        = Tinebase_Model_User::convertUserIdToInt($accountId);
477             $groupMemberships = Tinebase_Group::getInstance()->getGroupMemberships($accountId);
478             if (empty($groupMemberships)) {
479                 throw new Tinebase_Exception_NotFound('Any account must belong to at least one group. The account with accountId ' . $accountId . ' does not belong to any group.');
480             }
481             
482             $classCacheId = convertCacheId ($accountId . implode('', $groupMemberships) . $type);
483         } else if ($type === Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP) {
484             $accountId = Tinebase_Model_Group::convertGroupIdToInt($accountId);
485             
486             $classCacheId = convertCacheId ($accountId . $type);
487         } else {
488             throw new Tinebase_Exception_InvalidArgument('Invalid type: ' . $type);
489         }
490         
491         if (isset($this->_classCache[__FUNCTION__][$classCacheId])) {
492             return $this->_classCache[__FUNCTION__][$classCacheId];
493         }
494         
495         $select = $this->_db->select()
496             ->distinct()
497             ->from(array('role_accounts' => SQL_TABLE_PREFIX . 'role_accounts'), array('role_id'))
498             ->where($this->_db->quoteInto($this->_db->quoteIdentifier('account_id') . ' = ?', $accountId) . ' AND ' 
499                 . $this->_db->quoteInto($this->_db->quoteIdentifier('account_type') . ' = ?', $type));
500         
501         if ($type === Tinebase_Acl_Rights::ACCOUNT_TYPE_USER) {
502             $select->orwhere($this->_db->quoteInto($this->_db->quoteIdentifier('account_id') . ' IN (?)', $groupMemberships) . ' AND ' 
503                 .  $this->_db->quoteInto($this->_db->quoteIdentifier('account_type') . ' = ?', Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP));
504         }
505         
506         $stmt = $this->_db->query($select);
507         
508         $memberships = $stmt->fetchAll(Zend_Db::FETCH_COLUMN);
509         
510         $this->_classCache[__FUNCTION__][$classCacheId] = $memberships;
511         
512         return $memberships;
513     }
514
515     /**
516      * set role members 
517      *
518      * @param   int $_roleId
519      * @param   array $_roleMembers with role members ("account_type" => account type, "account_id" => account id)
520      * @throws  Tinebase_Exception_InvalidArgument
521      */
522     public function setRoleMembers($_roleId, array $_roleMembers)
523     {
524         $roleId = (int)$_roleId;
525         if ($roleId != $_roleId && $roleId > 0) {
526             throw new Tinebase_Exception_InvalidArgument('$_roleId must be integer and greater than 0');
527         }
528         
529         // remove old members
530         $where = $this->_db->quoteInto($this->_db->quoteIdentifier('role_id') . ' = ?', $roleId);
531         $this->_roleMembersTable->delete($where);
532               
533         $validTypes = array( Tinebase_Acl_Rights::ACCOUNT_TYPE_USER, Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP, Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE);
534         foreach ($_roleMembers as $member) {
535             if (!in_array($member['type'], $validTypes)) {
536                 throw new Tinebase_Exception_InvalidArgument('account_type must be one of ' . 
537                     implode(', ', $validTypes) . ' (values given: ' . 
538                     print_r($member, true) . ')');
539             }
540             
541             $data = array(
542                 'role_id'       => $roleId,
543                 'account_type'  => $member['type'],
544                 'account_id'    => $member['id'],
545             );
546             $this->_roleMembersTable->insert($data);
547         }
548         
549         $this->resetClassCache();
550     }
551     
552     /**
553      * set all roles an user is member of
554      *
555      * @param  array  $_account as role member ("account_type" => account type, "account_id" => account id)
556      * @param  mixed  $_roleIds
557      * @return array
558      */
559     public function setRoleMemberships($_account, $_roleIds)
560     {
561         if ($_roleIds instanceof Tinebase_Record_RecordSet) {
562             $_roleIds = $_roleIds->getArrayOfIds();
563         }
564         
565         if(count($_roleIds) === 0) {
566             throw new Tinebase_Exception_InvalidArgument('user must belong to at least one role');
567         }
568         
569         $validTypes = array( Tinebase_Acl_Rights::ACCOUNT_TYPE_USER, Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP, Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE);
570
571         if (! in_array($_account['type'], $validTypes)) {
572             throw new Tinebase_Exception_InvalidArgument('account_type must be one of ' . 
573                 implode(', ', $validTypes) . ' (values given: ' . 
574                 print_r($_account, true) . ')');
575         }
576         
577         $roleMemberships = $this->getRoleMemberships($_account['id']);
578         
579         $removeRoleMemberships = array_diff($roleMemberships, $_roleIds);
580         $addRoleMemberships    = array_diff($_roleIds, $roleMemberships);
581         
582         foreach ($addRoleMemberships as $roleId) {
583             $this->addRoleMember($roleId, $_account);
584         }
585         
586         foreach ($removeRoleMemberships as $roleId) {
587             $this->removeRoleMember($roleId, $_account);
588         }
589         
590         return $this->getRoleMemberships($_account['id']);
591     }
592     
593     /**
594      * add a new member to a role
595      *
596      * @param  string  $_roleId
597      * @param  array   $_account as role member ("account_type" => account type, "account_id" => account id)
598      */
599     public function addRoleMember($_roleId, $_account)
600     {
601         $roleId = (int)$_roleId;
602         if ($roleId != $_roleId && $roleId > 0) {
603             throw new Tinebase_Exception_InvalidArgument('$_roleId must be integer and greater than 0');
604         }
605         
606         $validTypes = array(Tinebase_Acl_Rights::ACCOUNT_TYPE_USER, Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP, Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE);
607
608         if (! in_array($_account['type'], $validTypes)) {
609             throw new Tinebase_Exception_InvalidArgument('account_type must be one of ' . 
610                 implode(', ', $validTypes) . ' (values given: ' . 
611                 print_r($_account, true) . ')');
612         }
613         
614         $data = array(
615             'role_id'       => $roleId,
616             'account_type'  => $_account['type'],
617             'account_id'    => $_account['id'],
618         );
619         
620         try {
621             $this->_roleMembersTable->insert($data);
622         } catch (Zend_Db_Statement_Exception $e) {
623             // account is already member of this group
624         }
625         
626         $this->resetClassCache();
627     }
628     
629     /**
630      * remove one member from the role
631      *
632      * @param  mixed  $_groupId
633      * @param  array   $_account as role member ("type" => account type, "id" => account id)
634      */
635     public function removeRoleMember($_roleId, $_account)
636     {
637         $roleId = (int)$_roleId;
638         if ($roleId != $_roleId && $roleId > 0) {
639             throw new Tinebase_Exception_InvalidArgument('$_roleId must be integer and greater than 0');
640         }
641         
642         $where = array(
643             $this->_db->quoteInto($this->_db->quoteIdentifier('role_id') . '= ?', $roleId),
644             $this->_db->quoteInto($this->_db->quoteIdentifier('account_type') . '= ?', $_account['type']),
645             $this->_db->quoteInto($this->_db->quoteIdentifier('account_id') . '= ?', (string) $_account['id']),
646         );
647          
648         $this->_roleMembersTable->delete($where);
649         
650         $this->resetClassCache();
651     }
652     
653     /**
654      * reset class cache
655      * 
656      * @param string $key
657      * @return Tinebase_Acl_Roles
658      */
659     public function resetClassCache($key = null)
660     {
661         foreach ($this->_classCache as $cacheKey => $cacheValue) {
662             if ($key === null || $key === $cacheKey) {
663                 $this->_classCache[$cacheKey] = array();
664             }
665         }
666         
667         return $this;
668     }
669     
670     /**
671      * get list of role rights
672      *
673      * @param   int $_roleId
674      * @return  array of array with application ids & rights
675      * @throws  Tinebase_Exception_InvalidArgument
676      */
677     public function getRoleRights($_roleId)
678     {
679         $roleId = (int)$_roleId;
680         if ($roleId != $_roleId || $roleId <= 0) {
681             throw new Tinebase_Exception_InvalidArgument('$_roleId must be integer and greater than 0');
682         }
683         
684         $select = $this->_db->select()
685             ->distinct()
686             ->from(array('role_rights' => SQL_TABLE_PREFIX . 'role_rights'), array('application_id', 'right'))
687             ->where($this->_db->quoteIdentifier('role_id') . ' = ?', $_roleId);
688         
689         $stmt = $this->_db->query($select);
690         
691         $rights = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
692         
693         return $rights;
694     }
695
696     /**
697      * set role rights 
698      *
699      * @param   int    $roleId
700      * @param   array  $_roleRights  with role rights array(("application_id" => app id, "right" => the right to set), (...))
701      * @throws  Tinebase_Exception_InvalidArgument
702      */
703     public function setRoleRights($roleId, array $roleRights)
704     {
705         if (!is_numeric($roleId) || ($roleId = (int)$roleId) === 0) {
706             throw new Tinebase_Exception_InvalidArgument('$_roleId must be integer and greater than 0');
707         }
708         
709         $currentRights = $this->getRoleRights($roleId);
710         // change array key to string identifying right
711         foreach ($currentRights as $id => $right) {
712             $currentRights[$right['application_id'] . $right['right']] = $right;
713             unset($currentRights[$id]);
714         }
715         
716         // change array key to string identifying right
717         foreach ($roleRights as $id => $right) {
718             $roleRights[$right['application_id'] . $right['right']] = $right;
719             unset($roleRights[$id]);
720         }
721         
722         // compare array keys to calculate changes
723         $rightsToBeDeleted = array_diff_key($currentRights, $roleRights);
724         $rightsToBeAdded   = array_diff_key($roleRights, $currentRights);
725         
726         $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($this->_db);
727         
728         foreach ($rightsToBeDeleted as $right) {
729             $this->deleteRoleRight($roleId, $right['application_id'], $right['right']);
730         }
731         
732         foreach ($rightsToBeAdded as $right) {
733             $this->addRoleRight($roleId, $right['application_id'], $right['right']);
734         }
735         
736         Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
737         
738         $this->_invalidateRightsCache($roleId, array_merge($rightsToBeDeleted, $rightsToBeAdded));
739     }
740     
741     /**
742      * add one role right
743      * 
744      * @param unknown $roleId
745      * @param unknown $applicationId
746      * @param unknown $right
747      */
748     public function addRoleRight($roleId, $applicationId, $right)
749     {
750         $this->_roleRightsTable->insert(array(
751             'role_id'           => $roleId,
752             'application_id'    => $applicationId,
753             'right'             => $right,
754         ));
755         
756         $this->resetClassCache();
757     }
758     
759     /**
760      * remove one role right
761      * 
762      * @param unknown $roleId
763      * @param unknown $applicationId
764      * @param unknown $right
765      */
766     public function deleteRoleRight($roleId, $applicationId, $right)
767     {
768         $where = array(
769             $this->_db->quoteInto($this->_db->quoteIdentifier('role_id') . ' = ?',        $roleId),
770             $this->_db->quoteInto($this->_db->quoteIdentifier('application_id') . ' = ?', $applicationId),
771             $this->_db->quoteInto($this->_db->quoteIdentifier('right') . ' = ?',          $right)
772         );
773         
774         $this->_roleRightsTable->delete($where);
775         
776         $this->resetClassCache();
777     }
778     
779     /**
780      * invalidate rights cache
781      * 
782      * @param int   $roleId
783      * @param array $roleRights  the role rights to purge from cache
784      */
785     protected function _invalidateRightsCache($roleId, $roleRights)
786     {
787         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
788             . ' Invalidating rights cache for role id ' . $roleId);
789         
790         $rightsInvalidateCache = array();
791         foreach ($roleRights as $right) {
792             $rightsInvalidateCache[] = strtoupper($right['right']) . Tinebase_Application::getInstance()->getApplicationById($right['application_id'])->name;
793         }
794         
795         // @todo can be further improved, by only selecting the users which are members of this role
796         $userIds = Tinebase_User::getInstance()->getUsers()->getArrayOfIds();
797         
798         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) 
799             Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($rightsInvalidateCache, TRUE));
800         
801         foreach ($rightsInvalidateCache as $rightData) {
802             foreach ($userIds as $userId) {
803                 $cacheId = convertCacheId('checkRight' . $userId . $rightData);
804                 
805                 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) 
806                     Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' Removing cache id ' . $cacheId);
807                 
808                 Tinebase_Core::getCache()->remove($cacheId);
809             }
810         }
811         
812         $this->resetClassCache();
813     }
814     
815     /**
816      * add single role rights 
817      *
818      * @param   int $_roleId
819      * @param   int $_applicationId
820      * @param   string $_right
821      * 
822      * @todo this function should be removed and setRoleRights should be used instead
823      */
824     public function addSingleRight($_roleId, $_applicationId, $_right)
825     {
826         // check if already in
827         $select = $this->_roleRightsTable->select();
828         $select->where($this->_db->quoteIdentifier('role_id') . ' = ?',        $_roleId)
829                ->where($this->_db->quoteIdentifier('right') . ' = ?',          $_right)
830                ->where($this->_db->quoteIdentifier('application_id') . ' = ?', $_applicationId);
831         
832         if (!$row = $this->_roleRightsTable->fetchRow($select)) {
833             $this->addRoleRight($_roleId, $_applicationId, $_right);
834         }
835         
836         $this->resetClassCache();
837     }
838     
839     /**
840      * Create initial Roles
841      * 
842      * @todo make hard coded role names ('user role' and 'admin role') configurable
843      * 
844      * @return void
845      */
846     public function createInitialRoles()
847     {
848         $groupsBackend = Tinebase_Group::getInstance();
849         
850         $adminGroup = $groupsBackend->getDefaultAdminGroup();
851         $userGroup  = $groupsBackend->getDefaultGroup();
852         
853         // add roles and add the groups to the roles
854         $adminRole = new Tinebase_Model_Role(array(
855             'name'                  => 'admin role',
856             'description'           => 'admin role for tine. this role has all rights per default.',
857         ));
858         $adminRole = $this->createRole($adminRole);
859         $this->setRoleMembers($adminRole->getId(), array(
860             array(
861                 'id'    => $adminGroup->getId(),
862                 'type'  => Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP, 
863             )
864         ));
865         
866         $userRole = new Tinebase_Model_Role(array(
867             'name'                  => 'user role',
868             'description'           => 'userrole for tine. this role has only the run rights for all applications per default.',
869         ));
870         $userRole = $this->createRole($userRole);
871         $this->setRoleMembers($userRole->getId(), array(
872             array(
873                 'id'    => $userGroup->getId(),
874                 'type'  => Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP, 
875             )
876         ));
877         
878         $this->resetClassCache();
879     }
880 }