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