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)
11 * @todo catch error (and show alert) if postfix email already exists
12 * @todo extend Tinebase_Controller_Record_Abstract
16 * User Controller for Admin application
19 * @subpackage Controller
21 class Admin_Controller_User extends Tinebase_Controller_Abstract
24 * @var Tinebase_User_Abstract
26 protected $_userBackend = NULL;
29 * @var Tinebase_SambaSAM_Ldap
31 protected $_samBackend = NULL;
36 * don't use the constructor. use the singleton
38 private function __construct()
40 $this->_applicationName = 'Admin';
42 $this->_userBackend = Tinebase_User::getInstance();
46 * don't clone. Use the singleton.
49 private function __clone()
54 * holds the instance of the singleton
56 * @var Admin_Controller_User
58 private static $_instance = NULL;
61 * the singleton pattern
63 * @return Admin_Controller_User
65 public static function getInstance()
67 if (self::$_instance === NULL) {
68 self::$_instance = new Admin_Controller_User;
71 return self::$_instance;
75 * get list of full accounts -> renamed to search full users
77 * @param string $_filter string to search accounts for
78 * @param string $_sort
82 * @return Tinebase_Record_RecordSet with record class Tinebase_Model_FullUser
84 public function searchFullUsers($_filter, $_sort = NULL, $_dir = 'ASC', $_start = NULL, $_limit = NULL)
86 $this->checkRight('VIEW_ACCOUNTS');
88 $result = $this->_userBackend->getUsers($_filter, $_sort, $_dir, $_start, $_limit, 'Tinebase_Model_FullUser');
96 * @param string $_filter string to search user accounts for
97 * @return int total user count
99 public function searchCount($_filter)
101 $this->checkRight('VIEW_ACCOUNTS');
103 $result = $this->_userBackend->getUsersCount($_filter);
111 * @param string $_accountId account id to get
112 * @return Tinebase_Model_FullUser
114 public function get($_userId)
116 $this->checkRight('VIEW_ACCOUNTS');
118 $user = $this->_userBackend->getUserById($_userId, 'Tinebase_Model_FullUser');
126 * @param string $_accountId account id
127 * @param string $_status status to set
130 public function setAccountStatus($_accountId, $_status)
132 $this->checkRight('MANAGE_ACCOUNTS');
134 $result = $this->_userBackend->setStatus($_accountId, $_status);
136 if ($_status === Tinebase_Model_FullUser::ACCOUNT_STATUS_DISABLED) {
137 // TODO send this for blocked/expired, too? allow to configure this?
138 Tinebase_User::getInstance()->sendDeactivationNotification($_accountId);
145 * set the password for a given account
147 * @param Tinebase_Model_FullUser $_account the account
148 * @param string $_password the new password
149 * @param string $_passwordRepeat the new password again
150 * @param bool $_mustChange
153 * @todo add must change pwd info to normal tine user accounts
155 public function setAccountPassword(Tinebase_Model_FullUser $_account, $_password, $_passwordRepeat, $_mustChange = null)
157 $this->checkRight('MANAGE_ACCOUNTS');
159 if ($_password != $_passwordRepeat) {
160 throw new Admin_Exception("Passwords don't match.");
163 $this->_userBackend->setPassword($_account, $_password, true, $_mustChange);
165 Tinebase_Core::getLogger()->info(
166 __METHOD__ . '::' . __LINE__ .
167 ' Set new password for user ' . $_account->accountLoginName . '. Must change:' . $_mustChange
174 * @param Tinebase_Model_FullUser $_user the user
175 * @param string $_password the new password
176 * @param string $_passwordRepeat the new password again
178 * @return Tinebase_Model_FullUser
180 public function update(Tinebase_Model_FullUser $_user, $_password, $_passwordRepeat)
182 $this->checkRight('MANAGE_ACCOUNTS');
184 $oldUser = $this->_userBackend->getUserByProperty('accountId', $_user, 'Tinebase_Model_FullUser');
186 if ($oldUser->accountLoginName !== $_user->accountLoginName) {
187 $this->_checkLoginNameExistance($_user);
189 $this->_checkLoginNameLength($_user);
190 $this->_checkPrimaryGroupExistance($_user);
192 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Update user ' . $_user->accountLoginName);
195 $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
197 if (Tinebase_Application::getInstance()->isInstalled('Addressbook') === true) {
198 $_user->contact_id = $oldUser->contact_id;
199 $contact = $this->createOrUpdateContact($_user);
200 $_user->contact_id = $contact->getId();
203 Tinebase_Timemachine_ModificationLog::setRecordMetaData($_user, 'update', $oldUser);
205 $user = $this->_userBackend->updateUser($_user);
207 // make sure primary groups is in the list of groupmemberships
208 $groups = array_unique(array_merge(array($user->accountPrimaryGroup), (array) $_user->groups));
209 Admin_Controller_Group::getInstance()->setGroupMemberships($user, $groups);
211 Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
213 } catch (Exception $e) {
214 Tinebase_TransactionManager::getInstance()->rollBack();
215 Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e);
217 if ($e instanceof Zend_Db_Statement_Exception && preg_match('/Lock wait timeout exceeded/', $e->getMessage())) {
218 throw new Tinebase_Exception_Backend_Database_LockTimeout($e->getMessage());
224 // fire needed events
225 $event = new Admin_Event_UpdateAccount;
226 $event->account = $user;
227 $event->oldAccount = $oldUser;
228 Tinebase_Event::fireEvent($event);
230 if (!empty($_password) && !empty($_passwordRepeat)) {
231 $this->setAccountPassword($_user, $_password, $_passwordRepeat, FALSE);
240 * @param Tinebase_Model_FullUser $_account the account
241 * @param string $_password the new password
242 * @param string $_passwordRepeat the new password again
243 * @return Tinebase_Model_FullUser
245 public function create(Tinebase_Model_FullUser $_user, $_password, $_passwordRepeat)
247 $this->checkRight('MANAGE_ACCOUNTS');
249 // avoid forging accountId, gets created in backend
250 unset($_user->accountId);
252 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Create new user ' . $_user->accountLoginName);
253 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_user->toArray(), TRUE));
255 $this->_checkLoginNameExistance($_user);
256 $this->_checkLoginNameLength($_user);
257 $this->_checkPrimaryGroupExistance($_user);
259 if ($_password != $_passwordRepeat) {
260 throw new Admin_Exception("Passwords don't match.");
261 } else if (empty($_password)) {
263 $_passwordRepeat = '';
265 Tinebase_User::getInstance()->checkPasswordPolicy($_password, $_user);
268 $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
270 if (Tinebase_Application::getInstance()->isInstalled('Addressbook') === true) {
271 $contact = $this->createOrUpdateContact($_user);
272 $_user->contact_id = $contact->getId();
275 Tinebase_Timemachine_ModificationLog::setRecordMetaData($_user, 'create');
277 $user = $this->_userBackend->addUser($_user);
279 // make sure primary groups is in the list of groupmemberships
280 $groups = array_unique(array_merge(array($user->accountPrimaryGroup), (array) $_user->groups));
281 Admin_Controller_Group::getInstance()->setGroupMemberships($user, $groups);
283 Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
285 } catch (Exception $e) {
286 Tinebase_TransactionManager::getInstance()->rollBack();
287 Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e->getMessage());
288 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . $e->getTraceAsString());
292 $event = new Admin_Event_AddAccount(array(
295 Tinebase_Event::fireEvent($event);
297 $this->setAccountPassword($user, $_password, $_passwordRepeat);
303 * look for user with the same login name
305 * @param Tinebase_Model_FullUser $user
307 * @throws Tinebase_Exception_SystemGeneric
309 protected function _checkLoginNameExistance(Tinebase_Model_FullUser $user)
312 $existing = Tinebase_User::getInstance()->getUserByLoginName($user->accountLoginName);
313 if ($user->getId() === NULL || $existing->getId() !== $user->getId()) {
314 throw new Tinebase_Exception_SystemGeneric('Login name already exists. Please choose another one.');
316 } catch (Tinebase_Exception_NotFound $tenf) {
323 * Check login name length
325 * @param Tinebase_Model_FullUser $user
327 * @throws Tinebase_Exception_SystemGeneric
329 protected function _checkLoginNameLength(Tinebase_Model_FullUser $user)
331 $maxLoginNameLength = Tinebase_Config::getInstance()->get(Tinebase_Config::MAX_USERNAME_LENGTH);
332 if (!empty($maxLoginNameLength) && strlen($user->accountLoginName) > $maxLoginNameLength) {
333 throw new Tinebase_Exception_SystemGeneric('The login name exceeds the maximum length of ' . $maxLoginNameLength . ' characters. Please choose another one.');
339 * look for primary group, if it does not exist, fallback to default user group
341 * @param Tinebase_Model_FullUser $user
342 * @throws Tinebase_Exception_SystemGeneric
344 protected function _checkPrimaryGroupExistance(Tinebase_Model_FullUser $user)
347 $group = Tinebase_Group::getInstance()->getGroupById($user->accountPrimaryGroup);
348 } catch (Tinebase_Exception_Record_NotDefined $ternd) {
349 $defaultUserGroup = Tinebase_Group::getInstance()->getDefaultGroup();
350 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
351 . ' Group with id ' . $user->accountPrimaryGroup . ' not found. Use default group (' . $defaultUserGroup->name
352 . ') as primary group for ' . $user->accountLoginName);
354 $user->accountPrimaryGroup = $defaultUserGroup->getId();
361 * @param mixed $_accountIds array of account ids
362 * @return array with success flag
363 * @throws Tinebase_Exception_Record_NotAllowed
365 public function delete($_accountIds)
367 $this->checkRight('MANAGE_ACCOUNTS');
369 $groupsController = Admin_Controller_Group::getInstance();
371 foreach ((array)$_accountIds as $accountId) {
372 if ($accountId === Tinebase_Core::getUser()->getId()) {
373 $message = 'You are not allowed to delete yourself!';
374 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' ' . $message);
375 throw new Tinebase_Exception_AccessDenied($message);
378 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " about to remove user with id: {$accountId}");
380 $oldUser = $this->get($accountId);
382 $memberships = $groupsController->getGroupMemberships($accountId);
383 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " removing user from groups: " . print_r($memberships, true));
385 foreach ((array)$memberships as $groupId) {
386 $groupsController->removeGroupMember($groupId, $accountId);
389 $this->_userBackend->deleteUser($accountId);
394 * returns all shared addressbooks
396 * @return Tinebase_Record_RecordSet of shared addressbooks
398 * @todo do we need to fetch ALL shared containers here (even if admin user has NO grants for them)?
400 public function searchSharedAddressbooks()
402 $this->checkRight('MANAGE_ACCOUNTS');
404 return Tinebase_Container::getInstance()->getSharedContainer(Tinebase_Core::getUser(), 'Addressbook', Tinebase_Model_Grants::GRANT_READ, TRUE);
408 * returns default internal addressbook container
410 * @return string|int ID
412 public function getDefaultInternalAddressbook()
414 $appConfigDefaults = Admin_Controller::getInstance()->getConfigSettings();
415 if (empty($appConfigDefaults[Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK])) {
416 $internalAdb = Addressbook_Setup_Initialize::setDefaultInternalAddressbook();
417 $internalAdbId = $internalAdb->getId();
419 $internalAdbId = $appConfigDefaults[Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK];
421 return $internalAdbId;
425 * create or update contact in addressbook backend
427 * @param Tinebase_Model_FullUser $_user
428 * @return Addressbook_Model_Contact
430 public function createOrUpdateContact(Tinebase_Model_FullUser $_user)
432 $contactsBackend = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
433 $contactsBackend->setGetDisabledContacts(true);
435 if (empty($_user->container_id)) {
436 $_user->container_id = $this->getDefaultInternalAddressbook();
440 if (empty($_user->contact_id)) { // jump to catch block
441 throw new Tinebase_Exception_NotFound('contact_id is empty');
444 $contact = $contactsBackend->get($_user->contact_id);
446 // update exisiting contact
447 $contact->n_family = $_user->accountLastName;
448 $contact->n_given = $_user->accountFirstName;
449 $contact->n_fn = $_user->accountFullName;
450 $contact->n_fileas = $_user->accountDisplayName;
451 $contact->email = $_user->accountEmailAddress;
452 $contact->type = Addressbook_Model_Contact::CONTACTTYPE_USER;
453 $contact->container_id = $_user->container_id;
456 Tinebase_Timemachine_ModificationLog::setRecordMetaData($contact, 'update');
458 $contact = $contactsBackend->update($contact);
460 } catch (Tinebase_Exception_NotFound $tenf) {
462 $contact = new Addressbook_Model_Contact(array(
463 'n_family' => $_user->accountLastName,
464 'n_given' => $_user->accountFirstName,
465 'n_fn' => $_user->accountFullName,
466 'n_fileas' => $_user->accountDisplayName,
467 'email' => $_user->accountEmailAddress,
468 'type' => Addressbook_Model_Contact::CONTACTTYPE_USER,
469 'container_id' => $_user->container_id
473 Tinebase_Timemachine_ModificationLog::setRecordMetaData($contact, 'create');
475 $contact = $contactsBackend->create($contact);