7 * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
8 * @copyright Copyright (c) 2008-2015 Metaways Infosystems GmbH (http://www.metaways.de)
9 * @author Lars Kneschke <l.kneschke@metaways.de>
13 * sql implementation of the groups interface
18 class Tinebase_Group_Sql extends Tinebase_Group_Abstract
21 * @var Zend_Db_Adapter_Abstract
28 * @var Tinebase_Db_Table
30 protected $groupsTable;
33 * the groupmembers table
35 * @var Tinebase_Db_Table
37 protected $groupMembersTable;
40 * Table name without prefix
44 protected $_tableName = 'groups';
47 * set to true is addressbook table is found
51 protected $_addressBookInstalled = false;
58 protected $_classCache = array (
59 'getGroupMemberships' => array()
65 public function __construct()
67 $this->_db = Tinebase_Core::getDb();
69 $this->groupsTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . $this->_tableName));
70 $this->groupMembersTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'group_members'));
73 // MySQL throws an exception if the table does not exist
74 // PostgreSQL returns an empty array if the table does not exist
75 $adbSchema = Tinebase_Db_Table::getTableDescriptionFromCache(SQL_TABLE_PREFIX . 'addressbook');
76 $adbListsSchema = Tinebase_Db_Table::getTableDescriptionFromCache(SQL_TABLE_PREFIX . 'addressbook_lists');
77 if (! empty($adbSchema) && ! empty($adbListsSchema) ) {
78 $this->_addressBookInstalled = TRUE;
80 } catch (Zend_Db_Statement_Exception $zdse) {
86 * return all groups an account is member of
88 * @param mixed $_accountId the account as integer or Tinebase_Model_User
91 public function getGroupMemberships($_accountId)
93 $accountId = Tinebase_Model_User::convertUserIdToInt($_accountId);
95 $classCacheId = $accountId;
97 if (isset($this->_classCache[__FUNCTION__][$classCacheId])) {
98 return $this->_classCache[__FUNCTION__][$classCacheId];
101 $cacheId = convertCacheId(__FUNCTION__ . $classCacheId);
102 $memberships = Tinebase_Core::getCache()->load($cacheId);
104 if (! $memberships) {
105 $select = $this->_db->select()
107 ->from(array('group_members' => SQL_TABLE_PREFIX . 'group_members'), array('group_id'))
108 ->where($this->_db->quoteIdentifier('account_id') . ' = ?', $accountId);
110 $stmt = $this->_db->query($select);
112 $memberships = $stmt->fetchAll(Zend_Db::FETCH_COLUMN);
114 Tinebase_Core::getCache()->save($memberships, $cacheId);
117 $this->_classCache[__FUNCTION__][$classCacheId] = $memberships;
123 * get list of groupmembers
125 * @param int $_groupId
126 * @return array with account ids
128 public function getGroupMembers($_groupId)
130 $groupId = Tinebase_Model_Group::convertGroupIdToInt($_groupId);
132 $cacheId = convertCacheId(__FUNCTION__ . $groupId);
133 $members = Tinebase_Core::getCache()->load($cacheId);
138 $select = $this->groupMembersTable->select();
139 $select->where($this->_db->quoteIdentifier('group_id') . ' = ?', $groupId);
141 $rows = $this->groupMembersTable->fetchAll($select);
143 foreach($rows as $member) {
144 $members[] = $member->account_id;
147 Tinebase_Core::getCache()->save($members, $cacheId);
154 * replace all current groupmembers with the new groupmembers list
156 * @param mixed $_groupId
157 * @param array $_groupMembers
159 public function setGroupMembers($_groupId, $_groupMembers)
161 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
162 . ' Setting ' . count($_groupMembers) . ' new groupmembers for group ' . $_groupId);
164 if ($this instanceof Tinebase_Group_Interface_SyncAble) {
165 $_groupMembers = $this->setGroupMembersInSyncBackend($_groupId, $_groupMembers);
168 $this->setGroupMembersInSqlBackend($_groupId, $_groupMembers);
172 * replace all current groupmembers with the new groupmembers list
174 * @param mixed $_groupId
175 * @param array $_groupMembers
177 public function setGroupMembersInSqlBackend($_groupId, $_groupMembers)
179 $groupId = Tinebase_Model_Group::convertGroupIdToInt($_groupId);
181 // remove old members
182 $where = $this->_db->quoteInto($this->_db->quoteIdentifier('group_id') . ' = ?', $groupId);
183 $this->groupMembersTable->delete($where);
185 // check if users have accounts
186 $userIdsWithExistingAccounts = Tinebase_User::getInstance()->getMultiple($_groupMembers)->getArrayOfIds();
188 if (count($_groupMembers) > 0) {
190 foreach ($_groupMembers as $accountId) {
191 $accountId = Tinebase_Model_User::convertUserIdToInt($accountId);
192 if (in_array($accountId, $userIdsWithExistingAccounts)) {
193 $this->_db->insert(SQL_TABLE_PREFIX . 'group_members', array(
194 'group_id' => $groupId,
195 'account_id' => $accountId
198 if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
199 . ' User with ID ' . $accountId . ' does not have an account!');
202 $this->_clearCache(array('getGroupMemberships' => $accountId));
206 $this->_clearCache(array('getGroupMembers' => $groupId));
210 * invalidate cache by type/id
212 * @param array $cacheIds
214 protected function _clearCache($cacheIds = array())
216 $cache = Tinebase_Core::getCache();
218 foreach ($cacheIds as $type => $id) {
219 $cacheId = convertCacheId($type . $id);
220 $cache->remove($cacheId);
223 $this->resetClassCache();
227 * set all groups an account is member of
229 * @param mixed $_userId the userid as string or Tinebase_Model_User
230 * @param mixed $_groupIds
234 public function setGroupMemberships($_userId, $_groupIds)
236 if(count($_groupIds) === 0) {
237 throw new Tinebase_Exception_InvalidArgument('user must belong to at least one group');
240 if($this instanceof Tinebase_Group_Interface_SyncAble) {
241 $this->setGroupMembershipsInSyncBackend($_userId, $_groupIds);
244 return $this->setGroupMembershipsInSqlBackend($_userId, $_groupIds);
248 * set all groups an user is member of
250 * @param mixed $_usertId the account as integer or Tinebase_Model_User
251 * @param mixed $_groupIds
254 public function setGroupMembershipsInSqlBackend($_userId, $_groupIds)
256 if ($_groupIds instanceof Tinebase_Record_RecordSet) {
257 $_groupIds = $_groupIds->getArrayOfIds();
260 if (count($_groupIds) === 0) {
261 throw new Tinebase_Exception_InvalidArgument('user must belong to at least one group');
264 $userId = Tinebase_Model_user::convertUserIdToInt($_userId);
266 $groupMemberships = $this->getGroupMemberships($userId);
268 $removeGroupMemberships = array_diff($groupMemberships, $_groupIds);
269 $addGroupMemberships = array_diff($_groupIds, $groupMemberships);
271 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' current groupmemberships: ' . print_r($groupMemberships, true));
272 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' new groupmemberships: ' . print_r($_groupIds, true));
273 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' added groupmemberships: ' . print_r($addGroupMemberships, true));
274 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' removed groupmemberships: ' . print_r($removeGroupMemberships, true));
276 foreach ($addGroupMemberships as $groupId) {
277 $this->addGroupMemberInSqlBackend($groupId, $userId);
280 foreach ($removeGroupMemberships as $groupId) {
281 $this->removeGroupMemberFromSqlBackend($groupId, $userId);
284 $event = new Tinebase_Group_Event_SetGroupMemberships(array(
286 'addedMemberships' => $addGroupMemberships,
287 'removedMemberships' => $removeGroupMemberships
289 Tinebase_Event::fireEvent($event);
291 return $this->getGroupMemberships($userId);
295 * add a new groupmember to a group
297 * @param string $_groupId
298 * @param string $_accountId
300 public function addGroupMember($_groupId, $_accountId)
302 if ($this instanceof Tinebase_Group_Interface_SyncAble) {
303 $this->addGroupMemberInSyncBackend($_groupId, $_accountId);
306 $this->addGroupMemberInSqlBackend($_groupId, $_accountId);
310 * add a new groupmember to a group
312 * @param string $_groupId
313 * @param string $_accountId
315 public function addGroupMemberInSqlBackend($_groupId, $_accountId)
317 $groupId = Tinebase_Model_Group::convertGroupIdToInt($_groupId);
318 $accountId = Tinebase_Model_User::convertUserIdToInt($_accountId);
320 $memberShips = $this->getGroupMemberships($accountId);
322 if (!in_array($groupId, $memberShips)) {
324 'group_id' => $groupId,
325 'account_id' => $accountId
328 $this->groupMembersTable->insert($data);
330 $this->_clearCache(array(
331 'getGroupMembers' => $groupId,
332 'getGroupMemberships' => $accountId,
339 * remove one groupmember from the group
341 * @param mixed $_groupId
342 * @param mixed $_accountId
344 public function removeGroupMember($_groupId, $_accountId)
346 if ($this instanceof Tinebase_Group_Interface_SyncAble) {
347 $this->removeGroupMemberInSyncBackend($_groupId, $_accountId);
350 return $this->removeGroupMemberFromSqlBackend($_groupId, $_accountId);
354 * remove one groupmember from the group
356 * @param mixed $_groupId
357 * @param mixed $_accountId
359 public function removeGroupMemberFromSqlBackend($_groupId, $_accountId)
361 $groupId = Tinebase_Model_Group::convertGroupIdToInt($_groupId);
362 $accountId = Tinebase_Model_User::convertUserIdToInt($_accountId);
365 $this->_db->quoteInto($this->_db->quoteIdentifier('group_id') . '= ?', $groupId),
366 $this->_db->quoteInto($this->_db->quoteIdentifier('account_id') . '= ?', $accountId),
369 $this->groupMembersTable->delete($where);
371 $this->_clearCache(array(
372 'getGroupMembers' => $groupId,
373 'getGroupMemberships' => $accountId,
380 * @param Tinebase_Model_Group $_group
382 * @return Tinebase_Model_Group
384 * @todo do not create group in sql if sync backend is readonly?
386 public function addGroup(Tinebase_Model_Group $_group)
388 if ($this instanceof Tinebase_Group_Interface_SyncAble) {
389 $groupFromSyncBackend = $this->addGroupInSyncBackend($_group);
391 if (isset($groupFromSyncBackend->id)) {
392 $_group->setId($groupFromSyncBackend->getId());
396 return $this->addGroupInSqlBackend($_group);
402 * @param Tinebase_Model_Group $group
403 * @return Tinebase_Model_Group
405 public function create(Tinebase_Model_Group $group)
407 return $this->addGroup($group);
411 * create a new group in sql backend
413 * @param Tinebase_Model_Group $_group
415 * @return Tinebase_Model_Group
416 * @throws Tinebase_Exception_Record_Validation
418 public function addGroupInSqlBackend(Tinebase_Model_Group $_group)
420 if(!$_group->isValid()) {
421 throw new Tinebase_Exception_Record_Validation('invalid group object');
424 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
425 . ' Creating new group ' . $_group->name
426 //. print_r($_group->toArray(), true)
429 if(!isset($_group->id)) {
430 $groupId = $_group->generateUID();
431 $_group->setId($groupId);
434 if (empty($_group->list_id)) {
435 $_group->visibility = 'hidden';
436 $_group->list_id = null;
439 $data = $_group->toArray();
441 unset($data['members']);
442 unset($data['container_id']);
444 $this->groupsTable->insert($data);
452 * @param Tinebase_Model_Group $_group
454 * @return Tinebase_Model_Group
456 public function updateGroup(Tinebase_Model_Group $_group)
458 if ($this instanceof Tinebase_Group_Interface_SyncAble) {
459 $this->updateGroupInSyncBackend($_group);
462 return $this->updateGroupInSqlBackend($_group);
466 * create a new group in sync backend
468 * NOTE: sets visibility to HIDDEN if list_id is empty
470 * @param Tinebase_Model_Group $_group
471 * @return Tinebase_Model_Group
473 public function updateGroupInSqlBackend(Tinebase_Model_Group $_group)
475 $groupId = Tinebase_Model_Group::convertGroupIdToInt($_group);
477 if (empty($_group->list_id)) {
478 $_group->visibility = Tinebase_Model_Group::VISIBILITY_HIDDEN;
479 $_group->list_id = null;
483 'name' => $_group->name,
484 'description' => $_group->description,
485 'visibility' => $_group->visibility,
486 'email' => $_group->email,
487 'list_id' => $_group->list_id
490 $where = $this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', $groupId);
492 $this->groupsTable->update($data, $where);
494 return $this->getGroupById($groupId);
500 * @param mixed $_groupId
502 * @throws Tinebase_Exception_Backend
504 public function deleteGroups($_groupId)
508 if (is_array($_groupId) or $_groupId instanceof Tinebase_Record_RecordSet) {
509 foreach ($_groupId as $groupId) {
510 $groupIds[] = Tinebase_Model_Group::convertGroupIdToInt($groupId);
513 $groupIds[] = Tinebase_Model_Group::convertGroupIdToInt($_groupId);
517 $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
519 $this->_updatePrimaryGroupsOfUsers($groupIds);
521 $this->deleteGroupsInSqlBackend($groupIds);
522 if ($this instanceof Tinebase_Group_Interface_SyncAble) {
523 $this->deleteGroupsInSyncBackend($groupIds);
526 Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
528 } catch (Exception $e) {
529 Tinebase_TransactionManager::getInstance()->rollBack();
530 Tinebase_Exception::log($e);
531 throw new Tinebase_Exception_Backend($e->getMessage());
536 * set primary group for accounts with given primary group id
538 * @param array $groupIds
539 * @param string $newPrimaryGroupId
540 * @throws Tinebase_Exception_Record_NotDefined
542 protected function _updatePrimaryGroupsOfUsers($groupIds, $newPrimaryGroupId = null)
544 if ($newPrimaryGroupId === null) {
545 $newPrimaryGroupId = $this->getDefaultGroup()->getId();
547 foreach ($groupIds as $groupId) {
548 $users = Tinebase_User::getInstance()->getUsersByPrimaryGroup($groupId);
549 $users->accountPrimaryGroup = $newPrimaryGroupId;
550 foreach ($users as $user) {
551 Tinebase_User::getInstance()->updateUser($user);
557 * delete groups in sql backend
559 * @param array $groupIds
561 public function deleteGroupsInSqlBackend($groupIds)
563 $where = $this->_db->quoteInto($this->_db->quoteIdentifier('group_id') . ' IN (?)', (array) $groupIds);
564 $this->groupMembersTable->delete($where);
565 $where = $this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' IN (?)', (array) $groupIds);
566 $this->groupsTable->delete($where);
570 * Delete all groups returned by {@see getGroups()} using {@see deleteGroups()}
573 public function deleteAllGroups()
575 $groups = $this->getGroups();
577 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Deleting ' . count($groups) .' groups');
579 if(count($groups) > 0) {
580 $this->deleteGroups($groups);
587 * @param string $_filter
588 * @param string $_sort
589 * @param string $_dir
592 * @return Tinebase_Record_RecordSet with record class Tinebase_Model_Group
594 public function getGroups($_filter = NULL, $_sort = 'name', $_dir = 'ASC', $_start = NULL, $_limit = NULL)
596 $select = $this->_getSelect();
598 if($_filter !== NULL) {
599 $select->where($this->_db->quoteIdentifier($this->_tableName. '.name') . ' LIKE ?', '%' . $_filter . '%');
601 if($_sort !== NULL) {
602 $select->order($this->_tableName . '.' . $_sort . ' ' . $_dir);
604 if($_start !== NULL) {
605 $select->limit($_limit, $_start);
608 $stmt = $this->_db->query($select);
609 $queryResult = $stmt->fetchAll();
610 $stmt->closeCursor();
612 $result = new Tinebase_Record_RecordSet('Tinebase_Model_Group', $queryResult, TRUE);
620 * @param string $_name
621 * @return Tinebase_Model_Group
622 * @throws Tinebase_Exception_Record_NotDefined
624 public function getGroupByName($_name)
626 $result = $this->getGroupByPropertyFromSqlBackend('name', $_name);
632 * get group by property
634 * @param string $_property the key to filter
635 * @param string $_value the value to search for
637 * @return Tinebase_Model_Group
638 * @throws Tinebase_Exception_Record_NotDefined
639 * @throws Tinebase_Exception_InvalidArgument
641 public function getGroupByPropertyFromSqlBackend($_property, $_value)
643 if (! in_array($_property, array('id', 'name', 'description', 'list_id', 'email'))) {
644 throw new Tinebase_Exception_InvalidArgument('property not allowed');
647 $select = $this->_getSelect();
649 $select->where($this->_db->quoteIdentifier($this->_tableName . '.' . $_property) . ' = ?', $_value);
651 $stmt = $this->_db->query($select);
652 $queryResult = $stmt->fetch();
653 $stmt->closeCursor();
656 throw new Tinebase_Exception_Record_NotDefined('Group not found.');
659 $result = new Tinebase_Model_Group($queryResult, TRUE);
668 * @param string $_name
669 * @return Tinebase_Model_Group
670 * @throws Tinebase_Exception_Record_NotDefined
672 public function getGroupById($_groupId)
674 $groupdId = Tinebase_Model_Group::convertGroupIdToInt($_groupId);
676 $result = $this->getGroupByPropertyFromSqlBackend('id', $groupdId);
682 * Get multiple groups
684 * @param string|array $_ids Ids
685 * @return Tinebase_Record_RecordSet
687 * @todo this should return the container_id, too
689 public function getMultiple($_ids)
691 $result = new Tinebase_Record_RecordSet('Tinebase_Model_Group');
693 if (! empty($_ids)) {
694 $select = $this->groupsTable->select();
695 $select->where($this->_db->quoteIdentifier('id') . ' IN (?)', array_unique((array) $_ids));
697 $rows = $this->groupsTable->fetchAll($select);
698 foreach ($rows as $row) {
699 $result->addRecord(new Tinebase_Model_Group($row->toArray(), TRUE));
707 * get the basic select object to fetch records from the database
709 * NOTE: container_id is joined from addressbook lists table
711 * @param array|string|Zend_Db_Expr $_cols columns to get, * per default
712 * @param boolean $_getDeleted get deleted records (if modlog is active)
713 * @return Zend_Db_Select
715 protected function _getSelect($_cols = '*', $_getDeleted = FALSE)
717 $select = $this->_db->select();
719 $select->from(array($this->_tableName => SQL_TABLE_PREFIX . $this->_tableName), $_cols);
721 if ($this->_addressBookInstalled === true) {
723 array('addressbook_lists' => SQL_TABLE_PREFIX . 'addressbook_lists'),
724 $this->_db->quoteIdentifier($this->_tableName . '.list_id') . ' = ' . $this->_db->quoteIdentifier('addressbook_lists.id'),
725 array('container_id')
733 * Method called by {@see Addressbook_Setup_Initialize::_initilaize()}
736 * @return unknown_type
738 public function __importGroupMembers($_options = null)