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_Exception::log($e);
291 $event = new Admin_Event_AddAccount(array(
294 Tinebase_Event::fireEvent($event);
296 $this->setAccountPassword($user, $_password, $_passwordRepeat);
302 * look for user with the same login name
304 * @param Tinebase_Model_FullUser $user
306 * @throws Tinebase_Exception_SystemGeneric
308 protected function _checkLoginNameExistance(Tinebase_Model_FullUser $user)
311 $existing = Tinebase_User::getInstance()->getUserByLoginName($user->accountLoginName);
312 if ($user->getId() === NULL || $existing->getId() !== $user->getId()) {
313 throw new Tinebase_Exception_SystemGeneric('Login name already exists. Please choose another one.');
315 } catch (Tinebase_Exception_NotFound $tenf) {
322 * Check login name length
324 * @param Tinebase_Model_FullUser $user
326 * @throws Tinebase_Exception_SystemGeneric
328 protected function _checkLoginNameLength(Tinebase_Model_FullUser $user)
330 $maxLoginNameLength = Tinebase_Config::getInstance()->get(Tinebase_Config::MAX_USERNAME_LENGTH);
331 if (!empty($maxLoginNameLength) && strlen($user->accountLoginName) > $maxLoginNameLength) {
332 throw new Tinebase_Exception_SystemGeneric('The login name exceeds the maximum length of ' . $maxLoginNameLength . ' characters. Please choose another one.');
338 * look for primary group, if it does not exist, fallback to default user group
340 * @param Tinebase_Model_FullUser $user
341 * @throws Tinebase_Exception_SystemGeneric
343 protected function _checkPrimaryGroupExistance(Tinebase_Model_FullUser $user)
346 $group = Tinebase_Group::getInstance()->getGroupById($user->accountPrimaryGroup);
347 } catch (Tinebase_Exception_Record_NotDefined $ternd) {
348 $defaultUserGroup = Tinebase_Group::getInstance()->getDefaultGroup();
349 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
350 . ' Group with id ' . $user->accountPrimaryGroup . ' not found. Use default group (' . $defaultUserGroup->name
351 . ') as primary group for ' . $user->accountLoginName);
353 $user->accountPrimaryGroup = $defaultUserGroup->getId();
360 * @param mixed $_accountIds array of account ids
361 * @return array with success flag
362 * @throws Tinebase_Exception_Record_NotAllowed
364 public function delete($_accountIds)
366 $this->checkRight('MANAGE_ACCOUNTS');
368 $groupsController = Admin_Controller_Group::getInstance();
370 foreach ((array)$_accountIds as $accountId) {
371 if ($accountId === Tinebase_Core::getUser()->getId()) {
372 $message = 'You are not allowed to delete yourself!';
373 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' ' . $message);
374 throw new Tinebase_Exception_AccessDenied($message);
377 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " about to remove user with id: {$accountId}");
379 $oldUser = $this->get($accountId);
381 $memberships = $groupsController->getGroupMemberships($accountId);
382 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " removing user from groups: " . print_r($memberships, true));
384 foreach ((array)$memberships as $groupId) {
385 $groupsController->removeGroupMember($groupId, $accountId);
388 $this->_userBackend->deleteUser($accountId);
393 * returns all shared addressbooks
395 * @return Tinebase_Record_RecordSet of shared addressbooks
397 * @todo do we need to fetch ALL shared containers here (even if admin user has NO grants for them)?
399 public function searchSharedAddressbooks()
401 $this->checkRight('MANAGE_ACCOUNTS');
403 return Tinebase_Container::getInstance()->getSharedContainer(Tinebase_Core::getUser(), 'Addressbook', Tinebase_Model_Grants::GRANT_READ, TRUE);
407 * returns default internal addressbook container
409 * @return string|int ID
411 public function getDefaultInternalAddressbook()
413 $appConfigDefaults = Admin_Controller::getInstance()->getConfigSettings();
414 if (empty($appConfigDefaults[Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK])) {
415 $internalAdb = Addressbook_Setup_Initialize::setDefaultInternalAddressbook();
416 $internalAdbId = $internalAdb->getId();
418 $internalAdbId = $appConfigDefaults[Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK];
420 return $internalAdbId;
424 * create or update contact in addressbook backend
426 * @param Tinebase_Model_FullUser $_user
427 * @return Addressbook_Model_Contact
429 public function createOrUpdateContact(Tinebase_Model_FullUser $_user)
431 $contactsBackend = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
432 $contactsBackend->setGetDisabledContacts(true);
434 if (empty($_user->container_id)) {
435 $_user->container_id = $this->getDefaultInternalAddressbook();
439 if (empty($_user->contact_id)) { // jump to catch block
440 throw new Tinebase_Exception_NotFound('contact_id is empty');
443 $contact = $contactsBackend->get($_user->contact_id);
445 // update exisiting contact
446 $contact->n_family = $_user->accountLastName;
447 $contact->n_given = $_user->accountFirstName;
448 $contact->n_fn = $_user->accountFullName;
449 $contact->n_fileas = $_user->accountDisplayName;
450 $contact->email = $_user->accountEmailAddress;
451 $contact->type = Addressbook_Model_Contact::CONTACTTYPE_USER;
452 $contact->container_id = $_user->container_id;
455 Tinebase_Timemachine_ModificationLog::setRecordMetaData($contact, 'update');
457 $contact = $contactsBackend->update($contact);
459 } catch (Tinebase_Exception_NotFound $tenf) {
461 $contact = new Addressbook_Model_Contact(array(
462 'n_family' => $_user->accountLastName,
463 'n_given' => $_user->accountFirstName,
464 'n_fn' => $_user->accountFullName,
465 'n_fileas' => $_user->accountDisplayName,
466 'email' => $_user->accountEmailAddress,
467 'type' => Addressbook_Model_Contact::CONTACTTYPE_USER,
468 'container_id' => $_user->container_id
472 Tinebase_Timemachine_ModificationLog::setRecordMetaData($contact, 'create');
474 $contact = $contactsBackend->create($contact);