social()->auth($this->input->get('provider', TYPE_NOTAGS)); } /** * Авторизация на фронтенде из админ-панели * @return \bff\http\Response */ public function loginAdmin() { $userID = $this->input->get('uid', TYPE_UINT); do { if (! $this->security->validateReferer()) { break; } if (! $userID || $this->model->userIsAdministrator($userID)) { break; } $userData = $this->model->userData($userID, ['last_login','email']); if (empty($userData)) { break; } $encrypt = $this->input->get('encrypted', TYPE_STR); if (empty($encrypt)) { break; } if ($this->adminAuthURL($userID, $userData['last_login'], $userData['email'], $encrypt) !== true) { break; } if ($this->authById($userID) !== true) { break; } return $this->redirect()->route('index'); } while (false); return $this->errors->error404(); } /** * Выход * @return \bff\http\Response */ public function logout() { $redirect = Url::route('index'); if (! User::logined()) { return $this->redirect($redirect); } # оставляем пользователя на текущей странице, # за исключением следующих: $wrongReferers = [ '/user/login', '/user/register', '/cabinet/', '/item/edit', '/item/success', '/item/activate', ]; $referer = Request::referer(); if (! empty($referer)) { foreach ($wrongReferers as $v) { if (strpos($referer, $v) !== false) { $referer = null; break; } } if (! empty($referer)) { $redirect = $referer; } } if ($this->security->validateReferer($referer)) { Auth::logout(); } $this->app->hook('users.user.logout', User::id(), $redirect); return $this->redirect($redirect); } /** * My account Layout * @param string $content * @param string $title * @return mixed|string */ public function accountLayout(string $content, string $title = '') { $data = [ 'content' => $content, 'title' => $title, 'menu' => Site::menu('user.account'), 'guest' => User::guest(), ]; SEO::robotsIndex(false); $this->app->setMeta(_t('users', 'My Account')); return $this->template('account/layout', $data); } /** * Кабинет: Настройки профиля пользователя * @route users-my.settings * @return string|mixed */ public function my_settings() { if (User::guest()) { $loginMessage = _t('users', 'To access your profile you need to Log In'); if ($this->isPOST()) { $this->errors->set($loginMessage); return $this->ajaxResponseForm(); } return $this->showInlineMessage($loginMessage, [ 'auth' => true, ]); } $userID = User::id(); $companyId = User::companyID(); $publisher = Listings::publisher(); # доступность настроек: # true - доступна, false - скрыта $on_contacts = true; # контактные данные $on_email = $this->config('users.settings.email.change', true, TYPE_BOOL); # смена email-адреса $on_destroy = $this->config('users.settings.destroy', 'none', TYPE_STR); # удаление аккаунта $on_phone = $this->registerPhone([static::REGISTER_TYPE_PHONE, static::REGISTER_TYPE_BOTH]); # Способ регистрации пользователя # скрываем настройки контактов пользователя при включенной обязательной компании (не в статусе заявки) if ($companyId && ($publisher == Listings::PUBLISHER_COMPANY || $publisher == Listings::PUBLISHER_USER_TO_COMPANY)) { if (Business::premoderation()) { $companyData = Business::model()->companyData($companyId, ['status']); if (!empty($companyData['status']) && $companyData['status'] != Business::STATUS_REQUEST) { $on_contacts = false; } } else { $on_contacts = false; } } if ($this->isPOST()) { $action = $this->input->getpost('act', TYPE_NOTAGS); if (!$this->isRequestValid() && $action != 'avatar-upload') { $this->errors->reloadPage(); return $this->ajaxResponseForm(); } $response = []; switch ($action) { case 'contacts': # контактные данные $data = $this->input->postm([ 'name' => [TYPE_NOTAGS, 'len' => 50, 'len.sys' => 'users.contacts.name.limit'], # Контактное лицо 'firstname' => [TYPE_NOTAGS, 'len' => 50, 'len.sys' => 'users.contacts.firstname.limit'], # Имя 'surname' => [TYPE_NOTAGS, 'len' => 50, 'len.sys' => 'users.contacts.surname.limit'], # Фамилия ]); if ($on_contacts) { $data = $this->input->postm([ 'geo_city' => TYPE_UINT, # город или 0 'addr_addr' => [TYPE_NOTAGS, 'len' => 400, 'len.sys' => 'users.contacts.addr.limit'], # адрес 'addr_lat' => TYPE_NUM, # адрес, координата LAT 'addr_lon' => TYPE_NUM, # адрес, координата LON 'phones' => TYPE_ARRAY_NOTAGS, # телефоны 'contacts' => TYPE_ARRAY_NOTAGS, # контакты ], $data); } $this->cleanUserData($data); $this->model->userSave($userID, $data); $response['name'] = $data['name']; break; case 'avatar-upload': # аватар: загрузка $avatar = $this->avatar($userID); if (!$this->isRequestValid()) { $this->errors->reloadPage(); $result = false; } else { $result = $avatar->uploadQQ(true, true); } $response = [ 'success' => ($result !== false && $this->errors->no()), 'errors' => $this->errors->get(), ]; if ($result !== false) { $sex = User::data('sex'); $response = array_merge($response, $result); foreach (array_keys($avatar->getSizes()) as $size) { $response[$size] = $avatar::url($userID, $result['filename'], $size, $sex); } } return $this->ajaxResponse($response, true); case 'avatar-delete': # аватар: удаление $avatar = $this->avatar($userID); $deleted = $avatar->delete(true); if ($deleted) { $sex = User::data('sex'); foreach (array_keys($avatar->getSizes()) as $size) { $response[$size] = $avatar::url(0, false, $size, $sex); } } break; case 'social-unlink': # соц. сети: отвязывание $social = $this->social(); $providerKey = $this->input->post('provider', TYPE_STR); $providerID = $social->getProviderID($providerKey); if ($providerID) { $success = $social->unlinkSocialAccountFromUser($providerID, $userID); if (! $success) { $this->errors->reloadPage(); } } break; case 'pass': # смена пароля $passwordCurrent = $this->input->post('pass0', TYPE_NOTRIM); # текущий пароль $passwordNew = $this->input->post('pass1', TYPE_NOTRIM); # новый пароль if (!User::isCurrentPassword($passwordCurrent)) { $this->errors->set(_t('users', 'Current password is incorrect'), 'pass0'); break; } if (empty($passwordNew)) { $this->errors->set(_t('users', 'Enter new password'), 'pass1'); } elseif (mb_strlen($passwordNew) < $this->passwordMinLength) { $this->errors->set(_t('users', 'New password must not be shorter than [symbols] characters', [ 'symbols' => $this->passwordMinLength, ]), 'pass1'); } elseif ($passwordCurrent === $passwordNew) { $this->errors->set(_t('users', 'New password must not be the same as the current one'), 'pass1'); } # запрещаем редактирование пароля администраторам if ($this->model->userIsAdministrator($userID)) { $this->errors->reloadPage(); break; } if (! $this->errors->no()) { break; } $passwordNewHash = $this->security->passwordHash($passwordNew, User::data('password_salt')); $res = $this->model->userSave($userID, ['password' => $passwordNewHash]); if (empty($res)) { $this->errors->reloadPage(); } break; case 'phone': # смена номера телефона if (! in_array($on_phone, [static::REGISTER_TYPE_PHONE, static::REGISTER_TYPE_BOTH])) { $this->errors->reloadPage(); break; } $phone = $this->input->post('phone', TYPE_NOTAGS, [ # новый номер телефона 'len' => 30, 'len.sys' => 'users.contacts.phone.limit', ]); $code = $this->input->post('code', TYPE_NOTAGS); # код активации из sms $step = $this->input->post('step', TYPE_NOTAGS); # этап if (! $this->input->isPhoneNumber($phone)) { $this->errors->set(_t('users', 'Incorrect phone number'), 'phone'); break; } if ($this->model->userPhoneExists($phone, $userID)) { $this->errors->set(_t('users', 'A user with this phone number is already registered'), 'phone'); break; } if ($step == 'code-send') { $res = $this->mySettingsPhoneValidate($userID, $phone); if (! $res) { $this->errors->reloadPage(); break; } } elseif ($step == 'finish') { $userData = $this->model->userData($userID, ['activate_key']); if (empty($userData['activate_key'])) { $this->errors->reloadPage(); break; } if ($userData['activate_key'] != $code && mb_strtolower($userData['activate_key']) !== mb_strtolower(md5($phone . $code))) { $this->errors->set(_t('users', 'Incorrect verification code'), 'code'); break; } $success = $this->model->userSave($userID, [ 'phone_number' => $phone, 'phone_number_verified' => 1, 'activate_key' => '', ]); if ($success) { $response['phone'] = '+' . $phone; } else { $this->errors->reloadPage(); } } break; case 'phone-approve': # подтверждение телефона $user = $this->model->userData($userID, ['phone_number']); if (empty($user['phone_number']) || ! $this->input->isPhoneNumber($user['phone_number'])) { $this->errors->set(_t('users', 'Incorrect phone number'), 'phone'); break; } $res = $this->mySettingsPhoneValidate($userID, $user['phone_number']); if (! $res) { $this->errors->reloadPage(); break; } break; case 'email': # смена email if (!$on_email) { $this->errors->reloadPage(); break; } $email = $this->input->post('email', [TYPE_NOTAGS, 'len' => 100]); # новый email $pass = $this->input->post('pass', TYPE_NOTRIM); # текущий пароль $user = $this->model->userData($userID, ['email']); if (! empty($user['email']) && ! User::isCurrentPassword($pass)) { $this->errors->set(_t('users', 'Current password is incorrect'), 'pass'); break; } if (! $this->input->isEmail($email)) { $this->errors->set(_t('users', 'Incorrect email'), 'email'); break; } if ($this->isEmailTemporary($email)) { $this->errors->set(_t('', 'The email address you provided is in the list of forbidden ones, use for example @gmail.com'), 'email'); break; } if ($this->model->userEmailExists($email)) { $this->errors->set(_t('users', 'A user with this email address is already registered'), 'email'); break; } $res = $this->sendEmailChangeConfirmation($userID, $email); if (! empty($res)) { $response['msg'] = _t('users', 'An email has been sent to your email. Follow the instructions in the email.'); } else { $this->errors->reloadPage(); } break; case 'email-approve': # подтверждение e-mail $user = $this->model->userData($userID, ['email']); if (empty($user['email']) || ! $this->input->isEmail($user['email'])) { $this->errors->set(_t('users', 'Incorrect email'), 'email'); break; } $res = $this->sendEmailChangeConfirmation($userID, $user['email']); if (! empty($res)) { $response['msg'] = _t('users', 'An email has been sent to your email. Follow the instructions in the email.'); } else { $this->errors->reloadPage(); } break; case 'destroy': # удаление аккаунта if ($on_destroy == 'none') { $this->errors->reloadPage(); break; } $pass = $this->input->post('pass', TYPE_NOTRIM); if (! User::isCurrentPassword($pass)) { $this->errors->set(_t('users', 'Current password is incorrect'), 'pass'); break; } $user = $this->model->userData($userID, ['admin','login','email','admin_comment']); if (! empty($user['admin'])) { $this->errors->impossible(); return false; } switch ($on_destroy) { case 'delete': # полное удаление $saved = $this->model->userSave($userID, [ 'deleted' => static::DESTROY_BY_OWNER, 'blocked' => 1, 'blocked_reason' => _t('users', 'The account will be deleted within 24 hours.'), ]); if ($saved) { $this->app->cronManager()->executeOnce('users', 'cronDeleteUsers'); } break; case 'block': # блокировка # Триггер блокировки/разблокировки аккаунта BlockedEvent::dispatch($userID); bff()->callModules('onUserBlocked', [$userID, true]); $comments = $user['admin_comment']; if (! empty($comments)) { $comments .= "\n"; } $comments .= _t('users', 'The user deleted his account and was marked as blocked, the source email is [email], the login is [login]', [ 'email' => $user['email'], 'login' => $user['login'], ]); $save = [ 'blocked' => 1, 'blocked_reason' => _t('users', 'The account was deleted'), 'email' => 'deleted' . func::generator(15) . '@' . SITEHOST, 'login' => 'd' . mt_rand(123456789, 987654321), 'admin_comment' => $comments, ]; $this->model->userSave($userID, $save); break; } if ($this->errors->no()) { Auth::logout(); $response['redirect'] = Site::url('index'); $response['message_success'] = _t('users', 'Your profile will be deleted within 24 hours'); } break; case 'enotify': # уведомления $channels = Sendmail::channels(); $update = []; foreach ($channels as $k => $v) { $field = 'enotify' . ($k == Sendmail::CHANNEL_EMAIL ? '' : '_' . $k); $enotify = array_sum($this->input->post($field, TYPE_ARRAY_UINT)); $update[$field] = $enotify; } if (! empty($update)) { $res = $this->model->userSave($userID, $update); if (empty($res)) { $this->errors->reloadPage(); } } break; } return $this->ajaxResponseForm($response); } $fields = [ 'user_id as id','email','email_verified', 'phones','phone_number','phone_number_verified', 'name','firstname','surname','contacts','avatar','sex','admin', 'addr_addr','addr_lat','addr_lon','geo_city', ]; $channels = Sendmail::channels(); if (! $on_phone) { unset($channels[Sendmail::CHANNEL_SMS]); } foreach ($channels as $k => $v) { $fields[] = 'enotify' . ($k == Sendmail::CHANNEL_EMAIL ? '' : '_' . $k); } $data = $this->model->userData($userID, $fields, true); if (empty($data)) { # ошибка получения данных о пользователе $this->log('Не удалось получить данные о пользователе #' . $userID . ' [users::my_settings]'); Auth::logout(); return $this->redirect('/'); } $avatar = $this->avatar($userID); $data['avatar_normal'] = $avatar::url($userID, $data['avatar'], null, $data['sex']); $data['avatar_maxsize'] = $avatar->getMaxSize(); # координаты по умолчанию Geo::mapDefaultCoordsCorrect($data['addr_lat'], $data['addr_lon']); # данные о привязанных соц. аккаунтах $social = $this->social(); $socialProviders = $social->getProvidersEnabled(); $socialUser = $social->getUserSocialAccountsData($userID); foreach ($socialUser as $k => $v) { if (isset($socialProviders[$k]) && strpos($v['profile_data'], 'a:') === 0) { $socialProviders[$k]['user'] = func::unserialize($v['profile_data']); } } $data['social'] = $socialProviders; # настройки уведомлений $types = []; foreach ($channels as $k => &$v) { $v['field'] = 'enotify' . ($k == Sendmail::CHANNEL_EMAIL ? '' : '_' . $k); $data[ $v['field'] ] = $this->getEnotifyTypes($data[ $v['field'] ], false, $k); foreach ($data[ $v['field'] ] as $kk => $vv) { if (isset($types[$kk])) { continue; } $types[$kk] = ['title' => $vv['title']]; } } unset($v); $data['channels'] = $channels; $data['enotify_types'] = $types; $data['sms_premium'] = $this->config('listings.items.sms.notify', 0, TYPE_UINT) == 2 && bff::servicesEnabled('listings'); if ($data['admin']) { $on_destroy = 'none'; } $data['on'] = [ 'contacts' => $on_contacts, 'phone' => $on_phone, 'email' => $on_email, 'destroy' => $on_destroy != 'none', ]; return $this->template('my.settings', $data); } /** * Отправка sms-уведомления для подтверждения номера телефона * @param int $userID * @param string $phone номер телефона * @return bool */ protected function mySettingsPhoneValidate($userID, string $phone) { $activationData = $this->getActivationInfo(); $res = $this->sms()->sendActivationCode($phone, $activationData['key']); if ($res) { $activationData['key'] = md5($phone . $activationData['key']); return $this->updateActivationKey($userID, $activationData['key']); } return false; } /** * Перенаправление в кабинет пользователя * @return \bff\http\Response */ public function redirectToAccountSettings() { return $this->redirect($this->url('account.settings')); } }