7 * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
8 * @copyright Copyright (c) 2007-2013 Metaways Infosystems GmbH (http://www.metaways.de)
9 * @author Lars Kneschke <l.kneschke@metaways.de>
11 * @todo extend Tinebase_Application_Backend_Sql and replace some functions
15 * sql implementation of the SQL users interface
20 class Tinebase_User_Sql extends Tinebase_User_Abstract
27 protected $rowNameMapping = array(
29 'accountDisplayName' => 'display_name',
30 'accountFullName' => 'full_name',
31 'accountFirstName' => 'first_name',
32 'accountLastName' => 'last_name',
33 'accountLoginName' => 'login_name',
34 'accountLastLogin' => 'last_login',
35 'accountLastLoginfrom' => 'last_login_from',
36 'accountLastPasswordChange' => 'last_password_change',
37 'accountStatus' => 'status',
38 'accountExpires' => 'expires_at',
39 'accountPrimaryGroup' => 'primary_group_id',
40 'accountEmailAddress' => 'email',
41 'accountHomeDirectory' => 'home_dir',
42 'accountLoginShell' => 'login_shell',
43 'lastLoginFailure' => 'last_login_failure_at',
44 'loginFailures' => 'login_failures',
46 'visibility' => 'visibility',
47 'contactId' => 'contact_id'
51 * copy of Tinebase_Core::get('dbAdapter')
53 * @var Zend_Db_Adapter_Abstract
62 protected $_sqlPlugins = array();
65 * Table name without prefix
69 protected $_tableName = 'accounts';
72 * @var Tinebase_Backend_Sql_Command_Interface
74 protected $_dbCommand;
79 * @param array $options Options used in connecting, binding, etc.
81 public function __construct(array $_options = array())
83 parent::__construct($_options);
85 $this->_db = Tinebase_Core::getDb();
86 $this->_dbCommand = Tinebase_Backend_Sql_Command::factory($this->_db);
92 * @param array $plugins
94 public function registerPlugins($plugins)
96 parent::registerPlugins($plugins);
98 foreach ($this->_plugins as $plugin) {
99 if ($plugin instanceof Tinebase_User_Plugin_SqlInterface) {
100 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
101 . " Registering " . get_class($plugin) . ' SQL plugin.');
102 $this->_sqlPlugins[] = $plugin;
108 * unregisterAllPlugins
110 public function unregisterAllPlugins()
112 parent::unregisterAllPlugins();
113 $this->_sqlPlugins = array();
119 * @param string $_filter
120 * @param string $_sort
121 * @param string $_dir
124 * @param string $_accountClass the type of subclass for the Tinebase_Record_RecordSet to return
125 * @return Tinebase_Record_RecordSet with record class Tinebase_Model_User
127 public function getUsers($_filter = NULL, $_sort = NULL, $_dir = 'ASC', $_start = NULL, $_limit = NULL, $_accountClass = 'Tinebase_Model_User')
129 $select = $this->_getUserSelectObject()
130 ->limit($_limit, $_start);
132 if ($_sort !== NULL && isset($this->rowNameMapping[$_sort])) {
133 $select->order($this->_db->table_prefix . $this->_tableName . '.' . $this->rowNameMapping[$_sort] . ' ' . $_dir);
136 if (!empty($_filter)) {
137 $whereStatement = array();
138 $defaultValues = array(
139 $this->rowNameMapping['accountLastName'],
140 $this->rowNameMapping['accountFirstName'],
141 $this->rowNameMapping['accountLoginName']
143 // prepare for case insensitive search
144 $db = Tinebase_Core::getDb();
145 foreach ($defaultValues as $defaultValue) {
146 $whereStatement[] = Tinebase_Backend_Sql_Command::factory($db)->prepareForILike($this->_db->quoteIdentifier($defaultValue)) . ' LIKE ' . Tinebase_Backend_Sql_Command::factory($db)->prepareForILike('?');
149 $select->where('(' . implode(' OR ', $whereStatement) . ')', '%' . $_filter . '%');
152 // @todo still needed?? either we use contacts from addressboook or full users now
153 // return only active users, when searching for simple users
154 if ($_accountClass == 'Tinebase_Model_User') {
155 $select->where($this->_db->quoteInto($this->_db->quoteIdentifier('status') . ' = ?', 'enabled'));
158 $stmt = $select->query();
160 $rows = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
162 $result = new Tinebase_Record_RecordSet($_accountClass, $rows, TRUE);
168 * get user by property
170 * @param string $_property the key to filter
171 * @param string $_value the value to search for
172 * @param string $_accountClass type of model to return
174 * @return Tinebase_Model_User the user object
176 public function getUserByProperty($_property, $_value, $_accountClass = 'Tinebase_Model_User')
178 $user = $this->getUserByPropertyFromSqlBackend($_property, $_value, $_accountClass);
180 // append data from plugins
181 foreach ($this->_sqlPlugins as $plugin) {
183 $plugin->inspectGetUserByProperty($user);
184 } catch (Tinebase_Exception_NotFound $tenf) {
186 } catch (Exception $e) {
187 if (Tinebase_Core::isLogLevel(Zend_Log::CRIT)) Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ . ' User sql plugin failure: ' . $e);
191 if ($this instanceof Tinebase_User_Interface_SyncAble) {
193 $syncUser = $this->getUserByPropertyFromSyncBackend('accountId', $user, $_accountClass);
195 if (!empty($syncUser->emailUser)) {
196 $user->emailUser = $syncUser->emailUser;
198 if (!empty($syncUser->imapUser)) {
199 $user->imapUser = $syncUser->imapUser;
201 if (!empty($syncUser->smtpUser)) {
202 $user->smtpUser = $syncUser->smtpUser;
204 if (!empty($syncUser->sambaSAM)) {
205 $user->sambaSAM = $syncUser->sambaSAM;
207 } catch (Tinebase_Exception_NotFound $tenf) {
208 if (Tinebase_Core::isLogLevel(Zend_Log::CRIT)) Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ . ' user not found in sync backend: ' . $user->getId());
212 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($user->toArray(), true));
218 * get user by property
220 * @param string $_property the key to filter
221 * @param string $_value the value to search for
222 * @param string $_accountClass type of model to return
224 * @return Tinebase_Model_User the user object
225 * @throws Tinebase_Exception_NotFound
226 * @throws Tinebase_Exception_Record_Validation
228 public function getUserByPropertyFromSqlBackend($_property, $_value, $_accountClass = 'Tinebase_Model_User')
230 if(!(isset($this->rowNameMapping[$_property]) || array_key_exists($_property, $this->rowNameMapping))) {
231 throw new Tinebase_Exception_InvalidArgument("invalid property $_property requested");
236 $value = Tinebase_Model_User::convertUserIdToInt($_value);
243 $select = $this->_getUserSelectObject()
244 ->where($this->_db->quoteInto($this->_db->quoteIdentifier( SQL_TABLE_PREFIX . 'accounts.' . $this->rowNameMapping[$_property]) . ' = ?', $value));
246 $stmt = $select->query();
248 $row = $stmt->fetch(Zend_Db::FETCH_ASSOC);
249 if ($row === false) {
250 throw new Tinebase_Exception_NotFound('User with ' . $_property . ' = ' . $value . ' not found.');
254 $account = new $_accountClass(NULL, TRUE);
255 $account->setFromArray($row);
256 } catch (Tinebase_Exception_Record_Validation $e) {
257 $validation_errors = $account->getValidationErrors();
258 Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e->getMessage() . "\n" .
259 "Tinebase_Model_User::validation_errors: \n" .
260 print_r($validation_errors,true));
268 * get users by primary group
270 * @param string $groupId
271 * @return Tinebase_Record_RecordSet of Tinebase_Model_FullUser
273 public function getUsersByPrimaryGroup($groupId)
275 $select = $this->_getUserSelectObject()
276 ->where($this->_db->quoteInto($this->_db->quoteIdentifier(SQL_TABLE_PREFIX . 'accounts.primary_group_id') . ' = ?', $groupId));
277 $stmt = $select->query();
278 $data = (array) $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
279 $result = new Tinebase_Record_RecordSet('Tinebase_Model_FullUser', $data, true);
284 * get full user by id
286 * @param int $_accountId
287 * @return Tinebase_Model_FullUser full user
289 public function getFullUserById($_accountId)
291 return $this->getUserById($_accountId, 'Tinebase_Model_FullUser');
297 * @return Zend_Db_Select
299 protected function _getUserSelectObject()
302 * CASE WHEN `status` = 'enabled' THEN (CASE WHEN NOW() > `expires_at` THEN 'expired'
303 * WHEN (`login_failures` > 5 AND `last_login_failure_at` + INTERVAL 15 MINUTE > NOW())
304 * THEN 'blocked' ELSE 'enabled' END) ELSE 'disabled' END
307 $maxLoginFailures = Tinebase_Config::getInstance()->get(Tinebase_Config::MAX_LOGIN_FAILURES, 5);
308 if ($maxLoginFailures > 0) {
309 $loginFailuresCondition = 'WHEN ( ' . $this->_db->quoteIdentifier($this->rowNameMapping['loginFailures']) . " > {$maxLoginFailures} AND "
310 . $this->_dbCommand->setDate($this->_db->quoteIdentifier($this->rowNameMapping['lastLoginFailure'])) . " + INTERVAL '{$this->_blockTime}' MINUTE > "
311 . $this->_dbCommand->setDate('NOW()') .") THEN 'blocked'";
313 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
314 . ' User blocking disabled.');
315 $loginFailuresCondition = '';
318 $statusSQL = 'CASE WHEN ' . $this->_db->quoteIdentifier($this->rowNameMapping['accountStatus']) . ' = ' . $this->_db->quote('enabled') . ' THEN (';
319 $statusSQL .= 'CASE WHEN '.$this->_dbCommand->setDate('NOW()') .' > ' . $this->_db->quoteIdentifier($this->rowNameMapping['accountExpires'])
320 . ' THEN ' . $this->_db->quote('expired')
321 . $loginFailuresCondition
322 . ' ELSE ' . $this->_db->quote('enabled') . ' END) ELSE ' . $this->_db->quote('disabled') . ' END';
324 $select = $this->_db->select()
325 ->from(SQL_TABLE_PREFIX . 'accounts',
327 'accountId' => $this->rowNameMapping['accountId'],
328 'accountLoginName' => $this->rowNameMapping['accountLoginName'],
329 'accountLastLogin' => $this->rowNameMapping['accountLastLogin'],
330 'accountLastLoginfrom' => $this->rowNameMapping['accountLastLoginfrom'],
331 'accountLastPasswordChange' => $this->rowNameMapping['accountLastPasswordChange'],
332 'accountStatus' => $statusSQL,
333 'accountExpires' => $this->rowNameMapping['accountExpires'],
334 'accountPrimaryGroup' => $this->rowNameMapping['accountPrimaryGroup'],
335 'accountHomeDirectory' => $this->rowNameMapping['accountHomeDirectory'],
336 'accountLoginShell' => $this->rowNameMapping['accountLoginShell'],
337 'accountDisplayName' => $this->rowNameMapping['accountDisplayName'],
338 'accountFullName' => $this->rowNameMapping['accountFullName'],
339 'accountFirstName' => $this->rowNameMapping['accountFirstName'],
340 'accountLastName' => $this->rowNameMapping['accountLastName'],
341 'accountEmailAddress' => $this->rowNameMapping['accountEmailAddress'],
342 'lastLoginFailure' => $this->rowNameMapping['lastLoginFailure'],
343 'loginFailures' => $this->rowNameMapping['loginFailures'],
350 SQL_TABLE_PREFIX . 'addressbook',
351 $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . 'accounts.contact_id') . ' = '
352 . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . 'addressbook.id'),
354 'container_id' => 'container_id'
362 * set the password for given account
364 * @param string $_userId
365 * @param string $_password
366 * @param bool $_encrypt encrypt password
367 * @param bool $_mustChange
369 * @throws Tinebase_Exception_InvalidArgument
371 public function setPassword($_userId, $_password, $_encrypt = TRUE, $_mustChange = null)
373 $userId = $_userId instanceof Tinebase_Model_User ? $_userId->getId() : $_userId;
374 $user = $_userId instanceof Tinebase_Model_FullUser ? $_userId : $this->getFullUserById($userId);
375 $this->checkPasswordPolicy($_password, $user);
377 $accountsTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'accounts'));
379 $accountData['password'] = ($_encrypt) ? Hash_Password::generate('SSHA256', $_password) : $_password;
380 $accountData['last_password_change'] = Tinebase_DateTime::now()->get(Tinebase_Record_Abstract::ISO8601LONG);
383 $accountsTable->getAdapter()->quoteInto($accountsTable->getAdapter()->quoteIdentifier('id') . ' = ?', $userId)
386 $result = $accountsTable->update($accountData, $where);
389 throw new Tinebase_Exception_NotFound('Unable to update password! account not found in authentication backend.');
392 $this->_setPluginsPassword($userId, $_password, $_encrypt);
396 * set password in plugins
398 * @param string $userId
399 * @param string $password
400 * @param bool $encrypt encrypt password
401 * @throws Tinebase_Exception_Backend
403 protected function _setPluginsPassword($userId, $password, $encrypt = TRUE)
405 foreach ($this->_sqlPlugins as $plugin) {
407 $plugin->inspectSetPassword($userId, $password, $encrypt);
408 } catch (Exception $e) {
409 Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Could not change plugin password: ' . $e);
410 throw new Tinebase_Exception_Backend($e->getMessage());
416 * ensure password policy
418 * @param string $password
419 * @param Tinebase_Model_FullUser $user
420 * @throws Tinebase_Exception_PasswordPolicyViolation
422 public function checkPasswordPolicy($password, Tinebase_Model_FullUser $user)
424 if (! Tinebase_Config::getInstance()->get(Tinebase_Config::PASSWORD_POLICY_ACTIVE, FALSE)) {
425 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
426 . ' No password policy enabled');
430 $failedTests = array();
433 Tinebase_Config::PASSWORD_POLICY_ONLYASCII => '/[^\x00-\x7F]/',
434 Tinebase_Config::PASSWORD_POLICY_MIN_LENGTH => NULL,
435 Tinebase_Config::PASSWORD_POLICY_MIN_WORD_CHARS => '/[\W]*/',
436 Tinebase_Config::PASSWORD_POLICY_MIN_UPPERCASE_CHARS => '/[^A-Z]*/',
437 Tinebase_Config::PASSWORD_POLICY_MIN_SPECIAL_CHARS => '/[\w]*/',
438 Tinebase_Config::PASSWORD_POLICY_MIN_NUMBERS => '/[^0-9]*/',
439 Tinebase_Config::PASSWORD_POLICY_FORBID_USERNAME => $user->accountLoginName,
442 foreach ($policy as $key => $regex) {
443 $test = $this->_testPolicy($password, $key, $regex);
444 if ($test !== TRUE) {
445 $failedTests[$key] = $test;
449 if (! empty($failedTests)) {
450 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
451 . ' ' . print_r($failedTests, TRUE));
453 $policyException = new Tinebase_Exception_PasswordPolicyViolation('Password failed to match the following policy requirements: '
454 . implode('|', array_keys($failedTests)));
455 throw $policyException;
460 * test password policy
462 * @param string $password
463 * @param string $configKey
464 * @param string $regex
467 protected function _testPolicy($password, $configKey, $regex = NULL)
471 if ($configKey === Tinebase_Config::PASSWORD_POLICY_ONLYASCII && Tinebase_Config::getInstance()->get($configKey, 0) && $regex !== NULL) {
472 $nonAsciiFound = preg_match($regex, $password, $matches);
473 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
474 . ' ' . print_r($matches, TRUE));
476 $result = ($nonAsciiFound) ? array('expected' => 0, 'got' => count($matches)) : TRUE;
477 } else if ($configKey === Tinebase_Config::PASSWORD_POLICY_FORBID_USERNAME) {
478 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
479 . ' Testing if password is part of username "' . $regex . '"');
480 if (! empty($password)) {
481 $result = ! preg_match('/' . preg_quote($password) . '/i', $regex);
484 // check min length restriction
485 $minLength = Tinebase_Config::getInstance()->get($configKey, 0);
486 if ($minLength > 0) {
487 $reduced = ($regex) ? preg_replace($regex, '', $password) : $password;
488 $charCount = strlen(utf8_decode($reduced));
489 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
490 . ' Found ' . $charCount . '/' . $minLength . ' chars for ' . $configKey /*. ': ' . $reduced */);
492 if ($charCount < $minLength) {
493 $result = array('expected' => $minLength, 'got' => $charCount);
502 * set the status of the user
504 * @param mixed $_accountId
505 * @param string $_status
508 public function setStatus($_accountId, $_status)
510 if($this instanceof Tinebase_User_Interface_SyncAble) {
511 $this->setStatusInSyncBackend($_accountId, $_status);
514 $accountId = Tinebase_Model_User::convertUserIdToInt($_accountId);
518 $accountData[$this->rowNameMapping['loginFailures']] = 0;
519 $accountData[$this->rowNameMapping['accountExpires']] = null;
520 $accountData['status'] = $_status;
524 $accountData['status'] = $_status;
528 $accountData['expires_at'] = Tinebase_DateTime::getTimestamp();
532 throw new Tinebase_Exception_InvalidArgument('$_status can be only enabled, disabled or expired');
536 $accountsTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'accounts'));
539 $this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', $accountId)
542 $result = $accountsTable->update($accountData, $where);
548 * sets/unsets expiry date
550 * @param mixed $_accountId
551 * @param Tinebase_DateTime $_expiryDate set to NULL to disable expirydate
553 public function setExpiryDate($_accountId, $_expiryDate)
555 if($this instanceof Tinebase_User_Interface_SyncAble) {
556 $this->setExpiryDateInSyncBackend($_accountId, $_expiryDate);
559 $accountId = Tinebase_Model_User::convertUserIdToInt($_accountId);
561 if($_expiryDate instanceof DateTime) {
562 $accountData['expires_at'] = $_expiryDate->get(Tinebase_Record_Abstract::ISO8601LONG);
564 $accountData['expires_at'] = NULL;
567 $accountsTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'accounts'));
570 $this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', $accountId)
573 $result = $accountsTable->update($accountData, $where);
579 * set last login failure in accounts table
581 * @param string $_loginName
582 * @see Tinebase/User/Tinebase_User_Interface::setLastLoginFailure()
584 public function setLastLoginFailure($_loginName)
586 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Login of user ' . $_loginName . ' failed.');
589 $user = $this->getUserByLoginName($_loginName);
590 } catch (Tinebase_Exception_NotFound $tenf) {
591 // nothing todo => is no existing user
596 'last_login_failure_at' => Tinebase_DateTime::now()->get(Tinebase_Record_Abstract::ISO8601LONG),
597 'login_failures' => new Zend_Db_Expr($this->_db->quoteIdentifier('login_failures') . ' + 1')
601 $this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', $user->getId())
604 $this->_db->update(SQL_TABLE_PREFIX . 'accounts', $values, $where);
608 * update the lastlogin time of user
610 * @param int $_accountId
611 * @param string $_ipAddress
614 public function setLoginTime($_accountId, $_ipAddress)
616 $accountId = Tinebase_Model_User::convertUserIdToInt($_accountId);
618 $accountsTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'accounts'));
620 $accountData['last_login_from'] = $_ipAddress;
621 $accountData['last_login'] = Tinebase_DateTime::now()->get(Tinebase_Record_Abstract::ISO8601LONG);
622 $accountData['login_failures'] = 0;
625 $this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', $accountId)
628 $result = $accountsTable->update($accountData, $where);
634 * update contact data(first name, last name, ...) of user
636 * @param Addressbook_Model_Contact $contact
638 public function updateContact(Addressbook_Model_Contact $_contact)
640 if($this instanceof Tinebase_User_Interface_SyncAble) {
641 $this->updateContactInSyncBackend($_contact);
644 return $this->updateContactInSqlBackend($_contact);
648 * update contact data(first name, last name, ...) of user in local sql storage
650 * @param Addressbook_Model_Contact $contact
652 public function updateContactInSqlBackend(Addressbook_Model_Contact $_contact)
654 $contactId = $_contact->getId();
656 $accountsTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'accounts'));
658 $accountData = array(
659 $this->rowNameMapping['accountDisplayName'] => $_contact->n_fileas,
660 $this->rowNameMapping['accountFullName'] => $_contact->n_fn,
661 $this->rowNameMapping['accountFirstName'] => $_contact->n_given,
662 $this->rowNameMapping['accountLastName'] => $_contact->n_family,
663 $this->rowNameMapping['accountEmailAddress'] => $_contact->email
667 $accountsTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'accounts'));
670 $this->_db->quoteInto($this->_db->quoteIdentifier('contact_id') . ' = ?', $contactId)
672 $accountsTable->update($accountData, $where);
674 } catch (Exception $e) {
675 Tinebase_TransactionManager::getInstance()->rollBack();
683 * this function updates an user
685 * @param Tinebase_Model_FullUser $_user
686 * @return Tinebase_Model_FullUser
688 public function updateUser(Tinebase_Model_FullUser $_user)
690 if($this instanceof Tinebase_User_Interface_SyncAble) {
691 $this->updateUserInSyncBackend($_user);
694 $updatedUser = $this->updateUserInSqlBackend($_user);
695 $this->updatePluginUser($updatedUser, $_user);
701 * update data in plugins
703 * @param Tinebase_Model_FullUser $updatedUser
704 * @param Tinebase_Model_FullUser $newUserProperties
706 public function updatePluginUser($updatedUser, $newUserProperties)
708 foreach ($this->_sqlPlugins as $plugin) {
709 $plugin->inspectUpdateUser($updatedUser, $newUserProperties);
716 * this function updates an user
718 * @param Tinebase_Model_FullUser $_user
719 * @return Tinebase_Model_FullUser
722 public function updateUserInSqlBackend(Tinebase_Model_FullUser $_user)
724 if(! $_user->isValid()) {
725 throw new Tinebase_Exception_Record_Validation('Invalid user object. ' . print_r($_user->getValidationErrors(), TRUE));
728 $accountId = Tinebase_Model_User::convertUserIdToInt($_user);
730 $oldUser = $this->getFullUserById($accountId);
732 $accountsTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'accounts'));
734 if (empty($_user->contact_id)) {
735 $_user->visibility = 'hidden';
736 $_user->contact_id = null;
739 $accountData = array(
740 'login_name' => $_user->accountLoginName,
741 'expires_at' => ($_user->accountExpires instanceof DateTime ? $_user->accountExpires->get(Tinebase_Record_Abstract::ISO8601LONG) : NULL),
742 'primary_group_id' => $_user->accountPrimaryGroup,
743 'home_dir' => $_user->accountHomeDirectory,
744 'login_shell' => $_user->accountLoginShell,
745 'openid' => $_user->openid,
746 'visibility' => $_user->visibility,
747 'contact_id' => $_user->contact_id,
748 $this->rowNameMapping['accountDisplayName'] => $_user->accountDisplayName,
749 $this->rowNameMapping['accountFullName'] => $_user->accountFullName,
750 $this->rowNameMapping['accountFirstName'] => $_user->accountFirstName,
751 $this->rowNameMapping['accountLastName'] => $_user->accountLastName,
752 $this->rowNameMapping['accountEmailAddress'] => $_user->accountEmailAddress,
755 // ignore all other states (expired and blocked)
756 if ($_user->accountStatus == Tinebase_User::STATUS_ENABLED) {
757 $accountData[$this->rowNameMapping['accountStatus']] = $_user->accountStatus;
759 if ($oldUser->accountStatus === Tinebase_User::STATUS_BLOCKED) {
760 $accountData[$this->rowNameMapping['loginFailures']] = 0;
761 } elseif ($oldUser->accountStatus === Tinebase_User::STATUS_EXPIRED) {
762 $accountData[$this->rowNameMapping['accountExpires']] = null;
764 } elseif ($_user->accountStatus == Tinebase_User::STATUS_DISABLED) {
765 $accountData[$this->rowNameMapping['accountStatus']] = $_user->accountStatus;
768 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($accountData, true));
771 $accountsTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'accounts'));
774 $this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', $accountId)
776 $accountsTable->update($accountData, $where);
778 } catch (Exception $e) {
779 Tinebase_TransactionManager::getInstance()->rollBack();
783 return $this->getUserById($accountId, 'Tinebase_Model_FullUser');
789 * @param Tinebase_Model_FullUser $_user
790 * @return Tinebase_Model_FullUser
792 public function addUser(Tinebase_Model_FullUser $_user)
794 if ($this instanceof Tinebase_User_Interface_SyncAble) {
795 $userFromSyncBackend = $this->addUserToSyncBackend($_user);
796 if ($userFromSyncBackend !== NULL) {
797 // set accountId for sql backend sql backend
798 $_user->setId($userFromSyncBackend->getId());
802 $addedUser = $this->addUserInSqlBackend($_user);
803 $this->addPluginUser($addedUser, $_user);
809 * add data from/to plugins
811 * @param Tinebase_Model_FullUser $addedUser
812 * @param Tinebase_Model_FullUser $newUserProperties
814 public function addPluginUser($addedUser, $newUserProperties)
816 foreach ($this->_sqlPlugins as $plugin) {
817 $plugin->inspectAddUser($addedUser, $newUserProperties);
824 * @todo fix $contactData['container_id'] = 1;
826 * @param Tinebase_Model_FullUser $_user
827 * @return Tinebase_Model_FullUser
829 public function addUserInSqlBackend(Tinebase_Model_FullUser $_user)
831 $_user->isValid(TRUE);
833 $accountsTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'accounts'));
835 if(!isset($_user->accountId)) {
836 $userId = $_user->generateUID();
837 $_user->setId($userId);
840 if (empty($_user->contact_id)) {
841 $_user->visibility = 'hidden';
842 $_user->contact_id = null;
845 $accountData = array(
846 'id' => $_user->accountId,
847 'login_name' => $_user->accountLoginName,
848 'status' => $_user->accountStatus,
849 'expires_at' => ($_user->accountExpires instanceof DateTime ? $_user->accountExpires->get(Tinebase_Record_Abstract::ISO8601LONG) : NULL),
850 'primary_group_id' => $_user->accountPrimaryGroup,
851 'home_dir' => $_user->accountHomeDirectory,
852 'login_shell' => $_user->accountLoginShell,
853 'openid' => $_user->openid,
854 'visibility' => $_user->visibility,
855 'contact_id' => $_user->contact_id,
856 $this->rowNameMapping['accountDisplayName'] => $_user->accountDisplayName,
857 $this->rowNameMapping['accountFullName'] => $_user->accountFullName,
858 $this->rowNameMapping['accountFirstName'] => $_user->accountFirstName,
859 $this->rowNameMapping['accountLastName'] => $_user->accountLastName,
860 $this->rowNameMapping['accountEmailAddress'] => $_user->accountEmailAddress,
863 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Adding user to SQL backend: ' . $_user->accountLoginName);
865 $accountsTable->insert($accountData);
867 return $this->getUserById($_user->getId(), 'Tinebase_Model_FullUser');
873 * @param mixed $_userId
875 public function deleteUser($_userId)
877 $deletedUser = $this->deleteUserInSqlBackend($_userId);
879 if($this instanceof Tinebase_User_Interface_SyncAble) {
880 $this->deleteUserInSyncBackend($deletedUser);
883 // update data from plugins
884 foreach ($this->_sqlPlugins as $plugin) {
885 $plugin->inspectDeleteUser($deletedUser);
893 * @param mixed $_userId
894 * @return Tinebase_Model_FullUser the delete user
896 public function deleteUserInSqlBackend($_userId)
898 if ($_userId instanceof Tinebase_Model_FullUser) {
901 $user = $this->getFullUserById($_userId);
904 $accountsTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'accounts'));
905 $groupMembersTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'group_members'));
906 $roleMembersTable = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'role_accounts'));
909 $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($this->_db);
912 $this->_db->quoteInto($this->_db->quoteIdentifier('account_id') . ' = ?', $user->getId()),
914 $groupMembersTable->delete($where);
917 $this->_db->quoteInto($this->_db->quoteIdentifier('account_id') . ' = ?', $user->getId()),
918 $this->_db->quoteInto($this->_db->quoteIdentifier('account_type') . ' = ?', Tinebase_Acl_Rights::ACCOUNT_TYPE_USER),
920 $roleMembersTable->delete($where);
923 $this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', $user->getId()),
925 $accountsTable->delete($where);
928 $this->_db->quoteInto($this->_db->quoteIdentifier('login_name') . ' = ?', $user->accountLoginName),
931 Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
932 } catch (Exception $e) {
933 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' error while deleting account ' . $e->__toString());
934 Tinebase_TransactionManager::getInstance()->rollBack();
944 * @param array $_accountIds
946 public function deleteUsers(array $_accountIds)
948 foreach ( $_accountIds as $accountId ) {
949 $this->deleteUser($accountId);
954 * Delete all users returned by {@see getUsers()} using {@see deleteUsers()}
958 public function deleteAllUsers()
960 // need to fetch FullUser because otherwise we would get only enabled accounts :/
961 $users = $this->getUsers(NULL, NULL, 'ASC', NULL, NULL, 'Tinebase_Model_FullUser');
963 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Deleting ' . count($users) .' users');
964 foreach ( $users as $user ) {
965 $this->deleteUser($user);
972 * fetch FullUser by default
974 * @param string|array $_id Ids
975 * @param string $_accountClass type of model to return
976 * @return Tinebase_Record_RecordSet of 'Tinebase_Model_User' or 'Tinebase_Model_FullUser'
978 public function getMultiple($_id, $_accountClass = 'Tinebase_Model_FullUser')
981 return new Tinebase_Record_RecordSet($_accountClass);
984 $select = $this->_getUserSelectObject()
985 ->where($this->_db->quoteIdentifier(SQL_TABLE_PREFIX . 'accounts.id') . ' in (?)', (array) $_id);
987 $stmt = $this->_db->query($select);
988 $queryResult = $stmt->fetchAll();
990 $result = new Tinebase_Record_RecordSet($_accountClass, $queryResult, TRUE);