adds directory scanning for apps as fallback
[tine20] / tine20 / Tinebase / Group.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 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Lars Kneschke <l.kneschke@metaways.de>
10  */
11
12 /**
13  * primary class to handle groups
14  *
15  * @package     Tinebase
16  * @subpackage  Group
17  */
18 class Tinebase_Group
19 {
20     /**
21      * backend constants
22      * 
23      * @var string
24      */
25     const ACTIVEDIRECTORY = 'ActiveDirectory';
26     const LDAP            = 'Ldap';
27     const SQL             = 'Sql';
28     const TYPO3           = 'Typo3';
29     
30     
31     /**
32      * default admin group name
33      * 
34      * @var string
35      */
36     const DEFAULT_ADMIN_GROUP = 'Administrators';
37     
38     /**
39      * default user group name
40      * 
41      * @var string
42      */
43     const DEFAULT_USER_GROUP = 'Users';
44     
45     /**
46      * the constructor
47      *
48      * don't use the constructor. use the singleton 
49      */
50     private function __construct() {}
51     
52     /**
53      * don't clone. Use the singleton.
54      *
55      */
56     private function __clone() {}
57
58     /**
59      * holds the instance of the singleton
60      *
61      * @var Tinebase_Group
62      */
63     private static $_instance = NULL;
64     
65     /**
66      * the singleton pattern
67      *
68      * @return Tinebase_Group_Abstract
69      */
70     public static function getInstance() 
71     {
72         if (self::$_instance === NULL) {
73             $backendType = Tinebase_User::getConfiguredBackend();
74             
75             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .' groups backend: ' . $backendType);
76
77             self::$_instance = self::factory($backendType);
78         }
79         
80         return self::$_instance;
81     }
82     
83     /**
84      * return an instance of the current groups backend
85      *
86      * @param   string $_backendType name of the groups backend
87      * @return  Tinebase_Group_Abstract
88      * @throws  Tinebase_Exception_InvalidArgument
89      */
90     public static function factory($_backendType) 
91     {
92         switch($_backendType) {
93             case self::ACTIVEDIRECTORY:
94                 $options = Tinebase_User::getBackendConfiguration();
95                 
96                 $result = new Tinebase_Group_ActiveDirectory($options);
97                 
98                 break;
99                 
100             case self::LDAP:
101                 $options = Tinebase_User::getBackendConfiguration();
102                 
103                 $options['plugins'] = array();
104                 
105                 // manage samba sam?
106                 if (isset(Tinebase_Core::getConfig()->samba) && Tinebase_Core::getConfig()->samba->get('manageSAM', FALSE) == true) {
107                     $options['plugins'][] = Tinebase_Group_Ldap::PLUGIN_SAMBA;
108                     $options[Tinebase_Group_Ldap::PLUGIN_SAMBA] = Tinebase_Core::getConfig()->samba->toArray();
109                 }
110                 
111                 $result = new Tinebase_Group_Ldap($options);
112                 
113                 break;
114                 
115             case self::SQL:
116                 $result = new Tinebase_Group_Sql();
117                 break;
118             
119             case self::TYPO3:
120                 $result = new Tinebase_Group_Typo3();
121                 break;
122                 
123             default:
124                 throw new Tinebase_Exception_InvalidArgument("Groups backend type $_backendType not implemented.");
125         }
126         
127         return $result;
128     }
129     
130     /**
131      * syncronize groupmemberships for given $_username from syncbackend to local sql backend
132      * 
133      * @todo sync secondary group memberships
134      * @param  mixed  $_username  the login id of the user to synchronize
135      */
136     public static function syncMemberships($_username)
137     {
138         if ($_username instanceof Tinebase_Model_FullUser) {
139             $username = $_username->accountLoginName;
140         } else {
141             $username = $_username;
142         }
143         
144         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Sync group memberships for: " . $username);
145         
146         $userBackend  = Tinebase_User::getInstance();
147         $groupBackend = Tinebase_Group::getInstance();
148         
149         $user = $userBackend->getUserByProperty('accountLoginName', $username, 'Tinebase_Model_FullUser');
150         
151         $membershipsSyncBackend = $groupBackend->getGroupMembershipsFromSyncBackend($user);
152         if (! in_array($user->accountPrimaryGroup, $membershipsSyncBackend)) {
153             $membershipsSyncBackend[] = $user->accountPrimaryGroup;
154         }
155         
156         $membershipsSqlBackend = $groupBackend->getGroupMemberships($user);
157         
158         sort($membershipsSqlBackend);
159         sort($membershipsSyncBackend);
160         if ($membershipsSqlBackend == $membershipsSyncBackend) {
161             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
162                 . ' Group memberships are already in sync.');
163             return;
164         }
165         
166         $newGroupMemberships = array_diff($membershipsSyncBackend, $membershipsSqlBackend);
167         foreach ($newGroupMemberships as $groupId) {
168             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Add user to groupId " . $groupId);
169             // make sure new groups exist in sql backend / create empty group if needed
170             try {
171                 $groupBackend->getGroupById($groupId);
172             } catch (Tinebase_Exception_Record_NotDefined $tern) {
173                 $group = $groupBackend->getGroupByIdFromSyncBackend($groupId);
174                 Tinebase_Timemachine_ModificationLog::setRecordMetaData($group, 'create');
175                 $groupBackend->addGroupInSqlBackend($group);
176             }
177         }
178         
179         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
180             .' Set new group memberships: ' . print_r($membershipsSyncBackend, TRUE));
181         
182         $groupIds = $groupBackend->setGroupMembershipsInSqlBackend($user, $membershipsSyncBackend);
183         self::syncListsOfUserContact($groupIds, $user->contact_id);
184     }
185     
186     /**
187      * creates or updates addressbook lists for an array of group ids
188      * 
189      * @param array $groupIds
190      * @param string $contactId
191      */
192     public static function syncListsOfUserContact($groupIds, $contactId)
193     {
194         // check addressbook and empty contact id (for example cronuser)
195         if (! Tinebase_Application::getInstance()->isInstalled('Addressbook') || empty($contactId)) {
196             return;
197         }
198         
199         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
200             .' Syncing ' . count($groupIds) . ' group -> lists / memberships');
201         
202         $listBackend = new Addressbook_Backend_List();
203         
204         $listIds = array();
205         foreach ($groupIds as $groupId) {
206             // get single groups to make sure that container id is joined
207             $group = Tinebase_Group::getInstance()->getGroupById($groupId);
208
209             $list = NULL;
210             if (! empty($group->list_id)) {
211                 try {
212                     $list = $listBackend->get($group->list_id);
213                 } catch (Tinebase_Exception_NotFound $tenf) {
214                     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
215                         .' List ' . $group->name . ' not found.');
216                 }
217             }
218             
219             // could not get list by list_id -> try to get by name 
220             // if no list can be found, create new one
221             if (! $list) {
222                 $list = $listBackend->getByGroupName($group->name);
223                 if (! $list) {
224                     $list = Addressbook_Controller_List::getInstance()->createByGroup($group);
225                 }
226             }
227
228             if ($group->list_id !== $list->getId()) {
229                 // list id changed / is new -> update group and make group visible
230                 $group->list_id = $list->getId();
231                 $group->visibility = Tinebase_Model_Group::VISIBILITY_DISPLAYED;
232                 Tinebase_Timemachine_ModificationLog::setRecordMetaData($group, 'update');
233                 Tinebase_Group::getInstance()->updateGroup($group);
234             }
235             
236             $listIds[] = $list->getId();
237         }
238         
239         $listBackend->setMemberships($contactId, $listIds);
240     }
241     
242     /**
243      * import and sync groups from sync backend
244      */
245     public static function syncGroups()
246     {
247         $groupBackend = Tinebase_Group::getInstance();
248         
249         if (!$groupBackend->isDisabledBackend()) {
250             $groups = $groupBackend->getGroupsFromSyncBackend(NULL, NULL, 'ASC', NULL, NULL);
251         } else {
252             // fake groups by reading all gidnumber's of the accounts
253             $accountProperties = Tinebase_User::getInstance()->getUserAttributes(array('gidnumber'));
254             
255             $groupIds = array();
256             foreach ($accountProperties as $accountProperty) {
257                 $groupIds[$accountProperty['gidnumber']] = $accountProperty['gidnumber'];
258             }
259             
260             $groups = new Tinebase_Record_RecordSet('Tinebase_Model_Group');
261             foreach ($groupIds as $groupId) {
262                 $groups->addRecord(new Tinebase_Model_Group(array(
263                     'id'            => $groupId,
264                     'name'          => 'Group ' . $groupId
265                 ), TRUE));
266             }
267         }
268             
269         foreach ($groups as $group) {
270             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ .
271                 ' Sync group: ' . $group->name . ' - update or create group in local sql backend');
272             try {
273                 $sqlGroup = $groupBackend->getGroupById($group);
274                 
275                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ .
276                     ' Merge missing properties and update group.');
277                 $groupBackend->mergeMissingProperties($group, $sqlGroup);
278                 Tinebase_Timemachine_ModificationLog::setRecordMetaData($group, 'update');
279                 $groupBackend->updateGroupInSqlBackend($group);
280                 
281             } catch (Tinebase_Exception_Record_NotDefined $tern) {
282                 // try to find group by name
283                 try {
284                     $sqlGroup = $groupBackend->getGroupByName($group->name);
285                     
286                     if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ .
287                         ' Delete current sql group as it has the wrong id. Merge missing properties and create new group.');
288                     $groupBackend->mergeMissingProperties($group, $sqlGroup);
289                     $groupBackend->deleteGroupsInSqlBackend(array($sqlGroup->getId()));
290                     
291                 } catch (Tinebase_Exception_Record_NotDefined $tern2) {
292                     if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ .
293                         ' Group not found by ID and name, adding new group.');
294                 }
295                 Tinebase_Timemachine_ModificationLog::setRecordMetaData($group, 'create');
296                 $groupBackend->addGroupInSqlBackend($group);
297             }
298         }
299     }
300     
301     /**
302      * create initial groups
303      * 
304      * Method is called during Setup Initialization
305      */
306     public static function createInitialGroups()
307     {
308         $defaultAdminGroupName = (Tinebase_User::getBackendConfiguration(Tinebase_User::DEFAULT_ADMIN_GROUP_NAME_KEY)) 
309             ? Tinebase_User::getBackendConfiguration(Tinebase_User::DEFAULT_ADMIN_GROUP_NAME_KEY)
310             : self::DEFAULT_ADMIN_GROUP;
311         $adminGroup = new Tinebase_Model_Group(array(
312             'name'          => $defaultAdminGroupName,
313             'description'   => 'Group of administrative accounts'
314         ));
315         Tinebase_Timemachine_ModificationLog::setRecordMetaData($adminGroup, 'create');
316         Tinebase_Group::getInstance()->addGroup($adminGroup);
317
318         $defaultUserGroupName = (Tinebase_User::getBackendConfiguration(Tinebase_User::DEFAULT_USER_GROUP_NAME_KEY))
319             ? Tinebase_User::getBackendConfiguration(Tinebase_User::DEFAULT_USER_GROUP_NAME_KEY)
320             : self::DEFAULT_USER_GROUP;
321         $userGroup = new Tinebase_Model_Group(array(
322             'name'          => $defaultUserGroupName,
323             'description'   => 'Group of user accounts'
324         ));
325         Tinebase_Timemachine_ModificationLog::setRecordMetaData($userGroup, 'create');
326         Tinebase_Group::getInstance()->addGroup($userGroup);
327     }
328 }