53b4f4c39a82195fb8a4f26e5437368244914ed3
[tine20] / tine20 / Tinebase / Group / Sql.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  Group
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2008-2010 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Lars Kneschke <l.kneschke@metaways.de>
10  */
11
12 /**
13  * sql implementation of the groups interface
14  * 
15  * @package     Tinebase
16  * @subpackage  Group
17  */
18 class Tinebase_Group_Sql extends Tinebase_Group_Abstract
19 {
20     /**
21      * @var Zend_Db_Adapter_Abstract
22      */
23     protected $_db;
24     
25     /**
26      * the groups table
27      *
28      * @var Tinebase_Db_Table
29      */
30     protected $groupsTable;
31     
32     /**
33      * the groupmembers table
34      *
35      * @var Tinebase_Db_Table
36      */
37     protected $groupMembersTable;
38     
39     /**
40      * Table name without prefix
41      *
42      * @var string
43      */
44     protected $_tableName = 'groups';
45     
46     /**
47      * set to true is addressbook table is found
48      * 
49      * @var boolean
50      */
51     protected $_addressBookInstalled = false;
52     
53     /**
54      * the constructor
55      */
56     public function __construct() 
57     {
58         $this->_db = Tinebase_Core::getDb();
59         
60         $this->groupsTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . $this->_tableName));
61         $this->groupMembersTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'group_members'));
62         
63         try {
64             // MySQL throws an exception         if the table does not exist
65             // PostgreSQL returns an empty array if the table does not exist
66             $adbSchema = Tinebase_Db_Table::getTableDescriptionFromCache(SQL_TABLE_PREFIX . 'addressbook');
67             $adbListsSchema = Tinebase_Db_Table::getTableDescriptionFromCache(SQL_TABLE_PREFIX . 'addressbook_lists');
68             if (! empty($adbSchema) && ! empty($adbListsSchema) ) {
69                 $this->_addressBookInstalled = TRUE;
70             }
71         } catch (Zend_Db_Statement_Exception $zdse) {
72             // nothing to do
73         }
74     }
75     
76     /**
77      * return all groups an account is member of
78      *
79      * @param mixed $_accountId the account as integer or Tinebase_Model_User
80      * @return array
81      */
82     public function getGroupMemberships($_accountId)
83     {
84         $accountId = Tinebase_Model_User::convertUserIdToInt($_accountId);
85         
86         $cacheId = convertCacheId('groupMemberships' . $accountId);
87         $memberships = Tinebase_Core::getCache()->load($cacheId);
88
89         if (! $memberships) {
90             $memberships = array();
91             $colName = $this->groupsTable->getAdapter()->quoteIdentifier('account_id');
92             $select = $this->groupMembersTable->select();
93             $select->where($colName . ' = ?', $accountId);
94             
95             $rows = $this->groupMembersTable->fetchAll($select);
96             
97             foreach($rows as $membership) {
98                 $memberships[] = $membership->group_id;
99             }
100
101             Tinebase_Core::getCache()->save($memberships, $cacheId);
102         }
103
104         return $memberships;
105     }
106     
107     /**
108      * get list of groupmembers
109      *
110      * @param int $_groupId
111      * @return array with account ids
112      */
113     public function getGroupMembers($_groupId)
114     {
115         $groupId = Tinebase_Model_Group::convertGroupIdToInt($_groupId);
116         
117         $cacheId = convertCacheId('groupMembers' . $groupId);
118         $members = Tinebase_Core::getCache()->load($cacheId);
119
120         if (! $members) {
121             $members = array();
122
123             $select = $this->groupMembersTable->select();
124             $select->where($this->_db->quoteIdentifier('group_id') . ' = ?', $groupId);
125
126             $rows = $this->groupMembersTable->fetchAll($select);
127             
128             foreach($rows as $member) {
129                 $members[] = $member->account_id;
130             }
131
132             Tinebase_Core::getCache()->save($members, $cacheId);
133         }
134
135         return $members;
136     }
137     
138     /**
139      * replace all current groupmembers with the new groupmembers list
140      *
141      * @param  mixed  $_groupId
142      * @param  array  $_groupMembers
143      */
144     public function setGroupMembers($_groupId, $_groupMembers)
145     {
146         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
147             . ' Setting ' . count($_groupMembers) . ' new groupmembers for group ' . $_groupId);
148         
149         if ($this instanceof Tinebase_Group_Interface_SyncAble) {
150             $_groupMembers = $this->setGroupMembersInSyncBackend($_groupId, $_groupMembers);
151         }
152         
153         $this->setGroupMembersInSqlBackend($_groupId, $_groupMembers);
154     }
155      
156     /**
157      * replace all current groupmembers with the new groupmembers list
158      *
159      * @param  mixed  $_groupId
160      * @param  array  $_groupMembers
161      */
162     public function setGroupMembersInSqlBackend($_groupId, $_groupMembers)
163     {
164         $groupId = Tinebase_Model_Group::convertGroupIdToInt($_groupId);
165         
166         // remove old members
167         $where = $this->_db->quoteInto($this->_db->quoteIdentifier('group_id') . ' = ?', $groupId);
168         $this->groupMembersTable->delete($where);
169         
170         // check if users have accounts
171         $userIdsWithExistingAccounts = Tinebase_User::getInstance()->getMultiple($_groupMembers)->getArrayOfIds();
172         
173         if (count($_groupMembers) > 0) {
174             // add new members
175             foreach ($_groupMembers as $accountId) {
176                 $accountId = Tinebase_Model_User::convertUserIdToInt($accountId);
177                 if (in_array($accountId, $userIdsWithExistingAccounts)) {
178                     $this->_db->insert(SQL_TABLE_PREFIX . 'group_members', array(
179                         'group_id'    => $groupId,
180                         'account_id'  => $accountId
181                     ));
182                 } else {
183                     if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
184                         . ' User with ID ' . $accountId . ' does not have an account!');
185                 }
186                 
187                 $this->_clearCache(array('groupMemberships' => $accountId));
188             }
189         }
190         
191         $this->_clearCache(array('groupMembers' => $groupId));
192     }
193     
194     /**
195      * invalidate cache by type/id
196      * 
197      * @param array $cacheIds
198      */
199     protected function _clearCache($cacheIds = array())
200     {
201         foreach ($cacheIds as $type => $id) {
202             $cacheId = convertCacheId($type . $id);
203             Tinebase_Core::getCache()->remove($cacheId);
204         }
205     }
206     
207     /**
208      * set all groups an account is member of
209      *
210      * @param  mixed  $_userId    the userid as string or Tinebase_Model_User
211      * @param  mixed  $_groupIds
212      * 
213      * @return array
214      */
215     public function setGroupMemberships($_userId, $_groupIds)
216     {
217         if(count($_groupIds) === 0) {
218             throw new Tinebase_Exception_InvalidArgument('user must belong to at least one group');
219         }
220         
221         if($this instanceof Tinebase_Group_Interface_SyncAble) {
222             $this->setGroupMembershipsInSyncBackend($_userId, $_groupIds);
223         }
224         
225         return $this->setGroupMembershipsInSqlBackend($_userId, $_groupIds);
226     }
227     
228     /**
229      * set all groups an user is member of
230      *
231      * @param  mixed  $_usertId   the account as integer or Tinebase_Model_User
232      * @param  mixed  $_groupIds
233      * @return array
234      */
235     public function setGroupMembershipsInSqlBackend($_userId, $_groupIds)
236     {
237         if ($_groupIds instanceof Tinebase_Record_RecordSet) {
238             $_groupIds = $_groupIds->getArrayOfIds();
239         }
240         
241         if (count($_groupIds) === 0) {
242             throw new Tinebase_Exception_InvalidArgument('user must belong to at least one group');
243         }
244         
245         $userId = Tinebase_Model_user::convertUserIdToInt($_userId);
246         
247         $groupMemberships = $this->getGroupMemberships($userId);
248         
249         $removeGroupMemberships = array_diff($groupMemberships, $_groupIds);
250         $addGroupMemberships    = array_diff($_groupIds, $groupMemberships);
251         
252         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' current groupmemberships: ' . print_r($groupMemberships, true));
253         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' new groupmemberships: ' . print_r($_groupIds, true));
254         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' added groupmemberships: ' . print_r($addGroupMemberships, true));
255         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' removed groupmemberships: ' . print_r($removeGroupMemberships, true));
256         
257         foreach ($addGroupMemberships as $groupId) {
258             $this->addGroupMemberInSqlBackend($groupId, $userId);
259         }
260         
261         foreach ($removeGroupMemberships as $groupId) {
262             $this->removeGroupMemberFromSqlBackend($groupId, $userId);
263         }
264         
265         $event = new Tinebase_Group_Event_SetGroupMemberships(array(
266             'user'               => $_userId,
267             'addedMemberships'   => $addGroupMemberships,
268             'removedMemberships' => $removeGroupMemberships
269         ));
270         Tinebase_Event::fireEvent($event);
271         
272         return $this->getGroupMemberships($userId);
273     }
274     
275     /**
276      * add a new groupmember to a group
277      *
278      * @param  string  $_groupId
279      * @param  string  $_accountId
280      */
281     public function addGroupMember($_groupId, $_accountId)
282     {
283         if ($this instanceof Tinebase_Group_Interface_SyncAble) {
284             $this->addGroupMemberInSyncBackend($_groupId, $_accountId);
285         }
286         
287         $this->addGroupMemberInSqlBackend($_groupId, $_accountId);
288     }
289     
290     /**
291      * add a new groupmember to a group
292      *
293      * @param  string  $_groupId
294      * @param  string  $_accountId
295      */
296     public function addGroupMemberInSqlBackend($_groupId, $_accountId)
297     {
298         $groupId   = Tinebase_Model_Group::convertGroupIdToInt($_groupId);
299         $accountId = Tinebase_Model_User::convertUserIdToInt($_accountId);
300
301         $memberShips = $this->getGroupMemberships($accountId);
302         
303         if (!in_array($groupId, $memberShips)) {
304             $data = array(
305                 'group_id'      => $groupId,
306                 'account_id'    => $accountId
307             );
308         
309             $this->groupMembersTable->insert($data);
310             
311             $this->_clearCache(array(
312                 'groupMembers'     => $groupId,
313                 'groupMemberships' => $accountId,
314             ));
315         }
316         
317     }
318     
319     /**
320      * remove one groupmember from the group
321      *
322      * @param  mixed  $_groupId
323      * @param  mixed  $_accountId
324      */
325     public function removeGroupMember($_groupId, $_accountId)
326     {
327         if ($this instanceof Tinebase_Group_Interface_SyncAble) {
328             $this->removeGroupMemberInSyncBackend($_groupId, $_accountId);
329         }
330         
331         return $this->removeGroupMemberFromSqlBackend($_groupId, $_accountId);
332     }
333     
334     /**
335      * remove one groupmember from the group
336      *
337      * @param  mixed  $_groupId
338      * @param  mixed  $_accountId
339      */
340     public function removeGroupMemberFromSqlBackend($_groupId, $_accountId)
341     {
342         $groupId   = Tinebase_Model_Group::convertGroupIdToInt($_groupId);
343         $accountId = Tinebase_Model_User::convertUserIdToInt($_accountId);
344         
345         $where = array(
346             $this->_db->quoteInto($this->_db->quoteIdentifier('group_id') . '= ?', $groupId),
347             $this->_db->quoteInto($this->_db->quoteIdentifier('account_id') . '= ?', $accountId),
348         );
349          
350         $this->groupMembersTable->delete($where);
351         
352         $this->_clearCache(array(
353             'groupMembers'     => $groupId,
354             'groupMemberships' => $accountId,
355         ));
356     }
357     
358     /**
359      * create a new group
360      *
361      * @param   Tinebase_Model_Group  $_group
362      * 
363      * @return  Tinebase_Model_Group
364      * 
365      * @todo do not create group in sql if sync backend is readonly?
366      */
367     public function addGroup(Tinebase_Model_Group $_group)
368     {
369         if ($this instanceof Tinebase_Group_Interface_SyncAble) {
370             $groupFromSyncBackend = $this->addGroupInSyncBackend($_group);
371             
372             if (isset($groupFromSyncBackend->id)) {
373                 $_group->setId($groupFromSyncBackend->getId());
374             }
375         }
376         
377         return $this->addGroupInSqlBackend($_group);
378     }
379     
380     /**
381      * alias for addGroup
382      * 
383      * @param Tinebase_Model_Group $group
384      * @return Tinebase_Model_Group
385      */
386     public function create(Tinebase_Model_Group $group)
387     {
388         return $this->addGroup($group);
389     }
390     
391     /**
392      * create a new group in sql backend
393      *
394      * @param   Tinebase_Model_Group  $_group
395      * 
396      * @return  Tinebase_Model_Group
397      * @throws  Tinebase_Exception_Record_Validation
398      */
399     public function addGroupInSqlBackend(Tinebase_Model_Group $_group)
400     {
401         if(!$_group->isValid()) {
402             throw new Tinebase_Exception_Record_Validation('invalid group object');
403         }
404         
405         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
406             . ' Creating new group ' . $_group->name 
407             //. print_r($_group->toArray(), true)
408         );
409         
410         if(!isset($_group->id)) {
411             $groupId = $_group->generateUID();
412             $_group->setId($groupId);
413         }
414         
415         if (empty($_group->list_id)) {
416             $_group->visibility = 'hidden';
417             $_group->list_id    = null;
418         }
419         
420         $data = $_group->toArray();
421         
422         unset($data['members']);
423         unset($data['container_id']);
424         
425         $this->groupsTable->insert($data);
426                 
427         return $_group;
428     }
429     
430     /**
431      * update a group
432      *
433      * @param  Tinebase_Model_Group  $_group
434      * 
435      * @return Tinebase_Model_Group
436      */
437     public function updateGroup(Tinebase_Model_Group $_group)
438     {
439         if ($this instanceof Tinebase_Group_Interface_SyncAble) {
440             $this->updateGroupInSyncBackend($_group);
441         }
442         
443         return $this->updateGroupInSqlBackend($_group);
444     }
445     
446     /**
447      * create a new group in sync backend
448      * 
449      * NOTE: sets visibility to HIDDEN if list_id is empty
450      *
451      * @param  Tinebase_Model_Group  $_group
452      * @return Tinebase_Model_Group
453      */
454     public function updateGroupInSqlBackend(Tinebase_Model_Group $_group)
455     {
456         $groupId = Tinebase_Model_Group::convertGroupIdToInt($_group);
457         
458         if (empty($_group->list_id)) {
459             $_group->visibility = Tinebase_Model_Group::VISIBILITY_HIDDEN;
460             $_group->list_id    = null;
461         }
462         
463         $data = array(
464             'name'          => $_group->name,
465             'description'   => $_group->description,
466             'visibility'    => $_group->visibility,
467             'email'         => $_group->email,
468             'list_id'       => $_group->list_id
469         );
470         
471         $where = $this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', $groupId);
472         
473         $this->groupsTable->update($data, $where);
474         
475         return $this->getGroupById($groupId);
476     }
477     
478     /**
479      * delete groups
480      *
481      * @param   mixed $_groupId
482
483      * @throws  Tinebase_Exception_Backend
484      */
485     public function deleteGroups($_groupId)
486     {
487         $groupIds = array();
488         
489         if (is_array($_groupId) or $_groupId instanceof Tinebase_Record_RecordSet) {
490             foreach ($_groupId as $groupId) {
491                 $groupIds[] = Tinebase_Model_Group::convertGroupIdToInt($groupId);
492             }
493         } else {
494             $groupIds[] = Tinebase_Model_Group::convertGroupIdToInt($_groupId);
495         }
496         
497         try {
498             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
499             
500             $this->_updatePrimaryGroupsOfUsers($groupIds);
501             
502             $this->deleteGroupsInSqlBackend($groupIds);
503             if ($this instanceof Tinebase_Group_Interface_SyncAble) {
504                 $this->deleteGroupsInSyncBackend($groupIds);
505             }
506             
507             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
508             
509         } catch (Exception $e) {
510             Tinebase_TransactionManager::getInstance()->rollBack();
511             Tinebase_Exception::log($e);
512             throw new Tinebase_Exception_Backend($e->getMessage());
513         }
514     }
515     
516     /**
517      * set primary group for accounts with given primary group id
518      * 
519      * @param array $groupIds
520      * @param string $newPrimaryGroupId
521      * @throws Tinebase_Exception_Record_NotDefined
522      */
523     protected function _updatePrimaryGroupsOfUsers($groupIds, $newPrimaryGroupId = null)
524     {
525         if ($newPrimaryGroupId === null) {
526             $newPrimaryGroupId = $this->getDefaultGroup()->getId();
527         }
528         foreach ($groupIds as $groupId) {
529             $users = Tinebase_User::getInstance()->getUsersByPrimaryGroup($groupId);
530             $users->accountPrimaryGroup = $newPrimaryGroupId;
531             foreach ($users as $user) {
532                 Tinebase_User::getInstance()->updateUser($user);
533             }
534         }
535     }
536     
537     /**
538      * delete groups in sql backend
539      * 
540      * @param array $groupIds
541      */
542     public function deleteGroupsInSqlBackend($groupIds)
543     {
544         $where = $this->_db->quoteInto($this->_db->quoteIdentifier('group_id') . ' IN (?)', (array) $groupIds);
545         $this->groupMembersTable->delete($where);
546         $where = $this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' IN (?)', (array) $groupIds);
547         $this->groupsTable->delete($where);
548     }
549     
550     /**
551      * Delete all groups returned by {@see getGroups()} using {@see deleteGroups()}
552      * @return void
553      */
554     public function deleteAllGroups()
555     {
556         $groups = $this->getGroups();
557         
558         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Deleting ' . count($groups) .' groups');
559         
560         if(count($groups) > 0) {
561             $this->deleteGroups($groups);
562         }
563     }
564     
565     /**
566      * get list of groups
567      *
568      * @param string $_filter
569      * @param string $_sort
570      * @param string $_dir
571      * @param int $_start
572      * @param int $_limit
573      * @return Tinebase_Record_RecordSet with record class Tinebase_Model_Group
574      */
575     public function getGroups($_filter = NULL, $_sort = 'name', $_dir = 'ASC', $_start = NULL, $_limit = NULL)
576     {
577         $select = $this->_getSelect();
578         
579         if($_filter !== NULL) {
580             $select->where($this->_db->quoteIdentifier($this->_tableName. '.name') . ' LIKE ?', '%' . $_filter . '%');
581         }
582         if($_sort !== NULL) {
583             $select->order($this->_tableName . '.' . $_sort . ' ' . $_dir);
584         }
585         if($_start !== NULL) {
586             $select->limit($_limit, $_start);
587         }
588         
589         $stmt = $this->_db->query($select);
590         $queryResult = $stmt->fetchAll();
591         $stmt->closeCursor();
592         
593         $result = new Tinebase_Record_RecordSet('Tinebase_Model_Group', $queryResult, TRUE);
594         
595         return $result;
596     }
597     
598     /**
599      * get group by name
600      *
601      * @param   string $_name
602      * @return  Tinebase_Model_Group
603      * @throws  Tinebase_Exception_Record_NotDefined
604      */
605     public function getGroupByName($_name)
606     {
607         $select = $this->_getSelect();
608         
609         $select->where($this->_db->quoteIdentifier($this->_tableName . '.name') . ' = ?', $_name);
610         
611         $stmt = $this->_db->query($select);
612         $queryResult = $stmt->fetch();
613         $stmt->closeCursor();
614         
615         if (!$queryResult) {
616             throw new Tinebase_Exception_Record_NotDefined('Group not found.');
617         }
618
619         $result = new Tinebase_Model_Group($queryResult, TRUE);
620         
621         return $result;
622     }
623     
624     /**
625      * get group by id
626      *
627      * @param   string $_name
628      * @return  Tinebase_Model_Group
629      * @throws  Tinebase_Exception_Record_NotDefined
630      */
631     public function getGroupById($_groupId)
632     {
633         $groupdId = Tinebase_Model_Group::convertGroupIdToInt($_groupId);
634         
635         $select = $this->_getSelect();
636         
637         $select->where($this->_db->quoteIdentifier($this->_tableName . '.id') . ' = ?', $groupdId);
638         
639         $stmt = $this->_db->query($select);
640         $queryResult = $stmt->fetch();
641         $stmt->closeCursor();
642         
643         if (!$queryResult) {
644             throw new Tinebase_Exception_Record_NotDefined('Group not found.');
645         }
646         
647         $result = new Tinebase_Model_Group($queryResult, TRUE);
648         
649         return $result;
650     }
651     
652     /**
653      * Get multiple groups
654      *
655      * @param string|array $_ids Ids
656      * @return Tinebase_Record_RecordSet
657      * 
658      * @todo this should return the container_id, too
659      */
660     public function getMultiple($_ids)
661     {
662         $result = new Tinebase_Record_RecordSet('Tinebase_Model_Group');
663         
664         if (! empty($_ids)) {
665             $select = $this->groupsTable->select();
666             $select->where($this->_db->quoteIdentifier('id') . ' IN (?)', array_unique((array) $_ids));
667             
668             $rows = $this->groupsTable->fetchAll($select);
669             foreach ($rows as $row) {
670                 $result->addRecord(new Tinebase_Model_Group($row->toArray(), TRUE));
671             }
672         }
673         
674         return $result;
675     }
676     
677     /**
678      * get the basic select object to fetch records from the database
679      * 
680      * NOTE: container_id is joined from addressbook lists table
681      *  
682      * @param array|string|Zend_Db_Expr $_cols columns to get, * per default
683      * @param boolean $_getDeleted get deleted records (if modlog is active)
684      * @return Zend_Db_Select
685      */
686     protected function _getSelect($_cols = '*', $_getDeleted = FALSE)
687     {
688         $select = $this->_db->select();
689
690         $select->from(array($this->_tableName => SQL_TABLE_PREFIX . $this->_tableName), $_cols);
691         
692         if ($this->_addressBookInstalled === true) {
693             $select->joinLeft(
694                 array('addressbook_lists' => SQL_TABLE_PREFIX . 'addressbook_lists'),
695                 $this->_db->quoteIdentifier($this->_tableName . '.list_id') . ' = ' . $this->_db->quoteIdentifier('addressbook_lists.id'), 
696                 array('container_id')
697             );
698         }
699         
700         return $select;
701     }
702     
703     /**
704      * Method called by {@see Addressbook_Setup_Initialize::_initilaize()}
705      * 
706      * @param $_options
707      * @return unknown_type
708      */
709     public function __importGroupMembers($_options = null)
710     {
711         //nothing to do
712     }
713 }