Index: branches/5.2.x/core/units/users/users_event_handler.php =================================================================== --- branches/5.2.x/core/units/users/users_event_handler.php (revision 16773) +++ branches/5.2.x/core/units/users/users_event_handler.php (revision 16774) @@ -1,1946 +1,1952 @@ Array('self' => 'view'), // because setting to logged in user only 'OnUpdatePassword' => Array('self' => true), 'OnSaveSelected' => Array ('self' => 'view'), 'OnGeneratePassword' => Array ('self' => 'view'), // front 'OnRefreshForm' => Array('self' => true), 'OnForgotPassword' => Array('self' => true), 'OnSubscribeQuery' => Array('self' => true), 'OnSubscribeUser' => Array('self' => true), 'OnRecommend' => Array('self' => true), 'OnItemBuild' => Array('self' => true), 'OnMassResetSettings' => Array('self' => 'edit'), 'OnMassCloneUsers' => Array('self' => 'add'), ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Builds item (loads if needed) * * Pattern: Prototype Manager * * @param kEvent $event * @access protected */ protected function OnItemBuild(kEvent $event) { parent::OnItemBuild($event); /** @var kDBItem $object */ $object = $event->getObject(); if ( $event->Special == 'forgot' || $object->getFormName() == 'registration' ) { $this->_makePasswordRequired($event); } } /** * Shows only admins when required * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetCustomQuery(kEvent $event) { parent::SetCustomQuery($event); /** @var kDBList $object */ $object = $event->getObject(); if ( $event->Special == 'regular' ) { $object->addFilter('primary_filter', '%1$s.UserType = ' . UserType::USER); } if ( $event->Special == 'admins' ) { $object->addFilter('primary_filter', '%1$s.UserType = ' . UserType::ADMIN); } if ( !$this->Application->isAdminUser ) { $object->addFilter('status_filter', '%1$s.Status = ' . STATUS_ACTIVE); } if ( $event->Special == 'online' ) { $object->addFilter('online_users_filter', 's.PortalUserId IS NOT NULL'); } if ( $event->Special == 'group' ) { $group_id = $this->Application->GetVar('g_id'); if ( $group_id !== false ) { // show only users, that user doesn't belong to current group $sql = 'SELECT PortalUserId FROM ' . $this->Application->GetTempName(TABLE_PREFIX . 'UserGroupRelations', 'prefix:g') . ' WHERE GroupId = ' . (int)$group_id; $user_ids = $this->Conn->GetCol($sql); if ( $user_ids ) { $object->addFilter('already_member_filter', '%1$s.PortalUserId NOT IN (' . implode(',', $user_ids) . ')'); } } } } /** * Checks user permission to execute given $event * * @param kEvent $event * @return bool * @access public */ public function CheckPermission(kEvent $event) { if ( $event->Name == 'OnLogin' || $event->Name == 'OnLoginAjax' || $event->Name == 'OnLogout' ) { // permission is checked in OnLogin event directly return true; } if ( $event->Name == 'OnResetRootPassword' ) { return defined('DBG_RESET_ROOT') && DBG_RESET_ROOT; } if ( $event->Name == 'OnLoginAs' ) { /** @var Session $admin_session */ $admin_session = $this->Application->recallObject('Session.admin'); return $admin_session->LoggedIn(); } if ( !$this->Application->isAdminUser ) { $user_id = $this->Application->RecallVar('user_id'); $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ( ($event->Name == 'OnCreate' || $event->Name == 'OnRegisterAjax') && $user_id == USER_GUEST ) { // "Guest" can create new users return true; } if ( substr($event->Name, 0, 8) == 'OnUpdate' && $user_id > 0 ) { /** @var UsersItem $user_dummy */ $user_dummy = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true)); foreach ($items_info as $id => $field_values) { if ( $id != $user_id ) { // registered users can update their record only return false; } $user_dummy->Load($id); $status_field = $user_dummy->getStatusField(); if ( $user_dummy->GetDBField($status_field) != STATUS_ACTIVE ) { // not active user is not allowed to update his record (he could not activate himself manually) return false; } if ( isset($field_values[$status_field]) && $user_dummy->GetDBField($status_field) != $field_values[$status_field] ) { // user can't change status by himself return false; } } return true; } if ( $event->Name == 'OnResetLostPassword' && $event->Special == 'forgot' && $user_id == USER_GUEST ) { // non-logged in users can reset their password, when reset code is valid return is_numeric($this->getPassedID($event)); } if ( substr($event->Name, 0, 8) == 'OnUpdate' && $user_id <= 0 ) { // guests are not allowed to update their record, because they don't have it :) return false; } } return parent::CheckPermission($event); } /** * Handles session expiration (redirects to valid template) * * @param kEvent $event */ function OnSessionExpire($event) { $this->Application->resetCounters('UserSessions'); // place 2 of 2 (also in kHTTPQuery::getRedirectParams) $admin_url_params = Array ( 'm_cat_id' => 0, // category means nothing on admin login screen 'm_wid' => '', // remove wid, otherwise parent window may add wid to its name breaking all the frameset (for targets) 'pass' => 'm', // don't pass any other (except "m") prefixes to admin session expiration template 'expired' => 1, // expiration mark to show special error on login screen 'no_pass_through' => 1, // this way kApplication::HREF won't add them again ); if ($this->Application->isAdmin) { $this->Application->Redirect('index', $admin_url_params, '', 'index.php'); } if ($this->Application->GetVar('admin') == 1) { // Front-End showed in admin's right frame /** @var Session $session_admin */ $session_admin = $this->Application->recallObject('Session.admin'); if (!$session_admin->LoggedIn()) { // front-end session created from admin session & both expired $this->Application->DeleteVar('admin'); $this->Application->Redirect('index', $admin_url_params, '', 'admin/index.php'); } } // Front-End session expiration $get = $this->Application->HttpQuery->getRedirectParams(); $t = $this->Application->GetVar('t'); $get['js_redirect'] = $this->Application->ConfigValue('UseJSRedirect'); $this->Application->Redirect($t ? $t : 'index', $get); } /** * [SCHEDULED TASK] Deletes expired sessions * * @param kEvent $event */ function OnDeleteExpiredSessions($event) { if (defined('IS_INSTALL') && IS_INSTALL) { return ; } /** @var SessionStorage $session_storage */ $session_storage = $this->Application->recallObject('SessionStorage'); $session_storage->DeleteExpired(); } /** * Checks user data and logs it in if allowed * * @param kEvent $event * @return void * @access protected */ protected function OnLogin($event) { /** @var kDBItem $object */ $object = $event->getObject( Array ('form_name' => 'login') ); $object->SetFieldsFromHash($this->getSubmittedFields($event)); $username = $object->GetDBField('UserLogin'); $password = $object->GetDBField('UserPassword'); $remember_login = $object->GetDBField('UserRememberLogin') == 1; /** @var UserHelper $user_helper */ $user_helper = $this->Application->recallObject('UserHelper'); $user_helper->event =& $event; $result = $user_helper->loginUser($username, $password, false, $remember_login); if ($result != LoginResult::OK) { $event->status = kEvent::erFAIL; $object->SetError('UserLogin', $result == LoginResult::NO_PERMISSION ? 'no_permission' : 'invalid_password'); } if ( is_object($event->MasterEvent) && ($event->MasterEvent->Name == 'OnLoginAjax') ) { // used to insert just logged-in user e-mail on "One Step Checkout" form in "Modern Store" theme $user =& $user_helper->getUserObject(); $event->SetRedirectParam('user_email', $user->GetDBField('Email')); } } /** * Performs user login from ajax request * * @param kEvent $event * @return void * @access protected */ protected function OnLoginAjax($event) { /** @var AjaxFormHelper $ajax_form_helper */ $ajax_form_helper = $this->Application->recallObject('AjaxFormHelper'); $ajax_form_helper->transitEvent($event, 'OnLogin'); } /** * [HOOK] Auto-Logins Front-End user when "Remember Login" cookie is found * * @param kEvent $event */ function OnAutoLoginUser($event) { $remember_login_cookie = $this->Application->GetVar('remember_login'); if (!$remember_login_cookie || $this->Application->isAdmin || $this->Application->LoggedIn()) { return ; } /** @var UserHelper $user_helper */ $user_helper = $this->Application->recallObject('UserHelper'); $user_helper->loginUser('', '', false, false, $remember_login_cookie); } /** * Called when user logs in using old in-portal * * @param kEvent $event */ function OnInpLogin($event) { /** @var UsersSyncronizeManager $sync_manager */ $sync_manager = $this->Application->recallObject('UsersSyncronizeManager', null, Array(), Array ('InPortalSyncronize')); $sync_manager->performAction('LoginUser', $event->getEventParam('user'), $event->getEventParam('pass') ); if ($event->redirect && is_string($event->redirect)) { // some real template specified instead of true $this->Application->Redirect($event->redirect, $event->getRedirectParams()); } } /** * Called when user logs in using old in-portal * * @param kEvent $event */ function OnInpLogout($event) { /** @var UsersSyncronizeManager $sync_manager */ $sync_manager = $this->Application->recallObject('UsersSyncronizeManager', null, Array(), Array ('InPortalSyncronize')); $sync_manager->performAction('LogoutUser'); } /** * Performs user logout * * @param kEvent $event * @return void * @access protected */ protected function OnLogout($event) { /** @var UserHelper $user_helper */ $user_helper = $this->Application->recallObject('UserHelper'); $user_helper->event =& $event; $user_helper->logoutUser(); } /** * Redirects user after successful registration to confirmation template (on Front only) * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemCreate(kEvent $event) { parent::OnAfterItemCreate($event); $this->afterItemChanged($event); $this->assignToPrimaryGroup($event); } /** * Performs user registration * * @param kEvent $event * @return void * @access protected */ protected function OnCreate(kEvent $event) { if ( $this->Application->isAdmin ) { parent::OnCreate($event); return ; } /** @var UsersItem $object */ $object = $event->getObject( Array('form_name' => 'registration') ); $field_values = $this->getSubmittedFields($event); $user_email = getArrayValue($field_values, 'Email'); $subscriber_id = $user_email ? $this->getSubscriberByEmail($user_email) : false; if ( $subscriber_id ) { // update existing subscriber $object->Load($subscriber_id); $object->SetDBField('PrimaryGroupId', $this->Application->ConfigValue('User_NewGroup')); $this->Application->SetVar($event->getPrefixSpecial(true), Array ($object->GetID() => $field_values)); } $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); $status = $object->isLoaded() ? $object->Update() : $object->Create(); if ( !$status ) { $event->status = kEvent::erFAIL; $event->redirect = false; $object->setID( (int)$object->GetID() ); } $this->setNextTemplate($event, true); if ( ($event->status == kEvent::erSUCCESS) && $event->redirect ) { $this->assignToPrimaryGroup($event); $object->sendEmails(); $this->autoLoginUser($event); } } /** * Processes user registration from ajax request * * @param kEvent $event * @return void * @access protected */ protected function OnRegisterAjax(kEvent $event) { /** @var AjaxFormHelper $ajax_form_helper */ $ajax_form_helper = $this->Application->recallObject('AjaxFormHelper'); $ajax_form_helper->transitEvent($event, 'OnCreate', Array ('do_refresh' => 1)); } /** * Returns subscribed user ID by given e-mail address * * @param string $email * @return int|bool * @access protected */ protected function getSubscriberByEmail($email) { /** @var UsersItem $verify_user */ $verify_user = $this->Application->recallObject('u.verify', null, Array ('skip_autoload' => true)); $verify_user->Load($email, 'Email'); return $verify_user->isLoaded() && $verify_user->isSubscriberOnly() ? $verify_user->GetID() : false; } /** * Login user if possible, if not then redirect to corresponding template * * @param kEvent $event */ function autoLoginUser($event) { /** @var UsersItem $object */ $object = $event->getObject(); if ( $object->GetDBField('Status') == STATUS_ACTIVE ) { /** @var UserHelper $user_helper */ $user_helper = $this->Application->recallObject('UserHelper'); $user =& $user_helper->getUserObject(); $user->Load($object->GetID()); if ( $user_helper->checkLoginPermission() ) { $user_helper->loginUserById( $user->GetID() ); } } } /** * Set's new unique resource id to user * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemCreate(kEvent $event) { parent::OnBeforeItemCreate($event); $this->beforeItemChanged($event); /** @var kCountryStatesHelper $cs_helper */ $cs_helper = $this->Application->recallObject('CountryStatesHelper'); /** @var UsersItem $object */ $object = $event->getObject(); if ( !$object->isSubscriberOnly() ) { // don't check state-to-country relations for subscribers $cs_helper->CheckStateField($event, 'State', 'Country'); } if ( $object->getFormName() != 'login' ) { $this->_makePasswordRequired($event); } $cs_helper->PopulateStates($event, 'State', 'Country'); $this->setUserGroup($object); /** @var UserHelper $user_helper */ $user_helper = $this->Application->recallObject('UserHelper'); if ( !$user_helper->checkBanRules($object) ) { $object->SetError('Username', 'banned'); } $object->SetDBField('IPAddress', $this->Application->getClientIp()); if ( !$this->Application->isAdmin ) { $object->SetDBField('FrontLanguage', $this->Application->GetVar('m_lang')); } } /** * Sets primary group of the user * * @param kDBItem $object */ protected function setUserGroup(&$object) { if ($object->Special == 'subscriber') { $object->SetDBField('PrimaryGroupId', $this->Application->ConfigValue('User_SubscriberGroup')); return ; } // set primary group to user if ( !$this->Application->isAdminUser ) { $group_id = $object->GetDBField('PrimaryGroupId'); if ($group_id) { // check, that group is allowed for Front-End $sql = 'SELECT GroupId FROM ' . TABLE_PREFIX . 'UserGroups WHERE GroupId = ' . (int)$group_id . ' AND FrontRegistration = 1'; $group_id = $this->Conn->GetOne($sql); } if (!$group_id) { // when group not selected OR not allowed -> use default group $object->SetDBField('PrimaryGroupId', $this->Application->ConfigValue('User_NewGroup')); } } } /** * Assigns a user to it's primary group * * @param kEvent $event */ protected function assignToPrimaryGroup($event) { /** @var kDBItem $object */ $object = $event->getObject(); $primary_group_id = $object->GetDBField('PrimaryGroupId'); if ($primary_group_id) { $ug_table = TABLE_PREFIX . 'UserGroupRelations'; if ( $object->IsTempTable() ) { $ug_table = $this->Application->GetTempName($ug_table, 'prefix:' . $event->Prefix); } $fields_hash = Array ( 'PortalUserId' => $object->GetID(), 'GroupId' => $primary_group_id, ); $this->Conn->doInsert($fields_hash, $ug_table, 'REPLACE'); } } /** * Set's new unique resource id to user * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemValidate(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); $resource_id = $object->GetDBField('ResourceId'); if ( !$resource_id ) { $object->SetDBField('ResourceId', $this->Application->NextResourceId()); } } /** * Enter description here... * * @param kEvent $event */ function OnRecommend($event) { /** @var kDBItem $object */ $object = $event->getObject( Array ('form_name' => 'recommend') ); $object->SetFieldsFromHash($this->getSubmittedFields($event)); if ( !$object->ValidateField('RecommendEmail') ) { $event->status = kEvent::erFAIL; return ; } $send_params = Array ( 'to_email' => $object->GetDBField('RecommendEmail'), 'to_name' => $object->GetDBField('RecommendEmail'), ); $user_id = $this->Application->RecallVar('user_id'); $email_sent = $this->Application->emailUser('USER.SUGGEST', $user_id, $send_params); $this->Application->emailAdmin('USER.SUGGEST'); if ( $email_sent ) { $event->SetRedirectParam('pass', 'all'); $event->redirect = $this->Application->GetVar('template_success'); } else { $event->status = kEvent::erFAIL; $object->SetError('RecommendEmail', 'send_error'); } } /** * Saves address changes and mades no redirect * * @param kEvent $event */ function OnUpdateAddress($event) { /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ( $items_info ) { $id = key($items_info); $field_values = $items_info[$id]; if ( $id > 0 ) { $object->Load($id); } $object->setID($id); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); $object->Validate(); } /** @var kCountryStatesHelper $cs_helper */ $cs_helper = $this->Application->recallObject('CountryStatesHelper'); $cs_helper->PopulateStates($event, 'State', 'Country'); $event->redirect = false; } /** * Validate subscriber's email & store it to session -> redirect to confirmation template * * @param kEvent $event */ function OnSubscribeQuery($event) { /** @var UsersItem $object */ $object = $event->getObject( Array ('form_name' => 'subscription') ); $object->SetFieldsFromHash($this->getSubmittedFields($event)); if ( !$object->ValidateField('SubscriberEmail') ) { $event->status = kEvent::erFAIL; return ; } $user_email = $object->GetDBField('SubscriberEmail'); $object->Load($user_email, 'Email'); $event->SetRedirectParam('subscriber_email', $user_email); if ( $object->isLoaded() && $object->isSubscribed() ) { $event->redirect = $this->Application->GetVar('unsubscribe_template'); } else { $event->redirect = $this->Application->GetVar('subscribe_template'); } $event->SetRedirectParam('pass', 'm'); } /** * Subscribe/Unsubscribe user based on email stored in previous step * * @param kEvent $event */ function OnSubscribeUser($event) { /** @var UsersItem $object */ $object = $event->getObject( Array ('form_name' => 'subscription') ); $user_email = $this->Application->GetVar('subscriber_email'); $object->SetDBField('SubscriberEmail', $user_email); if ( !$object->ValidateField('SubscriberEmail') ) { $event->status = kEvent::erFAIL; return ; } $username_required = $object->isRequired('Username'); $this->RemoveRequiredFields($object); $object->Load($user_email, 'Email'); if ( $object->isLoaded() ) { if ( $object->isSubscribed() ) { if ( $event->getEventParam('no_unsubscribe') ) { // for customization code from FormsEventHandler return ; } if ( $object->isSubscriberOnly() ) { /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->Prefix . '_TempHandler', 'kTempTablesHandler'); $temp_handler->DeleteItems($event->Prefix, '', Array($object->GetID())); } else { $this->RemoveSubscriberGroup( $object->GetID() ); } $event->redirect = $this->Application->GetVar('unsubscribe_ok_template'); } else { $this->AddSubscriberGroup($object); $event->redirect = $this->Application->GetVar('subscribe_ok_template'); } } else { $object->generatePassword(); $object->SetDBField('Email', $user_email); if ( $username_required ) { $object->SetDBField('Username', str_replace('@', '_at_', $user_email)); } $object->SetDBField('Status', STATUS_ACTIVE); // make user subscriber Active by default if ( $object->Create() ) { $this->AddSubscriberGroup($object); $event->redirect = $this->Application->GetVar('subscribe_ok_template'); } } } /** * Adding user to subscribers group * * @param UsersItem $object */ function AddSubscriberGroup(&$object) { if ( !$object->isSubscriberOnly() ) { $fields_hash = Array ( 'PortalUserId' => $object->GetID(), 'GroupId' => $this->Application->ConfigValue('User_SubscriberGroup'), ); $this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'UserGroupRelations'); } $this->Application->emailAdmin('USER.SUBSCRIBE'); $this->Application->emailUser('USER.SUBSCRIBE', $object->GetID()); } /** * Removing user from subscribers group * * @param int $user_id */ function RemoveSubscriberGroup($user_id) { $group_id = $this->Application->ConfigValue('User_SubscriberGroup'); $sql = 'DELETE FROM ' . TABLE_PREFIX . 'UserGroupRelations WHERE PortalUserId = ' . $user_id . ' AND GroupId = ' . $group_id; $this->Conn->Query($sql); $this->Application->emailAdmin('USER.UNSUBSCRIBE'); $this->Application->emailUser('USER.UNSUBSCRIBE', $user_id); } /** * Validates forgot password form and sends password reset confirmation e-mail * * @param kEvent $event * @return void */ function OnForgotPassword($event) { /** @var kDBItem $object */ $object = $event->getObject( Array ('form_name' => 'forgot_password') ); $object->SetFieldsFromHash($this->getSubmittedFields($event)); /** @var UsersItem $user */ $user = $this->Application->recallObject('u.tmp', null, Array ('skip_autoload' => true)); $found = $allow_reset = false; $email_or_username = $object->GetDBField('ForgotLogin'); $is_email = strpos($email_or_username, '@') !== false; if ( strlen($email_or_username) ) { $user->Load($email_or_username, $is_email ? 'Email' : 'Username'); } if ( $user->isLoaded() ) { $min_pwd_reset_delay = $this->Application->ConfigValue('Users_AllowReset'); $found = ($user->GetDBField('Status') == STATUS_ACTIVE) && strlen($user->GetDBField('Password')); if ( !$user->GetDBField('PwResetConfirm') ) { // no reset made -> allow $allow_reset = true; } else { // reset made -> wait N minutes, then allow $allow_reset = TIMENOW > $user->GetDBField('PwRequestTime') + $min_pwd_reset_delay; } } if ( $found && $allow_reset ) { $this->Application->emailUser('USER.PSWDC', $user->GetID()); $event->redirect = $this->Application->GetVar('template_success'); return; } if ( strlen($email_or_username) ) { $object->SetError('ForgotLogin', $found ? 'reset_denied' : ($is_email ? 'unknown_email' : 'unknown_username')); } if ( !$object->ValidateField('ForgotLogin') ) { $event->status = kEvent::erFAIL; } } /** * Updates kDBItem * * @param kEvent $event * @return void * @access protected */ protected function OnUpdate(kEvent $event) { parent::OnUpdate($event); if ( !$this->Application->isAdmin ) { $this->setNextTemplate($event); } } /** * Updates kDBItem via AJAX. * * @param kEvent $event Event. * * @return void */ protected function OnUpdateAjax(kEvent $event) { /** @var AjaxFormHelper $ajax_form_helper */ $ajax_form_helper = $this->Application->recallObject('AjaxFormHelper'); $ajax_form_helper->transitEvent($event, 'OnUpdate'); } /** * Checks state against country * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemUpdate(kEvent $event) { parent::OnBeforeItemUpdate($event); $this->beforeItemChanged($event); /** @var kCountryStatesHelper $cs_helper */ $cs_helper = $this->Application->recallObject('CountryStatesHelper'); $cs_helper->CheckStateField($event, 'State', 'Country'); $cs_helper->PopulateStates($event, 'State', 'Country'); /** @var kDBItem $object */ $object = $event->getObject(); if ( $event->Special == 'forgot' ) { $object->SetDBField('PwResetConfirm', ''); $object->SetDBField('PwRequestTime_date', NULL); $object->SetDBField('PwRequestTime_time', NULL); } $changed_fields = array_keys($object->GetChangedFields()); if ( $changed_fields && !in_array('Modified', $changed_fields) ) { $object->SetDBField('Modified_date', adodb_mktime()); $object->SetDBField('Modified_time', adodb_mktime()); } if ( !$this->Application->isAdmin && in_array('Email', $changed_fields) && ($event->Special != 'email-restore') ) { $object->SetDBField('EmailVerified', 0); } } /** * Occurs before item is changed * * @param kEvent $event */ function beforeItemChanged($event) { /** @var UsersItem $object */ $object = $event->getObject(); if ( !$this->Application->isAdmin && $object->getFormName() == 'registration' ) { // sets new user's status based on config options $status_map = Array (1 => STATUS_ACTIVE, 2 => STATUS_DISABLED, 3 => STATUS_PENDING, 4 => STATUS_PENDING); $object->SetDBField('Status', $status_map[ $this->Application->ConfigValue('User_Allow_New') ]); if ( $this->Application->ConfigValue('User_Password_Auto') ) { $object->generatePassword( rand(5, 8) ); } if ( $this->Application->ConfigValue('RegistrationCaptcha') ) { /** @var kCaptchaHelper $captcha_helper */ $captcha_helper = $this->Application->recallObject('CaptchaHelper'); $captcha_helper->validateCode($event, false); } if ( $event->Name == 'OnBeforeItemUpdate' ) { // when a subscriber-only users performs normal registration, then assign him to Member group $this->setUserGroup($object); } } } /** * Sets redirect template based on user status & user request contents * * @param kEvent $event * @param bool $for_registration */ function setNextTemplate($event, $for_registration = false) { $event->SetRedirectParam('opener', 's'); /** @var UsersItem $object */ $object = $event->getObject(); $next_template = false; if ( $object->GetDBField('Status') == STATUS_ACTIVE && $this->Application->GetVar('next_template') ) { $next_template = $this->Application->GetVar('next_template'); } elseif ( $for_registration ) { switch ( $this->Application->ConfigValue('User_Allow_New') ) { case 1: // Immediate $next_template = $this->Application->GetVar('registration_confirm_template'); break; case 3: // Upon Approval case 4: // Email Activation $next_template = $this->Application->GetVar('registration_confirm_pending_template'); break; } } if ($next_template) { $event->redirect = $next_template; } } /** * Delete users from groups if their membership is expired * * @param kEvent $event */ function OnCheckExpiredMembership($event) { // send pre-expiration reminders: begin $pre_expiration = adodb_mktime() + $this->Application->ConfigValue('User_MembershipExpirationReminder') * 3600 * 24; $sql = 'SELECT PortalUserId, GroupId FROM '.TABLE_PREFIX.'UserGroupRelations WHERE (MembershipExpires IS NOT NULL) AND (ExpirationReminderSent = 0) AND (MembershipExpires < '.$pre_expiration.')'; $skip_clause = $event->getEventParam('skip_clause'); if ($skip_clause) { $sql .= ' AND !('.implode(') AND !(', $skip_clause).')'; } $records = $this->Conn->Query($sql); if ($records) { $conditions = Array(); foreach ($records as $record) { $this->Application->emailUser('USER.MEMBERSHIP.EXPIRATION.NOTICE', $record['PortalUserId']); $this->Application->emailAdmin('USER.MEMBERSHIP.EXPIRATION.NOTICE'); $conditions[] = '(PortalUserId = '.$record['PortalUserId'].' AND GroupId = '.$record['GroupId'].')'; } $sql = 'UPDATE '.TABLE_PREFIX.'UserGroupRelations SET ExpirationReminderSent = 1 WHERE '.implode(' OR ', $conditions); $this->Conn->Query($sql); } // send pre-expiration reminders: end // remove users from groups with expired membership: begin $sql = 'SELECT PortalUserId FROM '.TABLE_PREFIX.'UserGroupRelations WHERE (MembershipExpires IS NOT NULL) AND (MembershipExpires < '.adodb_mktime().')'; $user_ids = $this->Conn->GetCol($sql); if ($user_ids) { foreach ($user_ids as $id) { $this->Application->emailUser('USER.MEMBERSHIP.EXPIRED', $id); $this->Application->emailAdmin('USER.MEMBERSHIP.EXPIRED'); } } $sql = 'DELETE FROM '.TABLE_PREFIX.'UserGroupRelations WHERE (MembershipExpires IS NOT NULL) AND (MembershipExpires < '.adodb_mktime().')'; $this->Conn->Query($sql); // remove users from groups with expired membership: end } /** * Used to keep user registration form data, while showing affiliate registration form fields * * @param kEvent $event * @return void * @access protected */ protected function OnRefreshForm($event) { $event->redirect = false; $item_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); $id = key($item_info); $field_values = $item_info[$id]; /** @var kDBItem $object */ $object = $event->getObject( Array ('skip_autoload' => true) ); $object->IgnoreValidation = true; $object->setID($id); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); } /** * Sets persistant variable * * @param kEvent $event */ function OnSetPersistantVariable($event) { $field = $this->Application->GetVar('field'); $value = $this->Application->GetVar('value'); $this->Application->StorePersistentVar($field, $value); $force_tab = $this->Application->GetVar('SetTab'); if ($force_tab) { $this->Application->StoreVar('force_tab', $force_tab); } } /** * Return user from order by special .ord * * @param kEvent $event * @return int * @access public */ public function getPassedID(kEvent $event) { switch ($event->Special) { case 'ord': /** @var OrdersItem $order */ $order = $this->Application->recallObject('ord'); return $order->GetDBField('PortalUserId'); break; case 'profile': $id = $this->Application->GetVar('user_id'); if ( $id ) { $event->setEventParam(kEvent::FLAG_ID_FROM_REQUEST, true); return $id; } // If none user_id given use current user id. return $this->Application->RecallVar('user_id'); break; case 'forgot': /** @var UserHelper $user_helper */ $user_helper = $this->Application->recallObject('UserHelper'); $id = $user_helper->validateUserCode($this->Application->GetVar('user_key'), 'forgot_password'); if ( is_numeric($id) ) { return $id; } break; } if ( preg_match('/^(login|register|recommend|subscribe|forgot)/', $event->Special) ) { // this way we can have 2+ objects stating with same special, e.g. "u.login-sidebox" and "u.login-main" return USER_GUEST; } elseif ( preg_match('/^(update|delete)/', $event->Special) ) { // This way we can have 2+ objects stating with same special, e.g. "u.update-sidebox" and "u.update-profile". return $this->Application->RecallVar('user_id'); } return parent::getPassedID($event); } /** * Allows to change root password * * @param kEvent $event * @return void * @access protected */ protected function OnUpdatePassword($event) { $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ( !$items_info ) { return; } $id = key($items_info); $field_values = $items_info[$id]; $user_id = $this->Application->RecallVar('user_id'); if ( $id == $user_id && ($user_id > 0 || $user_id == USER_ROOT) ) { /** @var kDBItem $user_dummy */ $user_dummy = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true)); $user_dummy->Load($id); $status_field = $user_dummy->getStatusField(); if ( $user_dummy->GetDBField($status_field) != STATUS_ACTIVE ) { // not active user is not allowed to update his record (he could not activate himself manually) return ; } } if ( $user_id == USER_ROOT ) { /** @var UsersItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); // this is internal hack to allow root/root passwords for dev if ( $this->Application->isDebugMode() && $field_values['RootPassword'] == 'root' ) { $object->SetFieldOption('RootPassword', 'min_length', 4); } $this->RemoveRequiredFields($object); $object->SetDBField('RootPassword', $this->Application->ConfigValue('RootPass')); $object->setID(-1); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); if ( $object->Validate() ) { // validation on, password match too $fields_hash = Array ('VariableValue' => $object->GetDBField('RootPassword')); $conf_table = $this->Application->getUnitOption('conf', 'TableName'); $this->Conn->doUpdate($fields_hash, $conf_table, 'VariableName = "RootPass"'); $event->SetRedirectParam('opener', 'u'); } else { $event->status = kEvent::erFAIL; $event->redirect = false; return ; } } else { /** @var kDBItem $object */ $object = $event->getObject(); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); if ( !$object->Update() ) { $event->status = kEvent::erFAIL; $event->redirect = false; } } $event->SetRedirectParam('opener', 'u'); } /** * Resets grid settings, remembered in each user record * * @param kEvent $event * @return void * @access protected */ protected function OnMassResetSettings($event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return; } $ids = $this->StoreSelectedIDs($event); $default_user_id = $this->Application->ConfigValue('DefaultSettingsUserId'); if ( in_array($default_user_id, $ids) ) { array_splice($ids, array_search($default_user_id, $ids), 1); } if ( $ids ) { $q = 'DELETE FROM ' . TABLE_PREFIX . 'UserPersistentSessionData WHERE PortalUserId IN (' . join(',', $ids) . ') AND (VariableName LIKE "%_columns_%" OR VariableName LIKE "%_filter%" OR VariableName LIKE "%_PerPage%")'; $this->Conn->Query($q); } $this->clearSelectedIDs($event); } /** * Checks, that currently loaded item is allowed for viewing (non permission-based) * * @param kEvent $event * @return bool * @access protected */ protected function checkItemStatus(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); if ( !$object->isLoaded() ) { return true; } $virtual_users = Array (USER_ROOT, USER_GUEST); return ($object->GetDBField('Status') == STATUS_ACTIVE) || in_array($object->GetID(), $virtual_users); } /** * Sends approved/declined email event on user status change * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemUpdate(kEvent $event) { parent::OnAfterItemUpdate($event); $this->afterItemChanged($event); /** @var UsersItem $object */ $object = $event->getObject(); if ( !$this->Application->isAdmin && ($event->Special != 'email-restore') ) { $this->sendEmailChangeEvent($event); } if ( !$this->Application->isAdmin || $object->IsTempTable() ) { return; } $this->sendStatusChangeEvent($object->GetID(), $object->GetOriginalField('Status'), $object->GetDBField('Status')); } /** * Occurs, after item is changed * * @param kEvent $event */ protected function afterItemChanged($event) { $this->saveUserImages($event); /** @var UsersItem $object */ $object = $event->getObject(); if ( $object->GetDBField('EmailPassword') && $object->GetDBField('Password_plain') ) { $email_passwords = $this->Application->RecallVar('email_passwords'); $email_passwords = $email_passwords ? unserialize($email_passwords) : Array (); $email_passwords[ $object->GetID() ] = $object->GetDBField('Password_plain'); $this->Application->StoreVar('email_passwords', serialize($email_passwords)); } // update user subscription status (via my profile or new user registration) if ( !$this->Application->isAdmin && !$object->isSubscriberOnly() ) { if ( $object->GetDBField('SubscribeToMailing') && !$object->isSubscribed() ) { $this->AddSubscriberGroup($object); } elseif ( !$object->GetDBField('SubscribeToMailing') && $object->isSubscribed() ) { $this->RemoveSubscriberGroup( $object->GetID() ); } } } /** * Stores user's original Status before overwriting with data from temp table * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeDeleteFromLive(kEvent $event) { parent::OnBeforeDeleteFromLive($event); $user_id = $event->getEventParam('id'); $user_status = $this->Application->GetVar('user_status', Array ()); if ( $user_id > 0 ) { $user_status[$user_id] = $this->getUserStatus($user_id); $this->Application->SetVar('user_status', $user_status); } } /** * Sends approved/declined email event on user status change (in temp tables during editing) * * @param kEvent $event * @return void * @access protected */ protected function OnAfterCopyToLive(kEvent $event) { parent::OnAfterCopyToLive($event); $temp_id = $event->getEventParam('temp_id'); $email_passwords = $this->Application->RecallVar('email_passwords'); if ( $email_passwords ) { $email_passwords = unserialize($email_passwords); if ( isset($email_passwords[$temp_id]) ) { /** @var kDBItem $object */ $object = $event->getObject(); $object->SwitchToLive(); $object->Load( $event->getEventParam('id') ); $object->SetField('Password', $email_passwords[$temp_id]); $object->SetField('VerifyPassword', $email_passwords[$temp_id]); $this->Application->emailUser($temp_id > 0 ? 'USER.NEW.PASSWORD': 'USER.ADD.BYADMIN', $object->GetID()); unset($email_passwords[$temp_id]); $this->Application->StoreVar('email_passwords', serialize($email_passwords)); } } if ( $temp_id > 0 ) { // only send status change e-mail on user update $new_status = $this->getUserStatus($temp_id); $user_status = $this->Application->GetVar('user_status'); $this->sendStatusChangeEvent($temp_id, $user_status[$temp_id], $new_status); } } /** * Returns user status (active, pending, disabled) based on ID and temp mode setting * * @param int $user_id * @return int */ function getUserStatus($user_id) { $id_field = $this->Application->getUnitOption($this->Prefix, 'IDField'); $table_name = $this->Application->getUnitOption($this->Prefix, 'TableName'); $sql = 'SELECT Status FROM '.$table_name.' WHERE '.$id_field.' = '.$user_id; return $this->Conn->GetOne($sql); } /** * Sends approved/declined email event on user status change * * @param int $user_id * @param int $prev_status * @param int $new_status */ function sendStatusChangeEvent($user_id, $prev_status, $new_status) { $status_events = Array ( STATUS_ACTIVE => 'USER.APPROVE', STATUS_DISABLED => 'USER.DENY', ); $email_event = isset($status_events[$new_status]) ? $status_events[$new_status] : false; if (($prev_status != $new_status) && $email_event) { $this->Application->emailUser($email_event, $user_id); $this->Application->emailAdmin($email_event); } // deletes sessions from users, that are no longer active if (($prev_status != $new_status) && ($new_status != STATUS_ACTIVE)) { $sql = 'SELECT SessionKey FROM ' . TABLE_PREFIX . 'UserSessions WHERE PortalUserId = ' . $user_id; $session_ids = $this->Conn->GetCol($sql); $this->Application->Session->DeleteSessions($session_ids); } } /** * Sends restore/validation email event on user email change * * @param kEvent $event * @return void * @access protected */ protected function sendEmailChangeEvent(kEvent $event) { /** @var UsersItem $object */ $object = $event->getObject(); $new_email = $object->GetDBField('Email'); $prev_email = $object->GetOriginalField('Email'); if ( !$new_email || ($prev_email == $new_email) ) { return; } $prev_emails = $object->GetDBField('PrevEmails'); $prev_emails = $prev_emails ? unserialize($prev_emails) : Array (); $fields_hash = Array ( 'PrevEmails' => serialize($prev_emails), 'EmailVerified' => 0, ); $user_id = $object->GetID(); if ( $prev_email ) { $hash = md5(TIMENOW + $user_id); $prev_emails[$hash] = $prev_email; $fields_hash['PrevEmails'] = serialize($prev_emails); $send_params = Array ( 'hash' => $hash, 'to_email' => $prev_email, 'to_name' => trim($object->GetDBField('FirstName') . ' ' . $object->GetDBField('LastName')), ); $this->Application->emailUser('USER.EMAIL.CHANGE.UNDO', null, $send_params); } if ( $new_email ) { $this->Application->emailUser('USER.EMAIL.CHANGE.VERIFY', $user_id); } // direct DB update, since USER.EMAIL.CHANGE.VERIFY puts verification code in user record, that we don't want to loose $this->Conn->doUpdate($fields_hash, $object->TableName, 'PortalUserId = ' . $user_id); } /** * OnAfterConfigRead for users * * @param kEvent $event * @return void * @access protected */ protected function OnAfterConfigRead(kEvent $event) { parent::OnAfterConfigRead($event); $forms = $this->Application->getUnitOption($event->Prefix, 'Forms'); $form_fields =& $forms['default']['Fields']; // 1. arrange user registration countries /** @var SiteHelper $site_helper */ $site_helper = $this->Application->recallObject('SiteHelper'); $first_country = $site_helper->getDefaultCountry('', false); if ($first_country === false) { $first_country = $this->Application->ConfigValue('User_Default_Registration_Country'); } if ($first_country) { // update user country dropdown sql $form_fields['Country']['options_sql'] = preg_replace('/ORDER BY (.*)/', 'ORDER BY IF (CountryStateId = '.$first_country.', 1, 0) DESC, \\1', $form_fields['Country']['options_sql']); } // 2. set default user registration group $form_fields['PrimaryGroupId']['default'] = $this->Application->ConfigValue('User_NewGroup'); // 3. allow avatar upload on Front-End /** @var FileHelper $file_helper */ $file_helper = $this->Application->recallObject('FileHelper'); $file_helper->createItemFiles($event->Prefix, true); // create image fields if ($this->Application->isAdminUser) { // 4. when in administrative console, then create all users with Active status $form_fields['Status']['default'] = STATUS_ACTIVE; // 5. remove groups tab on editing forms when AdvancedUserManagement config variable not set if (!$this->Application->ConfigValue('AdvancedUserManagement')) { $edit_tab_presets = $this->Application->getUnitOption($event->Prefix, 'EditTabPresets'); foreach ($edit_tab_presets as $preset_name => $preset_tabs) { if (array_key_exists('groups', $preset_tabs)) { unset($edit_tab_presets[$preset_name]['groups']); if (count($edit_tab_presets[$preset_name]) == 1) { // only 1 tab left -> remove it too $edit_tab_presets[$preset_name] = Array (); } } } $this->Application->setUnitOption($event->Prefix, 'EditTabPresets', $edit_tab_presets); } } if ( $this->Application->ConfigValue('RegistrationUsernameRequired') ) { // Username becomes required only, when it's used in registration process $max_username = $this->Application->ConfigValue('MaxUserName'); $form_fields['Username']['required'] = 1; $form_fields['Username']['min_len'] = $this->Application->ConfigValue('Min_UserName'); $form_fields['Username']['max_len'] = $max_username ? $max_username : 255; } $this->Application->setUnitOption($event->Prefix, 'Forms', $forms); } /** * OnMassCloneUsers * * @param kEvent $event */ function OnMassCloneUsers($event) { if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $event->status = kEvent::erFAIL; return; } /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->Prefix.'_TempHandler', 'kTempTablesHandler'); $ids = $this->StoreSelectedIDs($event); $temp_handler->CloneItems($event->Prefix, '', $ids); $this->clearSelectedIDs($event); } /** * When cloning users, reset password (set random) * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeClone(kEvent $event) { parent::OnBeforeClone($event); /** @var UsersItem $object */ $object = $event->getObject(); $object->generatePassword(); $object->SetDBField('ResourceId', 0); // this will reset it // change email because it should be unique $object->NameCopy(Array (), $object->GetID(), 'Email', 'copy%1$s.%2$s'); } /** * Saves selected ids to session * * @param kEvent $event */ function OnSaveSelected($event) { $this->StoreSelectedIDs($event); // remove current ID, otherwise group selector will use it in filters $this->Application->DeleteVar($event->getPrefixSpecial(true) . '_id'); } /** * Sets primary group of selected users * * @param kEvent $event */ function OnProcessSelected($event) { $event->SetRedirectParam('opener', 'u'); $user_ids = $this->getSelectedIDs($event, true); $this->clearSelectedIDs($event); $dst_field = $this->Application->RecallVar('dst_field'); if ( $dst_field != 'PrimaryGroupId' ) { return; } $group_ids = array_keys($this->Application->GetVar('g')); $primary_group_id = $group_ids ? array_shift($group_ids) : false; if ( !$user_ids || !$primary_group_id ) { return; } $table_name = $this->Application->getUnitOption('ug', 'TableName'); // 1. mark group as primary $sql = 'UPDATE ' . TABLE_PREFIX . 'Users SET PrimaryGroupId = ' . $primary_group_id . ' WHERE PortalUserId IN (' . implode(',', $user_ids) . ')'; $this->Conn->Query($sql); + foreach ( $user_ids as $user_id ) { + $this->Application->incrementCacheSerial('u', $user_id); + } + + $this->Application->incrementCacheSerial('u'); + $sql = 'SELECT PortalUserId FROM ' . $table_name . ' WHERE (GroupId = ' . $primary_group_id . ') AND (PortalUserId IN (' . implode(',', $user_ids) . '))'; $existing_members = $this->Conn->GetCol($sql); // 2. add new members to a group $new_members = array_diff($user_ids, $existing_members); foreach ($new_members as $user_id) { $fields_hash = Array ( 'GroupId' => $primary_group_id, 'PortalUserId' => $user_id, ); $this->Conn->doInsert($fields_hash, $table_name); } } /** * Loads user images * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemLoad(kEvent $event) { parent::OnAfterItemLoad($event); // linking existing images for item with virtual fields /** @var ImageHelper $image_helper */ $image_helper = $this->Application->recallObject('ImageHelper'); /** @var UsersItem $object */ $object = $event->getObject(); $image_helper->LoadItemImages($object); /** @var kCountryStatesHelper $cs_helper */ $cs_helper = $this->Application->recallObject('CountryStatesHelper'); $cs_helper->PopulateStates($event, 'State', 'Country'); // get user subscription status $object->SetDBField('SubscribeToMailing', $object->isSubscribed() ? 1 : 0); if ( !$this->Application->isAdmin ) { $object->SetFieldOption('FrontLanguage', 'options', $this->getEnabledLanguages()); } } /** * Returns list of enabled languages with their names * * @return Array * @access protected */ protected function getEnabledLanguages() { $cache_key = 'user_languages[%LangSerial%]'; $ret = $this->Application->getCache($cache_key); if ( $ret === false ) { /** @var kDBList $languages */ $languages = $this->Application->recallObject('lang.enabled', 'lang_List'); $ret = Array (); foreach ($languages as $language_info) { $ret[$languages->GetID()] = $language_info['LocalName']; } $this->Application->setCache($cache_key, $ret); } return $ret; } /** * Save user images * * @param kEvent $event */ function saveUserImages($event) { if (!$this->Application->isAdmin) { /** @var ImageHelper $image_helper */ $image_helper = $this->Application->recallObject('ImageHelper'); /** @var kDBItem $object */ $object = $event->getObject(); // process image upload in virtual fields $image_helper->SaveItemImages($object); } } /** * Makes password required for new users * * @param kEvent $event * @return void * @access protected */ protected function OnPreCreate(kEvent $event) { parent::OnPreCreate($event); if ( $event->status != kEvent::erSUCCESS ) { return; } /** @var kDBItem $object */ $object = $event->getObject(); $user_type = $this->Application->GetVar('user_type'); if ( $user_type ) { $object->SetDBField('UserType', $user_type); if ( $user_type == UserType::ADMIN ) { $object->SetDBField('PrimaryGroupId', $this->Application->ConfigValue('User_AdminGroup')); } } if ( $this->Application->ConfigValue('User_Password_Auto') ) { $object->SetDBField('EmailPassword', 1); } $this->_makePasswordRequired($event); } /** * Makes password required for new users * * @param kEvent $event */ function _makePasswordRequired($event) { /** @var kDBItem $object */ $object = $event->getObject(); $required_fields = Array ('Password', 'Password_plain', 'VerifyPassword', 'VerifyPassword_plain'); $object->setRequired($required_fields); } /** * Load item if id is available * * @param kEvent $event * @return void * @access protected */ protected function LoadItem(kEvent $event) { $id = $this->getPassedID($event); if ( $id < 0 ) { // when root, guest and so on /** @var kDBItem $object */ $object = $event->getObject(); $object->Clear($id); return; } parent::LoadItem($event); } /** * Occurs just after login (for hooking) * * @param kEvent $event */ function OnAfterLogin($event) { if ( is_object($event->MasterEvent) && !$this->Application->isAdmin ) { $event->MasterEvent->SetRedirectParam('login', 1); } } /** * Occurs just before logout (for hooking) * * @param kEvent $event */ function OnBeforeLogout($event) { if ( is_object($event->MasterEvent) && !$this->Application->isAdmin ) { $event->MasterEvent->SetRedirectParam('logout', 1); } } /** * Generates password * * @param kEvent $event */ function OnGeneratePassword($event) { $event->status = kEvent::erSTOP; if ( $this->Application->isAdminUser ) { echo kUtil::generatePassword(); } } /** * Changes user's password and logges him in * * @param kEvent $event */ function OnResetLostPassword($event) { /** @var kDBItem $object */ $object = $event->getObject(); $event->CallSubEvent('OnUpdate'); if ( $event->status == kEvent::erSUCCESS ) { /** @var UserHelper $user_helper */ $user_helper = $this->Application->recallObject('UserHelper'); $user =& $user_helper->getUserObject(); $user->Load( $object->GetID() ); if ( $user_helper->checkLoginPermission() ) { $user_helper->loginUserById( $user->GetID() ); } } } /** * Generates new Root password and email it * * @param kEvent $event * @return void * @access protected */ protected function OnResetRootPassword($event) { /** @var kPasswordFormatter $password_formatter */ $password_formatter = $this->Application->recallObject('kPasswordFormatter'); $new_root_password = kUtil::generatePassword(); $this->Application->SetConfigValue('RootPass', $password_formatter->hashPassword($new_root_password)); $this->Application->emailAdmin('ROOT.RESET.PASSWORD', null, Array ('password' => $new_root_password)); $event->SetRedirectParam('reset', 1); $event->SetRedirectParam('pass', 'm'); } /** * Perform login of user, selected in Admin Console, on Front-End in a separate window * * @param kEvent $event * @return void * @access protected */ protected function OnLoginAs(kEvent $event) { /** @var UserHelper $user_helper */ $user_helper = $this->Application->recallObject('UserHelper'); $user =& $user_helper->getUserObject(); $user->Load( $this->Application->GetVar('user_id') ); if ( !$user->isLoaded() ) { return ; } if ( $user_helper->checkLoginPermission() ) { $user_helper->loginUserById( $user->GetID() ); } } } Index: branches/5.2.x/core/units/helpers/user_helper.php =================================================================== --- branches/5.2.x/core/units/helpers/user_helper.php (revision 16773) +++ branches/5.2.x/core/units/helpers/user_helper.php (revision 16774) @@ -1,725 +1,753 @@ event) ) { $this->event = new kEvent('u:OnLogin'); } if ( !$password && !$remember_login_cookie ) { return LoginResult::INVALID_PASSWORD; } $object =& $this->getUserObject(); // process "Save Username" checkbox if ( $this->Application->isAdmin ) { $save_username = $this->Application->GetVar('cb_save_username') ? $username : ''; $this->Application->Session->SetCookie('save_username', $save_username, strtotime('+1 year')); // cookie will be set on next refresh, but refresh won't occur if // login error present, so duplicate cookie in kHTTPQuery $this->Application->SetVar('save_username', $save_username); } // logging in "root" (admin only) $super_admin = ($username == 'super-root') && $this->verifySuperAdmin(); if ( $this->Application->isAdmin && ($username == 'root') || ($super_admin && $username == 'super-root') ) { /** @var kPasswordFormatter $password_formatter */ $password_formatter = $this->Application->recallObject('kPasswordFormatter'); if ( !$password_formatter->checkPasswordFromSetting('RootPass', $password) ) { return LoginResult::INVALID_PASSWORD; } $user_id = USER_ROOT; $object->Clear($user_id); $object->SetDBField('Username', 'root'); if ( !$dry_run ) { $this->loginUserById($user_id, $remember_login_cookie); if ( $super_admin ) { $this->Application->StoreVar('super_admin', 1); } // reset counters $this->Application->resetCounters('UserSessions'); $this->_processLoginRedirect('root', $password); $this->_processInterfaceLanguage(); $this->_fixNextTemplate(); } return LoginResult::OK; } $user_id = $this->getUserId($username, $password, $remember_login_cookie); if ( $user_id ) { $object->Load($user_id); if ( !$this->checkBanRules($object) ) { return LoginResult::BANNED; } if ( $object->GetDBField('Status') == STATUS_ACTIVE ) { if ( !$this->checkLoginPermission() ) { return LoginResult::NO_PERMISSION; } if ( !$dry_run ) { $this->loginUserById($user_id, $remember_login_cookie); if ( $remember_login ) { // remember username & password when "Remember Login" checkbox us checked (when user is using login form on Front-End) $sql = 'SELECT MD5(Password) FROM ' . TABLE_PREFIX . 'Users WHERE PortalUserId = ' . $user_id; $remember_login_hash = $this->Conn->GetOne($sql); $this->Application->Session->SetCookie('remember_login', $username . '|' . $remember_login_hash, strtotime('+1 month')); } if ( !$remember_login_cookie ) { // reset counters $this->Application->resetCounters('UserSessions'); $this->_processLoginRedirect($username, $password); $this->_processInterfaceLanguage(); $this->_fixNextTemplate(); } } return LoginResult::OK; } else { $pending_template = $this->Application->GetVar('pending_disabled_template'); if ( $pending_template !== false && !$dry_run ) { // when user found, but it's not yet approved redirect hit to notification template $this->event->redirect = $pending_template; return LoginResult::OK; } else { // when no notification template given return an error return LoginResult::INVALID_PASSWORD; } } } if ( !$dry_run ) { $this->event->SetRedirectParam('pass', 'all'); // $this->event->SetRedirectParam('pass_category', 1); // to test } return LoginResult::INVALID_PASSWORD; } /** * Login user by it's id * * @param int $user_id * @param bool $remember_login_cookie */ function loginUserById($user_id, $remember_login_cookie = false) { $object =& $this->getUserObject(); $this->Application->SetVar('u.current_id', $user_id); if ( !$this->Application->isAdmin ) { // needed for "profile edit", "registration" forms ON FRONT ONLY $this->Application->SetVar('u_id', $user_id); } $this->Application->StoreVar('user_id', $user_id); $this->Application->Session->SetField('PortalUserId', $user_id); if ($user_id != USER_ROOT) { $groups = $this->Application->RecallVar('UserGroups'); list ($first_group, ) = explode(',', $groups); $this->Application->Session->SetField('GroupId', $first_group); $this->Application->Session->SetField('GroupList', $groups); $this->Application->Session->SetField('TimeZone', $object->GetDBField('TimeZone')); } $this->Application->LoadPersistentVars(); if (!$remember_login_cookie) { // don't change last login time when auto-login is used $this_login = (int)$this->Application->RecallPersistentVar('ThisLogin'); $this->Application->StorePersistentVar('LastLogin', $this_login); $this->Application->StorePersistentVar('ThisLogin', adodb_mktime()); } $hook_event = new kEvent('u:OnAfterLogin'); $hook_event->MasterEvent = $this->event; $this->Application->HandleEvent($hook_event); } /** * Checks login permission * * @return bool */ function checkLoginPermission() { $object =& $this->getUserObject(); $ip_restrictions = $object->GetDBField('IPRestrictions'); if ( $ip_restrictions && !$this->Application->isDebugMode() && !kUtil::ipMatch($ip_restrictions, "\n") ) { return false; } $groups = $object->getMembershipGroups(true); if ( !$groups ) { $groups = Array (); } $default_group = $this->getUserTypeGroup(); if ( $default_group !== false ) { array_push($groups, $default_group); } // store groups, because kApplication::CheckPermission will use them! array_push($groups, $this->Application->ConfigValue('User_LoggedInGroup')); $groups = array_unique($groups); $this->Application->StoreVar('UserGroups', implode(',', $groups), true); // true for optional return $this->Application->CheckPermission($this->Application->isAdmin ? 'ADMIN' : 'LOGIN', 1); } /** * Returns default user group for it's type * * @return bool|string * @access protected */ protected function getUserTypeGroup() { $group_id = false; $object =& $this->getUserObject(); if ( $object->GetDBField('UserType') == UserType::USER ) { $group_id = $this->Application->ConfigValue('User_NewGroup'); } elseif ( $object->GetDBField('UserType') == UserType::ADMIN ) { $group_id = $this->Application->ConfigValue('User_AdminGroup'); } $ip_restrictions = $this->getGroupsWithIPRestrictions(); if ( !isset($ip_restrictions[$group_id]) || kUtil::ipMatch($ip_restrictions[$group_id], "\n") ) { return $group_id; } return false; } /** * Returns groups with IP restrictions * * @return Array * @access public */ public function getGroupsWithIPRestrictions() { static $cache = null; if ( $this->Application->isDebugMode() ) { return Array (); } if ( !isset($cache) ) { $sql = 'SELECT IPRestrictions, GroupId FROM ' . TABLE_PREFIX . 'UserGroups WHERE COALESCE(IPRestrictions, "") <> ""'; $cache = $this->Conn->GetCol($sql, 'GroupId'); } return $cache; } /** * Performs user logout * */ function logoutUser() { if (!isset($this->event)) { $this->event = new kEvent('u:OnLogout'); } $hook_event = new kEvent('u:OnBeforeLogout'); $hook_event->MasterEvent = $this->event; $this->Application->HandleEvent($hook_event); $this->_processLoginRedirect(); $user_id = USER_GUEST; $this->Application->SetVar('u.current_id', $user_id); /** @var UsersItem $object */ $object = $this->Application->recallObject('u.current', null, Array('skip_autoload' => true)); $object->Load($user_id); $this->Application->DestroySession(); $this->Application->StoreVar('user_id', $user_id, true); $this->Application->Session->SetField('PortalUserId', $user_id); $group_list = $this->Application->ConfigValue('User_GuestGroup') . ',' . $this->Application->ConfigValue('User_LoggedInGroup'); $this->Application->StoreVar('UserGroups', $group_list, true); $this->Application->Session->SetField('GroupList', $group_list); if ($this->Application->ConfigValue('UseJSRedirect')) { $this->event->SetRedirectParam('js_redirect', 1); } $this->Application->resetCounters('UserSessions'); $this->Application->Session->SetCookie('remember_login', '', strtotime('-1 hour')); // don't pass user prefix on logout, since resulting url will have broken "env" $this->event->SetRedirectParam('pass', MOD_REWRITE ? 'm' : 'all'); $this->_fixNextTemplate(); } /** * Returns user id based on given criteria * * @param string $username * @param string $password * @param string $remember_login_cookie * @return int */ function getUserId($username, $password, $remember_login_cookie) { if ( $remember_login_cookie ) { list ($username, $password) = explode('|', $remember_login_cookie); // 0 - username, 1 - md5(password_hash) } $sql = 'SELECT PortalUserId, Password, PasswordHashingMethod FROM ' . TABLE_PREFIX . 'Users WHERE ' . (strpos($username, '@') === false ? 'Username' : 'Email') . ' = %1$s'; $user_info = $this->Conn->GetRow(sprintf($sql, $this->Conn->qstr($username))); if ( $user_info ) { if ( $remember_login_cookie ) { return md5($user_info['Password']) == $password ? $user_info['PortalUserId'] : false; } else { /** @var kPasswordFormatter $password_formatter */ $password_formatter = $this->Application->recallObject('kPasswordFormatter'); $hashing_method = $user_info['PasswordHashingMethod']; if ( $password_formatter->checkPassword($password, $user_info['Password'], $hashing_method) ) { if ( $hashing_method != PasswordHashingMethod::PHPPASS ) { $this->_fixUserPassword($user_info['PortalUserId'], $password); } return $user_info['PortalUserId']; } } } return false; } /** * Apply new password hashing to given user's password * * @param int $user_id * @param string $password * @return void * @access protected */ protected function _fixUserPassword($user_id, $password) { /** @var kPasswordFormatter $password_formatter */ $password_formatter = $this->Application->recallObject('kPasswordFormatter'); $fields_hash = Array ( 'Password' => $password_formatter->hashPassword($password), 'PasswordHashingMethod' => PasswordHashingMethod::PHPPASS, ); $this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'Users', 'PortalUserId = ' . $user_id); } /** * Process all required data and redirect logged-in user * * @param string $username * @param string $password * @return void */ protected function _processLoginRedirect($username = null, $password = null) { // set next template $next_template = $this->Application->GetVar('next_template'); if ( $next_template ) { $this->event->redirect = $next_template; } // process IIS redirect if ( $this->Application->ConfigValue('UseJSRedirect') ) { $this->event->SetRedirectParam('js_redirect', 1); } // synchronize login /** @var UsersSyncronizeManager $sync_manager */ $sync_manager = $this->Application->recallObject('UsersSyncronizeManager', null, Array (), Array ('InPortalSyncronize')); if ( isset($username) && isset($password) ) { $sync_manager->performAction('LoginUser', $username, $password); } else { $sync_manager->performAction('LogoutUser'); } } /** * Sets correct interface language after successful login, based on user settings * * @return void * @access protected */ protected function _processInterfaceLanguage() { if ( defined('IS_INSTALL') && IS_INSTALL ) { $this->event->SetRedirectParam('m_lang', 1); // data $this->Application->Session->SetField('Language', 1); // interface return; } $language_field = $this->Application->isAdmin ? 'AdminLanguage' : 'FrontLanguage'; $primary_language_field = $this->Application->isAdmin ? 'AdminInterfaceLang' : 'PrimaryLang'; $is_root = $this->Application->RecallVar('user_id') == USER_ROOT; $object =& $this->getUserObject(); $user_language_id = $is_root ? $this->Application->RecallPersistentVar($language_field) : $object->GetDBField($language_field); $sql = 'SELECT LanguageId, IF(LanguageId = ' . (int)$user_language_id . ', 2, ' . $primary_language_field . ') AS SortKey FROM ' . TABLE_PREFIX . 'Languages WHERE Enabled = 1 HAVING SortKey <> 0 ORDER BY SortKey DESC'; $language_info = $this->Conn->GetRow($sql); $language_id = $language_info && $language_info['LanguageId'] ? $language_info['LanguageId'] : $user_language_id; if ( $user_language_id != $language_id ) { // first login OR language was deleted or disabled if ( $is_root ) { $this->Application->StorePersistentVar($language_field, $language_id); } else { $object->SetDBField($language_field, $language_id); $object->Update(); } } // set language for Admin Console & Front-End with disabled Mod-Rewrite $this->event->SetRedirectParam('m_lang', $language_id); // data $this->Application->Session->SetField('Language', $language_id); // interface } /** * Injects redirect params into next template, which doesn't happen if next template starts with "external:" * * @return void * @access protected */ protected function _fixNextTemplate() { if ( !MOD_REWRITE || !is_object($this->event) ) { return; } // solve problem, when template is "true" instead of actual template name $template = is_string($this->event->redirect) ? $this->event->redirect : ''; $url = $this->Application->HREF($template, '', $this->event->getRedirectParams(), $this->event->redirectScript); $vars = $this->Application->parseRewriteUrl($url, 'pass'); unset($vars['login'], $vars['logout']); // merge back url params, because they were ignored if this was "external:" url $vars = array_merge($vars, $this->getRedirectParams($vars['pass'], 'pass')); if ( $template != 'index' ) { // The 'index.html' becomes '', which in turn leads to current page instead of 'index.html'. $template = $vars['t']; } unset($vars['is_virtual'], $vars['t']); $this->event->redirect = $template; $this->event->setRedirectParams($vars, false); } /** * Returns current event redirect params with given $prefixes injected into 'pass'. * * @param array $prefixes List of prefixes to inject. * @param string $pass_name Name of array key in redirect params, containing comma-separated prefixes list. * * @return string * @access protected */ protected function getRedirectParams($prefixes, $pass_name = 'passed') { $redirect_params = $this->event->getRedirectParams(); if ( isset($redirect_params[$pass_name]) ) { $redirect_prefixes = explode(',', $redirect_params[$pass_name]); $prefixes = array_unique(array_merge($prefixes, $redirect_prefixes)); } $redirect_params[$pass_name] = implode(',', $prefixes); return $redirect_params; } /** * Checks that user is allowed to use super admin mode * * @return bool */ function verifySuperAdmin() { $sa_mode = kUtil::ipMatch(defined('SA_IP') ? SA_IP : ''); return $sa_mode || $this->Application->isDebugMode(); } /** * Returns user object, used during login processing * * @return UsersItem * @access public */ public function &getUserObject() { $prefix_special = $this->Application->isAdmin ? 'u.current' : 'u'; // "u" used on front not to change theme /** @var UsersItem $object */ $object = $this->Application->recallObject($prefix_special, null, Array('skip_autoload' => true)); return $object; } /** * Checks, if given user fields matches at least one of defined ban rules * * @param kDBItem $object * @return bool */ function checkBanRules(&$object) { $table = $this->Application->getUnitOption('ban-rule', 'TableName'); if (!$this->Conn->TableFound($table)) { // when ban table not found -> assume user is ok by default return true; } $sql = 'SELECT * FROM ' . $table . ' WHERE ItemType = 6 AND Status = ' . STATUS_ACTIVE . ' ORDER BY Priority DESC'; $rules = $this->Conn->Query($sql); $found = false; foreach ($rules as $rule) { $field = $rule['ItemField']; $this_value = mb_strtolower( $object->GetDBField($field) ); $test_value = mb_strtolower( $rule['ItemValue'] ); switch ( $rule['ItemVerb'] ) { case 1: // is if ($this_value == $test_value) { $found = true; } break; case 2: // is not if ($this_value != $test_value) { $found = true; } break; case 3: // contains if ( strstr($this_value, $test_value) ) { $found = true; } break; case 4: // not contains if ( !strstr($this_value, $test_value) ) { $found = true; } break; case 7: // exists if ( strlen($this_value) > 0 ) { $found = true; } break; case 8: // unique if ( $this->_checkValueExist($field, $this_value) ) { $found = true; } break; } if ( $found ) { // check ban rules, until one of them matches if ( $rule['RuleType'] ) { // invert rule type $found = false; } break; } } return !$found; } /** * Checks if value is unique in Users table against the specified field * * @param string $field * @param string $value * @return string */ function _checkValueExist($field, $value) { $sql = 'SELECT * FROM ' . $this->Application->getUnitOption('u', 'TableName') . ' WHERE '. $field .' = ' . $this->Conn->qstr($value); return $this->Conn->GetOne($sql); } public function validateUserCode($user_code, $code_type, $expiration_timeout = null) { $expiration_timeouts = Array ( 'forgot_password' => 'config:Users_AllowReset', 'activation' => 'config:UserEmailActivationTimeout', 'verify_email' => 'config:Users_AllowReset', 'custom' => '', ); if ( !$user_code ) { return 'code_is_not_valid'; } $sql = 'SELECT PwRequestTime, PortalUserId FROM ' . TABLE_PREFIX . 'Users WHERE PwResetConfirm = ' . $this->Conn->qstr( trim($user_code) ); $user_info = $this->Conn->GetRow($sql); if ( $user_info === false ) { return 'code_is_not_valid'; } $expiration_timeout = isset($expiration_timeout) ? $expiration_timeout : $expiration_timeouts[$code_type]; if ( preg_match('/^config:(.*)$/', $expiration_timeout, $regs) ) { $expiration_timeout = $this->Application->ConfigValue( $regs[1] ); } if ( $expiration_timeout && $user_info['PwRequestTime'] < strtotime('-' . $expiration_timeout . ' minutes') ) { return 'code_expired'; } return $user_info['PortalUserId']; } /** * Restores user's email, returns error label, if error occurred * * @param string $hash * @return string * @access public */ public function restoreEmail($hash) { if ( !preg_match('/^[a-f0-9]{32}$/', $hash) ) { return 'invalid_hash'; } $sql = 'SELECT PortalUserId, PrevEmails FROM ' . TABLE_PREFIX . 'Users WHERE PrevEmails LIKE ' . $this->Conn->qstr('%' . $hash . '%'); $user_info = $this->Conn->GetRow($sql); if ( $user_info === false ) { return 'invalid_hash'; } $prev_emails = $user_info['PrevEmails']; $prev_emails = $prev_emails ? unserialize($prev_emails) : Array (); if ( !isset($prev_emails[$hash]) ) { return 'invalid_hash'; } $email_to_restore = $prev_emails[$hash]; unset($prev_emails[$hash]); /** @var UsersItem $object */ $object = $this->Application->recallObject('u.email-restore', null, Array ('skip_autoload' => true)); $object->Load($user_info['PortalUserId']); $object->SetDBField('PrevEmails', serialize($prev_emails)); $object->SetDBField('Email', $email_to_restore); $object->SetDBField('EmailVerified', 1); return $object->Update() ? '' : 'restore_impossible'; } + + /** + * Returns user's primary group. + * + * @param integer $user_id User ID. + * + * @return integer + */ + public function getPrimaryGroup($user_id) + { + if ( $user_id <= 0 ) { + return $this->Application->ConfigValue('User_LoggedInGroup'); + } + + $cache_key = 'user' . $user_id . '_primary_group[%UIDSerial:' . $user_id . '%]'; + $cache_value = $this->Application->getCache($cache_key); + + if ( $cache_value === false ) { + $sql = 'SELECT PrimaryGroupId + FROM ' . TABLE_PREFIX . 'Users + WHERE PortalUserId = ' . $user_id; + $cache_value = $this->Conn->GetOne($sql); + $this->Application->setCache($cache_key, $cache_value); + } + + return $cache_value; + } + }