Admin - searchFullUser - add emailUser plugin resolving
[tine20] / tine20 / Admin / Controller / User.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Admin
6  * @subpackage  Controller
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Philipp Schüle <p.schuele@metaways.de>
9  * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
10  * 
11  * @todo        catch error (and show alert) if postfix email already exists
12  * @todo        extend Tinebase_Controller_Record_Abstract
13  */
14
15 /**
16  * User Controller for Admin application
17  *
18  * @package     Admin
19  * @subpackage  Controller
20  */
21 class Admin_Controller_User extends Tinebase_Controller_Abstract
22 {
23     /**
24      * @var Tinebase_User_Abstract
25      */
26     protected $_userBackend = NULL;
27     
28     /**
29      * @var Tinebase_SambaSAM_Ldap
30      */
31     protected $_samBackend = NULL;
32
33     /**
34      * the constructor
35      *
36      * don't use the constructor. use the singleton 
37      */
38     private function __construct() 
39     {
40         $this->_applicationName = 'Admin';
41         
42         $this->_userBackend = Tinebase_User::getInstance();
43     }
44
45     /**
46      * don't clone. Use the singleton.
47      *
48      */
49     private function __clone() 
50     {
51     }
52
53     /**
54      * holds the instance of the singleton
55      *
56      * @var Admin_Controller_User
57      */
58     private static $_instance = NULL;
59
60     /**
61      * the singleton pattern
62      *
63      * @return Admin_Controller_User
64      */
65     public static function getInstance() 
66     {
67         if (self::$_instance === NULL) {
68             self::$_instance = new Admin_Controller_User;
69         }
70         
71         return self::$_instance;
72     }
73
74     /**
75      * get list of full accounts -> renamed to search full users
76      *
77      * @param string $_filter string to search accounts for
78      * @param string $_sort
79      * @param string $_dir
80      * @param int $_start
81      * @param int $_limit
82      * @return Tinebase_Record_RecordSet with record class Tinebase_Model_FullUser
83      */
84     public function searchFullUsers($_filter, $_sort = NULL, $_dir = 'ASC', $_start = NULL, $_limit = NULL)
85     {
86         $this->checkRight('VIEW_ACCOUNTS');
87         
88         $result = $this->_userBackend->getUsers($_filter, $_sort, $_dir, $_start, $_limit, 'Tinebase_Model_FullUser');
89
90         $emailUser = Tinebase_EmailUser::getInstance();
91         foreach ($result as $user) {
92             $emailUser->inspectGetUserByProperty($user);
93         }
94         
95         return $result;
96     }
97     
98     /**
99      * count users
100      *
101      * @param string $_filter string to search user accounts for
102      * @return int total user count
103      */
104     public function searchCount($_filter)
105     {
106         $this->checkRight('VIEW_ACCOUNTS');
107         
108         $result = $this->_userBackend->getUsersCount($_filter);
109         
110         return $result;
111     }
112     
113     /**
114      * get account
115      *
116      * @param   string  $_accountId  account id to get
117      * @return  Tinebase_Model_FullUser
118      */
119     public function get($_userId)
120     {
121         $this->checkRight('VIEW_ACCOUNTS');
122         
123         $user = $this->_userBackend->getUserById($_userId, 'Tinebase_Model_FullUser');
124         
125         return $user;
126     }
127     
128     /**
129      * set account status
130      *
131      * @param   string $_accountId  account id
132      * @param   string $_status     status to set
133      * @return  int
134      */
135     public function setAccountStatus($_accountId, $_status)
136     {
137         $this->checkRight('MANAGE_ACCOUNTS');
138         
139         $result = $this->_userBackend->setStatus($_accountId, $_status);
140         
141         if ($_status === Tinebase_Model_FullUser::ACCOUNT_STATUS_DISABLED) {
142             // TODO send this for blocked/expired, too? allow to configure this?
143             Tinebase_User::getInstance()->sendDeactivationNotification($_accountId);
144         }
145         
146         return $result;
147     }
148     
149     /**
150      * set the password for a given account
151      *
152      * @param  Tinebase_Model_FullUser  $_account the account
153      * @param  string                   $_password the new password
154      * @param  string                   $_passwordRepeat the new password again
155      * @param  bool                     $_mustChange
156      * @return void
157      * 
158      * @todo add must change pwd info to normal tine user accounts
159      */
160     public function setAccountPassword(Tinebase_Model_FullUser $_account, $_password, $_passwordRepeat, $_mustChange = null)
161     {
162         $this->checkRight('MANAGE_ACCOUNTS');
163         
164         if ($_password != $_passwordRepeat) {
165             throw new Admin_Exception("Passwords don't match.");
166         }
167         
168         $this->_userBackend->setPassword($_account, $_password, true, $_mustChange);
169         
170         Tinebase_Core::getLogger()->info(
171             __METHOD__ . '::' . __LINE__ . 
172             ' Set new password for user ' . $_account->accountLoginName . '. Must change:' . $_mustChange
173         );
174     }
175
176     /**
177      * set the pin for a given account
178      *
179      * @param  Tinebase_Model_FullUser  $_account the account
180      * @param  string                   $_password the new password
181      * @return void
182      */
183     public function setAccountPin(Tinebase_Model_FullUser $_account, $_password)
184     {
185         $this->checkRight('MANAGE_ACCOUNTS');
186
187         $this->_userBackend->setPin($_account, $_password);
188
189         Tinebase_Core::getLogger()->info(
190             __METHOD__ . '::' . __LINE__ .
191             ' Set new pin for user ' . $_account->accountLoginName);
192     }
193
194     /**
195      * update user
196      *
197      * @param  Tinebase_Model_FullUser    $_user            the user
198      * @param  string                     $_password        the new password
199      * @param  string                     $_passwordRepeat  the new password again
200      * 
201      * @return Tinebase_Model_FullUser
202      */
203     public function update(Tinebase_Model_FullUser $_user, $_password, $_passwordRepeat)
204     {
205         $this->checkRight('MANAGE_ACCOUNTS');
206         
207         $oldUser = $this->_userBackend->getUserByProperty('accountId', $_user, 'Tinebase_Model_FullUser');
208         
209         if ($oldUser->accountLoginName !== $_user->accountLoginName) {
210             $this->_checkLoginNameExistance($_user);
211         }
212         $this->_checkLoginNameLength($_user);
213         $this->_checkPrimaryGroupExistance($_user);
214         
215         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Update user ' . $_user->accountLoginName);
216         
217         try {
218             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
219             
220             if (Tinebase_Application::getInstance()->isInstalled('Addressbook') === true) {
221                 $_user->contact_id = $oldUser->contact_id;
222                 $contact = $this->createOrUpdateContact($_user);
223                 $_user->contact_id = $contact->getId();
224             }
225             
226             Tinebase_Timemachine_ModificationLog::setRecordMetaData($_user, 'update', $oldUser);
227             
228             $user = $this->_userBackend->updateUser($_user);
229
230             // update account status if changed to enabled/disabled
231             if ($oldUser->accountStatus !== $_user->accountStatus && in_array($_user->accountStatus, array(
232                     Tinebase_Model_FullUser::ACCOUNT_STATUS_ENABLED,
233                     Tinebase_Model_FullUser::ACCOUNT_STATUS_DISABLED,
234                 ))) {
235                 $this->_userBackend->setStatus($_user->getId(), $_user->accountStatus);
236             }
237             
238             // make sure primary groups is in the list of groupmemberships
239             $groups = array_unique(array_merge(array($user->accountPrimaryGroup), (array) $_user->groups));
240             Admin_Controller_Group::getInstance()->setGroupMemberships($user, $groups);
241             
242             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
243             
244         } catch (Exception $e) {
245             Tinebase_TransactionManager::getInstance()->rollBack();
246             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e);
247             
248             if ($e instanceof Zend_Db_Statement_Exception && preg_match('/Lock wait timeout exceeded/', $e->getMessage())) {
249                 throw new Tinebase_Exception_Backend_Database_LockTimeout($e->getMessage());
250             }
251             
252             throw $e;
253         }
254         
255         // fire needed events
256         $event = new Admin_Event_UpdateAccount;
257         $event->account = $user;
258         $event->oldAccount = $oldUser;
259         Tinebase_Event::fireEvent($event);
260         
261         if (!empty($_password) && !empty($_passwordRepeat)) {
262             $this->setAccountPassword($_user, $_password, $_passwordRepeat, FALSE);
263         }
264
265         return $user;
266     }
267     
268     /**
269      * create user
270      *
271      * @param  Tinebase_Model_FullUser  $_account           the account
272      * @param  string                     $_password           the new password
273      * @param  string                     $_passwordRepeat  the new password again
274      * @return Tinebase_Model_FullUser
275      */
276     public function create(Tinebase_Model_FullUser $_user, $_password, $_passwordRepeat)
277     {
278         $this->checkRight('MANAGE_ACCOUNTS');
279         
280         // avoid forging accountId, gets created in backend
281         unset($_user->accountId);
282         
283         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Create new user ' . $_user->accountLoginName);
284         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_user->toArray(), TRUE));
285         
286         $this->_checkLoginNameExistance($_user);
287         $this->_checkLoginNameLength($_user);
288         $this->_checkPrimaryGroupExistance($_user);
289         
290         if ($_password != $_passwordRepeat) {
291             throw new Admin_Exception("Passwords don't match.");
292         } else if (empty($_password)) {
293             $_password = '';
294             $_passwordRepeat = '';
295         }
296         Tinebase_User::getInstance()->checkPasswordPolicy($_password, $_user);
297         
298         try {
299             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
300             
301             if (Tinebase_Application::getInstance()->isInstalled('Addressbook') === true) {
302                 $contact = $this->createOrUpdateContact($_user);
303                 $_user->contact_id = $contact->getId();
304             }
305             
306             Tinebase_Timemachine_ModificationLog::setRecordMetaData($_user, 'create');
307             
308             $user = $this->_userBackend->addUser($_user);
309             
310             // make sure primary groups is in the list of groupmemberships
311             $groups = array_unique(array_merge(array($user->accountPrimaryGroup), (array) $_user->groups));
312             Admin_Controller_Group::getInstance()->setGroupMemberships($user, $groups);
313             
314             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
315
316         } catch (Exception $e) {
317             Tinebase_TransactionManager::getInstance()->rollBack();
318             Tinebase_Exception::log($e);
319             throw $e;
320         }
321         
322         $event = new Admin_Event_AddAccount(array(
323             'account' => $user
324         ));
325         Tinebase_Event::fireEvent($event);
326         
327         $this->setAccountPassword($user, $_password, $_passwordRepeat);
328
329         return $user;
330     }
331     
332     /**
333      * look for user with the same login name
334      * 
335      * @param Tinebase_Model_FullUser $user
336      * @return boolean
337      * @throws Tinebase_Exception_SystemGeneric
338      */
339     protected function _checkLoginNameExistance(Tinebase_Model_FullUser $user)
340     {
341         try {
342             $existing = Tinebase_User::getInstance()->getUserByLoginName($user->accountLoginName);
343             if ($user->getId() === NULL || $existing->getId() !== $user->getId()) {
344                 throw new Tinebase_Exception_SystemGeneric('Login name already exists. Please choose another one.');
345             }
346         } catch (Tinebase_Exception_NotFound $tenf) {
347         }
348         
349         return TRUE;
350     }
351     
352     /**
353      * Check login name length
354      *
355      * @param Tinebase_Model_FullUser $user
356      * @return boolean
357      * @throws Tinebase_Exception_SystemGeneric
358      */
359     protected function _checkLoginNameLength(Tinebase_Model_FullUser $user)
360     {
361         $maxLoginNameLength = Tinebase_Config::getInstance()->get(Tinebase_Config::MAX_USERNAME_LENGTH);
362         if (!empty($maxLoginNameLength) && strlen($user->accountLoginName) > $maxLoginNameLength) {
363             throw new Tinebase_Exception_SystemGeneric('The login name exceeds the maximum length of  ' . $maxLoginNameLength . ' characters. Please choose another one.');
364         }
365         return TRUE;
366     }
367     
368     /**
369      * look for primary group, if it does not exist, fallback to default user group
370      * 
371      * @param Tinebase_Model_FullUser $user
372      * @throws Tinebase_Exception_SystemGeneric
373      */
374     protected function _checkPrimaryGroupExistance(Tinebase_Model_FullUser $user)
375     {
376         try {
377             $group = Tinebase_Group::getInstance()->getGroupById($user->accountPrimaryGroup);
378         } catch (Tinebase_Exception_Record_NotDefined $ternd) {
379             $defaultUserGroup = Tinebase_Group::getInstance()->getDefaultGroup();
380             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
381                 . ' Group with id ' . $user->accountPrimaryGroup . ' not found. Use default group (' . $defaultUserGroup->name
382                 . ') as primary group for ' . $user->accountLoginName);
383             
384             $user->accountPrimaryGroup = $defaultUserGroup->getId();
385         }
386     }
387     
388     /**
389      * delete accounts
390      *
391      * @param   mixed $_accountIds  array of account ids
392      * @return  array with success flag
393      * @throws  Tinebase_Exception_Record_NotAllowed
394      */
395     public function delete($_accountIds)
396     {
397         $this->checkRight('MANAGE_ACCOUNTS');
398         
399         $groupsController = Admin_Controller_Group::getInstance();
400         
401         foreach ((array)$_accountIds as $accountId) {
402             if ($accountId === Tinebase_Core::getUser()->getId()) {
403                 $message = 'You are not allowed to delete yourself!';
404                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' ' . $message);
405                 throw new Tinebase_Exception_AccessDenied($message);
406             }
407             
408             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " about to remove user with id: {$accountId}");
409             
410             $oldUser = $this->get($accountId);
411             
412             $memberships = $groupsController->getGroupMemberships($accountId);
413             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " removing user from groups: " . print_r($memberships, true));
414             
415             foreach ((array)$memberships as $groupId) {
416                 $groupsController->removeGroupMember($groupId, $accountId);
417             }
418             
419             $this->_userBackend->deleteUser($accountId);
420         }
421     }
422     
423     /**
424      * returns all shared addressbooks
425      * 
426      * @return Tinebase_Record_RecordSet of shared addressbooks
427      * 
428      * @todo do we need to fetch ALL shared containers here (even if admin user has NO grants for them)?
429      */
430     public function searchSharedAddressbooks()
431     {
432         $this->checkRight('MANAGE_ACCOUNTS');
433         
434         return Tinebase_Container::getInstance()->getSharedContainer(Tinebase_Core::getUser(), 'Addressbook', Tinebase_Model_Grants::GRANT_READ, TRUE);
435     }
436     
437     /**
438      * returns default internal addressbook container
439      * 
440      * @return string|int ID
441      */
442     public function getDefaultInternalAddressbook()
443     {
444         $appConfigDefaults = Admin_Controller::getInstance()->getConfigSettings();
445         if (empty($appConfigDefaults[Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK])) {
446             $internalAdb = Addressbook_Setup_Initialize::setDefaultInternalAddressbook();
447             $internalAdbId = $internalAdb->getId();
448         } else {
449             $internalAdbId = $appConfigDefaults[Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK];
450         }
451         return $internalAdbId;
452     }
453     
454     /**
455      * create or update contact in addressbook backend
456      * 
457      * @param Tinebase_Model_FullUser $_user
458      * @param boolean $_setModlog
459      * @return Addressbook_Model_Contact
460      */
461     public function createOrUpdateContact(Tinebase_Model_FullUser $_user, $_setModlog = true)
462     {
463         $contactsBackend = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
464         $contactsBackend->setGetDisabledContacts(true);
465         
466         if (empty($_user->container_id)) {
467             $_user->container_id = $this->getDefaultInternalAddressbook();
468         }
469         
470         try {
471             if (empty($_user->contact_id)) { // jump to catch block
472                 throw new Tinebase_Exception_NotFound('contact_id is empty');
473             }
474
475             /** @var Addressbook_Model_Contact $contact */
476             $contact = $contactsBackend->get($_user->contact_id);
477             
478             // update exisiting contact
479             $contact->n_family   = $_user->accountLastName;
480             $contact->n_given    = $_user->accountFirstName;
481             $contact->n_fn       = $_user->accountFullName;
482             $contact->n_fileas   = $_user->accountDisplayName;
483             $contact->email      = $_user->accountEmailAddress;
484             $contact->type       = Addressbook_Model_Contact::CONTACTTYPE_USER;
485             $contact->container_id = $_user->container_id;
486
487             unset($contact->jpegphoto);
488
489             if ($_setModlog) {
490                 Tinebase_Timemachine_ModificationLog::setRecordMetaData($contact, 'update');
491             }
492             
493             $contact = $contactsBackend->update($contact);
494             
495         } catch (Tinebase_Exception_NotFound $tenf) {
496             // add new contact
497             $contact = new Addressbook_Model_Contact(array(
498                 'n_family'      => $_user->accountLastName,
499                 'n_given'       => $_user->accountFirstName,
500                 'n_fn'          => $_user->accountFullName,
501                 'n_fileas'      => $_user->accountDisplayName,
502                 'email'         => $_user->accountEmailAddress,
503                 'type'          => Addressbook_Model_Contact::CONTACTTYPE_USER,
504                 'container_id'  => $_user->container_id
505             ));
506
507             if ($_setModlog) {
508                 Tinebase_Timemachine_ModificationLog::setRecordMetaData($contact, 'create');
509             }
510     
511             $contact = $contactsBackend->create($contact);
512         }
513         
514         return $contact;
515     }
516 }