0011522: improve handling of group-lists
[tine20] / tine20 / Addressbook / Backend / List.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Addressbook
6  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
7  * @author      Lars Kneschke <l.kneschke@metaways.de>
8  * @copyright   Copyright (c) 2007-2010 Metaways Infosystems GmbH (http://www.metaways.de)
9  * 
10  * @todo        move visibility='displayed' check from getSelect to contact filter
11  */
12
13 /**
14  * sql backend class for the addressbook
15  *
16  * @package     Addressbook
17  */
18 class Addressbook_Backend_List extends Tinebase_Backend_Sql_Abstract
19 {
20     /**
21      * Table name without prefix
22      *
23      * @var string
24      */
25     protected $_tableName = 'addressbook_lists';
26     
27     /**
28      * Model name
29      *
30      * @var string
31      */
32     protected $_modelName = 'Addressbook_Model_List';
33
34     /**
35      * if modlog is active, we add 'is_deleted = 0' to select object in _getSelect()
36      *
37      * @var boolean
38      */
39     protected $_modlogActive = TRUE;
40
41     /**
42      * default column(s) for count
43      *
44      * @var string
45      */
46     protected $_defaultCountCol = 'id';
47
48     /**
49      * foreign tables 
50      * name => array(table, joinOn, field)
51      *
52      * @var array
53      */
54     protected $_foreignTables = array(
55         'members'    => array(
56             'table'  => 'addressbook_list_members',
57             'field'  => 'contact_id',
58             'joinOn' => 'list_id',
59         ),
60         'group_id'    => array(
61             'table'        => 'groups',
62             'field'        => 'id',
63             'joinOn'       => 'list_id',
64         // use first element of result array
65             'singleValue'  => TRUE,
66         )
67     );
68
69     /**
70      * the constructor
71      * 
72      * allowed options:
73      *  - modelName
74      *  - tableName
75      *  - tablePrefix
76      *  - modlogActive
77      *  
78      * @param Zend_Db_Adapter_Abstract $_db (optional)
79      * @param array $_options (optional)
80      * @throws Tinebase_Exception_Backend_Database
81      */
82     public function __construct($_dbAdapter = NULL, $_options = array())
83     {
84         parent::__construct($_dbAdapter, $_options);
85
86         /**
87          * TODO move this code somewhere and make it optionally. Maybe even make it a new controller / frontend action and request the data async
88          */
89         if (Addressbook_Config::getInstance()->featureEnabled(Addressbook_Config::FEATURE_LIST_VIEW)) {
90             $this->_additionalColumns['emails'] = new Zend_Db_Expr('(' .
91                 $this->_db->select()
92                     ->from($this->_tablePrefix . 'addressbook', array($this->_dbCommand->getAggregate('email')))
93                     ->where($this->_db->quoteIdentifier('id') . ' IN ?', $this->_db->select()
94                         ->from(array('addressbook_list_members' => $this->_tablePrefix . 'addressbook_list_members'), array('contact_id'))
95                         ->where($this->_db->quoteIdentifier('addressbook_list_members.list_id') . ' = ' . $this->_db->quoteIdentifier('addressbook_lists.id'))
96                     ) .
97                 ')');
98         }
99     }
100
101     /**
102      * converts record into raw data for adapter
103      *
104      * @param  Tinebase_Record_Abstract $_record
105      * @return array
106      */
107     protected function _recordToRawData($_record)
108     {
109         $result = parent::_recordToRawData($_record);
110         
111         // stored in foreign key
112         unset($result['members']);
113         unset($result['group_id']);
114
115         return $result;
116     }
117
118     /**
119      * add new members to list
120      * 
121      * @param  mixed  $_listId
122      * @param  mixed  $_newMembers
123      * @return Addressbook_Model_List
124      */
125     public function addListMember($_listId, $_newMembers)
126     {
127         $list = $this->get($_listId);
128         
129         if (empty($_newMembers)) {
130             return $list;
131         }
132         
133         $newMembers = Tinebase_Record_RecordSet::getIdsFromMixed($_newMembers);
134         $idsToAdd   = array_diff($newMembers, $list->members);
135         
136         $listId     = Tinebase_Record_Abstract::convertId($_listId, $this->_modelName);
137         
138         $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
139         
140         foreach ($idsToAdd as $id) {
141             $recordArray = array (
142                 $this->_foreignTables['members']['joinOn'] => $listId,
143                 $this->_foreignTables['members']['field']  => $id
144             );
145             $this->_db->insert($this->_tablePrefix . $this->_foreignTables['members']['table'], $recordArray);
146         }
147         
148         Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
149         
150         return $this->get($_listId);
151     }
152     
153     /**
154      * Delete all lists returned by {@see getAll()} using {@see delete()}
155      * @return void
156      */
157     public function deleteAllLists()
158     {
159         $lists = $this->getAll();
160         
161         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Deleting ' . count($lists) .' lists');
162         
163         if(count($lists) > 0) {
164             $this->delete($lists->getArrayOfIds());
165         }
166     }
167     
168     /**
169      * remove members from list
170      * 
171      * @param  mixed  $_listId
172      * @param  mixed  $_membersToRemove
173      * @return Addressbook_Model_List
174      */
175     public function removeListMember($_listId, $_membersToRemove)
176     {
177         $list = $this->get($_listId);
178         
179         if (empty($_membersToRemove)) {
180             return $list;
181         }
182         
183         $removeMembers  = Tinebase_Record_RecordSet::getIdsFromMixed($_membersToRemove);
184         $idsToRemove = array_intersect($list->members, $removeMembers);
185         $listId      = Tinebase_Record_Abstract::convertId($_listId, $this->_modelName);
186         
187         $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
188         
189         if (!empty($idsToRemove)) {
190             $where = '(' . 
191                 $this->_db->quoteInto($this->_db->quoteIdentifier($this->_tablePrefix . $this->_foreignTables['members']['table'] . '.' . $this->_foreignTables['members']['joinOn']) . ' = ?', $listId) .
192                 ' AND ' .
193                 $this->_db->quoteInto($this->_db->quoteIdentifier($this->_tablePrefix . $this->_foreignTables['members']['table'] . '.' . $this->_foreignTables['members']['field']) . ' IN (?)', $idsToRemove) .
194             ')';
195                 
196             $this->_db->delete($this->_tablePrefix . $this->_foreignTables['members']['table'], $where);
197         }
198         
199         Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
200         
201         return $this->get($_listId);
202     }
203     
204     /**
205     * set all lists an user is member of
206     *
207     * @param  string  $contactId
208     * @param  mixed  $listIds
209     * @return array
210     */
211     public function setMemberships($contactId, $listIds)
212     {
213         $contactId = Tinebase_Record_Abstract::convertId($contactId, 'Addressbook_Model_Contact');
214         
215         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
216             . ' Set ' . count($listIds) . ' list memberships for contact ' . $contactId);
217         
218         if ($listIds instanceof Tinebase_Record_RecordSet) {
219             $listIds = $listIds->getArrayOfIds();
220         }
221     
222         $listMemberships = $this->getMemberships($contactId);
223     
224         $removeListMemberships = array_diff($listMemberships, $listIds);
225         $addListMemberships    = array_diff($listIds, $listMemberships);
226     
227         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' current memberships: ' . print_r($listMemberships, true));
228         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' new memberships: ' . print_r($listIds, true));
229         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' added memberships: ' . print_r($addListMemberships, true));
230         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' removed memberships: ' . print_r($removeListMemberships, true));
231     
232         foreach ($addListMemberships as $listId) {
233             $this->addListMember($listId, $contactId);
234         }
235     
236         foreach ($removeListMemberships as $listId) {
237             $this->removeListMember($listId, $contactId);
238         }
239     
240         return $this->getMemberships($contactId);
241     }
242     
243     /**
244      * get group memberships of contact id
245      * 
246      * @param mixed $contactId
247      * @return array
248      */
249     public function getMemberships($contactId)
250     {
251         $contactId = Tinebase_Record_Abstract::convertId($contactId, 'Addressbook_Model_Contact');
252         
253         $select = $this->_db->select()
254             ->from($this->_tablePrefix . $this->_foreignTables['members']['table'], 'list_id')
255             ->where($this->_db->quoteIdentifier('contact_id') . ' = ?', $contactId);
256         
257         $stmt = $this->_db->query($select);
258         $rows = (array) $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
259         
260         $result = array();
261         foreach ($rows as $membership) {
262             $result[] = $membership['list_id'];
263         }
264         
265         return $result;
266     }
267     
268     /**
269      * get list by group name
270      * 
271      * @param string $groupName
272      * @return NULL|Addressbook_Model_List
273      */
274     public function getByGroupName($groupName)
275     {
276         $filter = new Addressbook_Model_ListFilter(array(
277             array('field' => 'name', 'operator' => 'equals', 'value' => $groupName),
278             array('field' => 'type', 'operator' => 'equals', 'value' => Addressbook_Model_List::LISTTYPE_GROUP)
279         ));
280         
281         $existingLists = $this->search($filter);
282         
283         return $existingLists->getFirstRecord();
284     }
285 }