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