<?php

use modules\users\events\Registered as RegisteredEvent;

class UsersAdmin_ extends bff\modules\users\Admin
{
    protected $cache = [];

    /**
     * Access scopes
     */
    public function accessScopes()
    {
        return parent::accessScopes()
            ->scope('profile', _t('@users', 'Your Profile'))
                ->method('profile')
            ->scope('members-listing', _t('@users', 'Users List'))
                ->method('listing')
                ->method('ajax')->action('user-info')
            ->scope('admins-listing', _t('@users', 'Admins List'))
                ->method('listing_moderators')
            ->scope('users-edit', _t('@users', 'Users Management'))
                ->method('listing')
                    ->action('mass', function () {
                        return in_array($this->input->post('action'), [
                            'block', 'unblock', 'unfake', 'delete', 'del_empty',
                        ], true);
                    })
                ->methods(['user_add', 'user_edit', 'user_action'])
                ->method('ajax')->actions([
                    'user-block', 'user-activate', 'user-admin-comment', 'auth-url',
                ])
            ->scope('admins-edit', _t('@users', 'Admins Management'))
            ->publicMethod(['login','logout'])
            ->seoTemplates()
        ;
    }

    /**
     * Профиль пользователя
     * @return string|mixed
     */
    public function profile()
    {
        if (! $this->haveAccessTo('profile')) {
            return $this->showAccessDenied();
        }

        if (User::guest()) {
            return $this->adminRedirect(Errors::IMPOSSIBLE, 'login');
        }

        $userID = User::id();

        if ($this->isPOST()) {
            $changePassword = $this->input->post('changepass', TYPE_BOOL);

            $data = $this->model->userData($userID, ['password', 'password_salt']);

            $update = [];
            if ($this->errors->no() && $changePassword) {
                $passwordCurrent = $this->input->post('password0', TYPE_NOTRIM); # текущий пароль
                $passwordNew     = $this->input->post('password1', TYPE_NOTRIM); # новый пароль
                $passwordRepeat  = $this->input->post('password2', TYPE_NOTRIM); # новый пароль (подтверждение)

                do {
                    if (!$passwordCurrent) {
                        $this->errors->set(_t('@users', 'Enter current password'), 'password0');
                        break;
                    }

                    if (! $this->security->passwordCheck($passwordCurrent, $data['password'], $data['password_salt'])) {
                        $this->errors->set(_t('@users', 'Current password is incorrect'), 'password0');
                        break;
                    }

                    if (empty($passwordNew) || mb_strlen($passwordNew) < $this->passwordMinLength) {
                        $this->errors->set(_t('@users', 'New password is too short'), 'password1');
                        break;
                    }
                    if ($passwordNew !== $passwordRepeat) {
                        $this->errors->set(_t('@users', 'Password confirmation failed'), 'password2');
                        break;
                    }
                    if ($this->errors->no()) {
                        $update['password'] = $this->security->passwordHash($passwordNew, $data['password_salt']);
                    }
                } while (false);
            }

            $msg = '';
            if ($this->errors->no('users.admin.profile.submit', ['id' => $userID, 'data' => &$update]) && !empty($update)) {
                $this->model->userSave($userID, $update);
                $msg = _t('', 'Saved successfully');
            }
            return $this->ajaxResponseForm(['msg' => $msg]);
        }

        $userGroups = $this->model->userGroups($userID);
        if (empty($userGroups)) {
            $userGroups = [];
        }

        $data = User::data(['name', 'login', 'avatar', 'email']);
        $data['user_id'] = $userID;
        $data['groups'] = $userGroups;

        return $this->template('admin/profile', $data);
    }

    /**
     * Выход из админ-панели
     * @return mixed
     */
    public function logout()
    {
        $redirectUrl = Admin::url('users/login');
        $this->app->hook('users.user.logout', User::id(), $redirectUrl);
        Auth::logout();

        return $this->redirect($redirectUrl ?: '/');
    }

    /**
     * Форма авторизации в адми-панель
     * @return mixed
     */
    public function login()
    {
        if (User::admin()) {
            return $this->adminRedirect(null, 'profile');
        }

        $login = '';
        if ($this->isPOST()) {
            $login = $this->input->post('login', TYPE_STR);
            if (!$login) {
                $this->errors->set(_t('@users', 'Enter login'));
            }

            $password = $this->input->post('password', TYPE_NOTRIM);
            if (!$password) {
                $this->errors->set(_t('@users', 'Enter password'));
            }

            if ($this->errors->no('users.admin.login.step1', ['id' => 0, 'login' => $login, 'password' => $password])) {
                $this->isLoginOrEmail($login, $isEmail);
                $data = $this->model->userSessionData([($isEmail ? 'email' : 'login') => $login]);
                if (! empty($data)) {
                    if (! $this->security->passwordCheck($password, $data['password'], $data['password_salt'])) {
                        $data = false;
                    }
                }

                if (! $data) {
                    $this->errors->set(_t('@users', 'Incorrect login or password'));
                    $userID = 0;
                } else {
                    $userID = $data['id'];

                    # аккаунт заблокирован
                    if ($data['blocked'] == 1) {
                        $this->errors->set(sprintf(_t('@users', 'Account suspended: %s'), $data['blocked_reason']));
                    } else {
                        # проверка IP в бан-листе
                        $isBlocked = $this->checkBan();
                        if ($isBlocked) {
                            $this->errors->set(_t('@users', 'Access is blocked due to:<br />[reason]', [
                                'reason' => $isBlocked,
                            ]));
                        } else {
                            # проверка доступа в админ-панель
                            if (! $this->model->userIsAdministrator($userID)) {
                                $this->errors->accessDenied();
                            }
                        }
                    }
                }

                if ($this->errors->no('users.admin.login.step2', ['id' => $userID,'data' => &$data])) {
                    if ($user = Auth::loginUsingId($userID)) {
                        $user->touchOnLogin();
                    }

                    $referer = $this->input->post('ref', TYPE_NOTAGS);
                    $refererAdmin = Admin::url();
                    if (
                        ! empty($referer) &&
                        mb_stripos($referer, $refererAdmin) === 0 &&
                        $this->security->validateReferer($referer)
                    ) {
                        return $this->redirect($referer);
                    }

                    return $this->redirect($refererAdmin);
                }
            }
        }

        return Response::html(
            View::template('login', [
                'login'  => $login,
                'errors' => $this->errors->get(true),
            ])
        );
    }

    /**
     * Список пользователей
     * @return string|mixed
     */
    public function listing()
    {
        if (! $this->haveAccessTo('members-listing')) {
            return $this->showAccessDenied();
        }

        $mass = [];
        $massActions = [
            'unblock'       => ['t' => _t('@users', 'Unblock'),           'access' => 'users-edit',     'tab' => [3]],
            'unfake'        => ['t' => _t('@users', 'Convert'),           'access' => 'users-edit',     'tab' => [5]],
            'block'         => ['t' => _t('@users', 'Block'),            'access' => 'users-edit',     'tab' => [1,2,5],  'params' => ['blocked_reason' => TYPE_STR]],
            'delete'        => ['t' => _t('@users', 'Delete'),                  'access' => 'users-edit',     'tab' => [2,3,5], ],
            'del_empty'     => ['t' => _t('@users', 'Delete empty accounts'),  'access' => 'users-edit',     'tab' => [5], ],
        ];
        foreach ($massActions as $k => $v) {
            if (! $this->haveAccessTo($v['access'])) {
                unset($massActions[$k]);
            }
        }


        $action = $this->input->get('act', TYPE_STR);
        if ($action) {
            $response = [];
            switch ($action) {
                case 'mass':
                    $action = $this->input->post('action', TYPE_STR);

                    if (! isset($massActions[$action])) {
                        $this->errors->accessDenied();
                        break;
                    }
                    $a = $massActions[$action];
                    if (isset($a['alias']) && isset($massActions[ $a['alias'] ])) {
                        $action = $a['alias'];
                        $a = $massActions[$action];
                    }

                    $mass['action'] = $action;

                    if (isset($a['params'])) {
                        $params = $this->input->postm($a['params']);
                        $mass = array_merge($mass, $params);
                    }
                    break;
            }

            if (empty($mass)) {
                return $this->ajaxResponseForm($response);
            }
        }


        $f = $this->input->postgetm([
            'status' => TYPE_INT, # статус
            'q'      => TYPE_NOTAGS, #  поиск по ID/login/email
            'region' => TYPE_UINT, # страна / регион / город
            'r_from' => TYPE_NOTAGS, # регистрация: от
            'r_to'   => TYPE_NOTAGS, # регистрация: до
            'a_from' => TYPE_NOTAGS, # авторизация: от
            'a_to'   => TYPE_NOTAGS, # авторизация: до
            'page'   => TYPE_UINT, # страница
        ]);

        $filter = [':notSuperAdmin' => 'user_id != 1', 'admin' => 0];
        if ($f['status'] > 0) {
            switch ($f['status']) {
                case 1:
                    $filter['activated'] = 1;
                    break;
                case 2:
                    $filter['activated'] = 0;
                    break;
                case 3:
                    $filter['blocked'] = 1;
                    break;
                case 4:
                    $filter[':subscribed'] = 'enotify & ' . static::ENOTIFY_NEWS;
                    $filter['blocked'] = 0;
                    break;
                case 5:
                    $filter['fake'] = 1;
                    $filter['blocked'] = 0;
                    break;
            }
        }

        if ($f['q'] != '') {
            $filter[':q'] = [
                '( user_id = :q_user_id OR login LIKE :q_login OR email LIKE :q_email OR phone_number LIKE :q_email )',
                ':q_user_id' => intval($f['q']),
                ':q_login'   => '%' . $f['q'] . '%',
                ':q_email'   => '%' . $f['q'] . '%',
            ];
        }
        if ($f['region'] > 0) {
            $regionData = Geo::regionData($f['region']);
            if (! empty($regionData['numlevel'])) {
                $filter['geo_region' . $regionData['numlevel']] = $f['region'];
            }
        }

        # период регистрации
        if (! empty($f['r_from'])) {
            $r_from = strtotime($f['r_from']);
            if (! empty($r_from)) {
                $filter[':regFrom'] = ['created >= :regFrom', ':regFrom' => date('Y-m-d', $r_from)];
            }
        }
        if (! empty($f['r_to'])) {
            $r_to = strtotime($f['r_to']);
            if (! empty($r_to)) {
                $filter[':regTo'] = ['created <= :regTo', ':regTo' => date('Y-m-d', $r_to)];
            }
        }

        # период последней авторизации
        if (! empty($f['a_from'])) {
            $a_from = strtotime($f['a_from']);
            if (!empty($a_from)) {
                $filter[':authFrom'] = ['last_login >= :authFrom', ':authFrom' => date('Y-m-d', $a_from)];
            }
        }
        if (! empty($f['a_to'])) {
            $a_to = strtotime($f['a_to']);
            if (!empty($a_to)) {
                $filter[':authTo'] = ['last_login <= :authTo', ':authTo' => date('Y-m-d', $a_to)];
            }
        }

        if (! empty($mass)) {
            $all = $this->input->post('all', TYPE_BOOL);
            $ids = $this->input->post('ids', TYPE_ARRAY_UINT);
            if (! $all) {
                $filter = ['user_id' => $ids];
            }
            $count = $this->model->usersList($filter, [], true);
            $mass['sql'] = $filter;
            if ($count > 100) {
                $mass['count'] = $count;
                $this->app->cronManager()->executeOnce('users', 'cronAdminMassAction', $mass, md5(json_encode($mass)));
                $response = [
                    'message' => _t('@users', 'The bulk operation is scheduled and will be completed within a few minutes.'),
                ];
            } else {
                $response = $this->cronAdminMassAction($mass);
            }

            return $this->ajaxResponseForm($response);
        }

        $ordersAllowed = ['user_id' => 'desc', 'email' => 'asc', 'last_login' => 'desc'];
        $data = $this->prepareOrder($orderBy, $orderDirection, 'user_id' . tpl::ORDER_SEPARATOR . 'desc', $ordersAllowed);

        if (! $f['page']) {
            $f['page'] = 1;
        }
        $data['f'] = $f;
        $data['statusNotActivated'] = ($f['status'] == 2);

        $data['filter'] = '&' . http_build_query($f);

        $total = $this->model->usersList($filter, [], true);
        $pages = new Pagination($total, 15, $this->adminLink("listing{$data['filter']}&order=$orderBy-$orderDirection&page=" . Pagination::PAGE_ID));
        $data['pgn'] = $pages->view();
        $data['total'] = $total;

        $data['users'] = $this->model->usersList(
            $filter,
            [
                'user_id','admin','name','login','email','phone_number',
                'company_id','last_login','blocked','activated','fake',
            ],
            false,
            $pages->getLimitOffset(),
            "$orderBy $orderDirection"
        );

        # данные о компаниях пользователей
        if ($data['business_on'] = bff::businessEnabled()) {
            $aUsersCompanies = Business::model()->companiesDataToUsersListing((!empty($data['users']) ? array_keys($data['users']) : []));
            foreach ($data['users'] as $k => &$v) {
                if (isset($aUsersCompanies[$k])) {
                    $v['company'] = $aUsersCompanies[$k];
                }
            }
            unset($v);
        }

        $data['list'] = $this->template('admin/listing.ajax', $data);
        if ($this->isAJAX()) {
            return $this->ajaxResponse([
               'list'   => $data['list'],
               'pgn'    => $data['pgn'],
               'total'  => $data['total'],
            ]);
        }

        $data['massActions'] = $massActions;

        return $this->template('admin/listing', $data);
    }

    /**
     * Кеширование данных о пользователе
     * @param integer $userID
     * @return array
     */
    public function userData($userID)
    {
        if (empty($userID)) {
            return [];
        }
        if (! isset($this->cache['user'][$userID])) {
            $this->cache['user'][$userID] = $this->model->userData($userID, '*');
        }
        return $this->cache['user'][$userID];
    }

    /**
     * Список модераторов
     * @return string
     */
    public function listing_moderators()
    {
        if (! $this->haveAccessTo('admins-listing')) {
            return $this->showAccessDenied();
        }

        $ordersAllowed = ['user_id' => 'desc', 'login' => 'asc', 'email' => 'asc'];
        $data = $this->prepareOrder($orderBy, $orderDirection, 'login' . tpl::ORDER_SEPARATOR . 'asc', $ordersAllowed);

        $filter = ['admin' => 1];
        $total = $this->model->usersList($filter, [], true);
        $pages = new Pagination($total, 20, $this->adminLink(__FUNCTION__ . "&order=$orderBy-$orderDirection&page=" . Pagination::PAGE_ID));
        $data['pgn'] = $pages->view();

        $data['users'] = $this->model->usersList(
            $filter,
            ['user_id', 'login', 'email', 'password', 'blocked', 'deleted', 'activated'],
            false,
            $pages->getLimitOffset(),
            "$orderBy $orderDirection"
        );

        # получаем все группы с доступом в админ-панель
        $usersGroups = $this->model->usersGroups(true, 'user_id');

        foreach ($data['users'] as &$v) {
            $v['groups'] = $usersGroups[$v['user_id']] ?? [];
        } unset($v);

        $data['page'] = $pages->getCurrentPage();

        return $this->template('admin/listing.moderators', $data);
    }

    /**
     * Форма добавления пользователя
     * @return string|mixed
     */
    public function user_add()
    {
        if (! $this->haveAccessTo('users-edit')) {
            return $this->showAccessDenied();
        }

        $data = [];
        $this->validateUserData($data, 0);

        if ($this->isPOST()) {
            $response = [];
            do {
                $data['password_salt'] = $this->security->generatePasswordSalt();
                $data['password'] = $this->security->passwordHash($data['password'], $data['password_salt']);
                $data['activated'] = 1;
                if (! empty($data['email'])) {
                    $data['email_verified'] = 1;
                }
                if (! empty($data['phone_number'])) {
                    $data['phone_number_verified'] = 1;
                }

                $groupsID = $data['group_id'];
                unset($data['group_id']);

                if (!$this->errors->no('users.admin.user.submit', ['id' => 0, 'data' => &$data, 'groups' => &$groupsID])) {
                    break;
                }

                $userID = $this->model->userCreate($data, $groupsID);
                if (! $userID) {
                    $this->errors->impossible();
                    break;
                }

                $update = [];

                # upload avatar
                $avatar = $this->avatar($userID)->uploadFILES('avatar', false, false);
                if ($avatar !== false && !empty($avatar['filename'])) {
                    $update['avatar'] = $avatar['filename'];
                }

                if (! empty($update)) {
                    $this->model->userSave($userID, $update);
                }

                RegisteredEvent::dispatch($userID, ['createdByAdmin' => User::id()]);

                $response['redirect'] = ($this->model->userIsAdministrator($userID) ?
                    'listing_moderators' :
                    'listing'
                );
            } while (false);

            return $this->iframeResponseForm($response);
        }

        $data = array_merge($data, ['password' => '', 'password2' => '', 'user_id' => '', 'avatar' => '']);
        $this->prepareGroupsOptions($data, [static::GROUPID_SUPERADMIN], [static::GROUPID_MEMBER]);

        $data['phones'] = []; # телефоны
        $data['user_id'] = 0;
        $data['business_on'] = bff::businessEnabled();
        $data['region_title'] = '';

        return $this->template('admin/user.form', $data);
    }

    /**
     * Форма редактирования пользователя
     * @return string|mixed
     */
    public function user_edit()
    {
        if (! $this->haveAccessTo('users-edit')) {
            return $this->showAccessDenied();
        }

        $userID = $this->input->get('rec', TYPE_UINT);
        if (! $userID) {
            return $this->adminRedirect(Errors::IMPOSSIBLE, 'listing');
        }

        if ($this->model->userIsAdministrator($userID) && ! $this->haveAccessTo('admins-edit')) {
            return $this->showAccessDenied();
        }

        $data = ['admin' => 0];

        # анализируем группы, в которые входит пользователь
        $isSuperAdmin = 0;
        $userGroups = $this->model->userGroups($userID);
        foreach ($userGroups as $v) {
            if ($v['group_id'] == static::GROUPID_SUPERADMIN) {
                $isSuperAdmin = 1;
            }
            if ($v['adminpanel'] == 1) {
                $data['admin'] = 1;
            }
        }

        if ($this->isPOST()) {
            $response = ['reload' => false, 'back' => false];
            $this->validateUserData($data, $userID);

            if (! $data['admin']) {
                # пропускаем настройки предназначенные только для администраторов
                unset($data['im_noreply']);
            }

            do {
                # основный данные
                $groupsID = $data['group_id'] ?? [];
                unset($data['group_id']);
                $data['member'] = in_array(static::GROUPID_MEMBER, $groupsID) ? 1 : 0;
                if (!$this->errors->no('users.admin.user.submit', ['id' => $userID,'data' => &$data,'groups' => &$groupsID])) {
                    break;
                }
                $this->model->userSave($userID, $data);

                $update = [];

                # аватар
                $avatar = $this->avatar($userID)->uploadFILES('avatar', true, false);
                if ($avatar !== false && !empty($avatar['filename'])) {
                    $update['avatar'] = $avatar['filename'];
                    $response['reload'] = true;
                } else {
                    if ($this->input->postget('avatar_del', TYPE_BOOL)) {
                        if ($this->avatar($userID)->delete(false)) {
                            $update['avatar'] = '';
                            $response['reload'] = true;
                        }
                    }
                }

                # связь с группами
                if ($isSuperAdmin && !in_array(static::GROUPID_SUPERADMIN, $groupsID)) {
                    $groupsID = array_merge($groupsID, [static::GROUPID_SUPERADMIN]);
                }
                $this->model->userToGroups($userID, $groupsID);

                # обновляем, является ли юзер администратором
                if ($this->errors->no()) {
                    $isAdmin = ($this->model->userIsAdministrator($userID) ? 1 : 0);
                    if ($data['admin'] != $isAdmin) {
                        $update['admin'] = $isAdmin;
                        if (! $isAdmin) {
                            $update['im_noreply'] = 0;
                        }
                    }
                }

                if (! empty($update)) {
                    $this->model->userSave($userID, $update);
                }

                if ($this->input->post('back', TYPE_BOOL)) {
                    $response['back'] = true;
                }
            } while (false);

            return $this->iframeResponseForm($response);
        }

        $userInfo = $this->model->userData($userID, '*', true);
        $data = HTML::escape(array_merge($userInfo, $data), 'html', ['name', 'site']);

        $activeGroupsID = [];
        for ($j = 0; $j < count($userGroups); $j++) {
            $activeGroupsID[] = $userGroups[$j]['group_id'];
        }
        $this->prepareGroupsOptions($data, ($isSuperAdmin ? null : static::GROUPID_SUPERADMIN), $activeGroupsID);

        # настройки компании
        $data['business_on'] = bff::businessEnabled();

        $data['superadmin'] = $isSuperAdmin;
        $data['social'] = $this->social()->getUserSocialAccountsData($userID, true);

        $data['admin_auth'] = $this->adminAuthBlock(array_merge($data, $userInfo));
        $data['permissionDelete'] = $this->haveAccessTo('users-delete');
        if (! empty($data['fake'])) {
            $data['unfake_url'] = Admin::url('users/user_action', ['type' => 'unfake', 'rec' => $userID]);
        }

        return $this->template('admin/user.form', $data);
    }

    /**
     * Обработчик действий в форме редактирования пользователей
     * @return \bff\http\Response
     */
    public function user_action()
    {
        if (! $this->haveAccessTo('users-edit')) {
            return $this->showAccessDenied();
        }

        $userID = $this->input->getpost('rec', TYPE_UINT);
        if (! $userID) {
            return $this->adminRedirect(Errors::IMPOSSIBLE);
        }

        if ($this->model->userIsSuperAdmin($userID)) {
            return $this->adminRedirect(Errors::ACCESSDENIED);
        }
        if ($this->model->userIsAdministrator($userID) && ! $this->haveAccessTo('admins-edit')) {
            return $this->adminRedirect(Errors::ACCESSDENIED);
        }

        switch ($this->input->get('type')) {
            case 'delete': # удаление пользователя
            {
                # завершение сессии
                $this->userSessionDestroy($userID, false); # frontend
                if (! User::isCurrent($userID)) {
                    $this->userSessionDestroy($userID, true); # admin panel
                }

                $saved = $this->model->userSave($userID, [
                    'deleted' => static::DESTROY_BY_ADMIN,
                    'blocked' => 1,
                    'blocked_reason' => _t('@users', 'User account will be deleted within 24 hours'),
                ]);
                if ($saved) {
                    $this->app->cronManager()->executeOnce('users', 'cronDeleteUsers');
                }

                return $this->adminRedirect(Errors::SUCCESS);
            }
            case 'logout': # завершение сессии
            {
                $this->userSessionDestroy($userID, false); # frontend
                if (! User::isCurrent($userID)) {
                    $this->userSessionDestroy($userID, true); # admin panel
                }

                return $this->adminRedirect(Errors::SUCCESS, "user_edit&rec=$userID");
            }
            case 'unfake': # удаление флага fake
            {
                $this->userUnfake($userID);

                return $this->adminRedirect(Errors::SUCCESS, "user_edit&rec=$userID");
            }
        }

        return $this->adminRedirect(Errors::SUCCESS);
    }

    /**
     * Обработчик ajax действий
     * @return \bff\http\Response
     */
    public function ajax()
    {
        $userID = $this->input->getpost('id', TYPE_UINT);
        if (! $userID) {
            return $this->ajaxResponse(Errors::UNKNOWNRECORD);
        }

        $action = $this->input->getpost('act', TYPE_STR);
        switch ($action) {
            case 'user-info': # popup
            {
                if (! $this->haveAccessTo('members-listing')) {
                    return $this->showAccessDenied(['popup' => true]);
                }

                $data = $this->model->userData($userID, '*');
                $data['business_on'] = bff::businessEnabled();
                if ($data['business_on'] && $data['company_id'] > 0) {
                    $data['company'] = Business::model()->companyData($data['company_id'], ['title', 'link']);
                }
                $data['region_title'] = Geo::regionTitle($data['geo_city']);
                $data['im_form'] = $this->app->moduleExists('internalmail');
                $data['social'] = $this->social()->getUserSocialAccountsData($userID, true);
                $data['admin_auth'] = $this->adminAuthBlock(array_merge($data, ['popup' => true]));
                return $this->adminModal($data, 'admin/user.info');
            }
            case 'user-block': # блокировка / разблокировка пользователя
            {
                if (! $this->haveAccessTo('users-edit')) {
                    return $this->ajaxResponse(Errors::ACCESSDENIED);
                }
                if (User::isCurrent($userID)) {
                    return $this->ajaxResponse(Errors::IMPOSSIBLE);
                }
                $opt = [
                    'blocked_reason' => $this->input->postget('blocked_reason', [TYPE_STR, 'len' => 1000]),
                    'blocked'        => $this->input->postget('blocked', TYPE_BOOL),
                ];
                if ($this->userBlock($userID, $opt)) {
                    return $this->ajaxResponse([
                       'reason' => nl2br($opt['blocked_reason']),
                       'blocked' => $opt['blocked'],
                    ]);
                }
            }
            break;
            case 'user-activate': # активация пользователя
            {
                do {
                    if (! $this->haveAccessTo('users-edit')) {
                        $this->errors->accessDenied();
                        break;
                    }
                    $data = $this->model->userData($userID, ['user_id', 'activated', 'blocked']);
                    if (empty($data)) {
                        $this->errors->impossible();
                        break;
                    } else {
                        if ($data['activated'] == 1) {
                            $this->errors->set(_t('@users', 'This account is already activated'));
                            break;
                        } else {
                            if ($data['blocked'] == 1) {
                                $this->errors->set(_t('@users', 'Unable to activate a suspended account'));
                                break;
                            }
                        }
                    }

                    if (! $this->userActivate($userID, ['verifyPhone' => true])) {
                        $this->errors->impossible();
                    }
                } while (false);

                return $this->ajaxResponseForm();
            }
            case 'user-admin-comment': # комментарий о пользователе
            {
                $response = ['comment' => ''];
                do {
                    if (! $this->haveAccessTo('users-edit')) {
                        $this->errors->accessDenied();
                        break;
                    }
                    $data = $this->model->userData($userID, ['user_id', 'admin_comment']);
                    if (empty($data)) {
                        $this->errors->impossible();
                        break;
                    } else {
                        $response['comment'] = $this->input->post('admin_comment', [TYPE_TEXT, 'len' => 5000]);
                        $this->model->userSave($userID, [
                            'admin_comment' => $response['comment'],
                        ]);
                    }
                } while (false);
                $response['comment'] = nl2br($response['comment']);
                return $this->ajaxResponseForm($response);
            }
            case 'auth-url':
            {
                if (! $this->haveAccessTo('users-edit')) {
                    return $this->showAccessDenied();
                }
                if ($this->model->userIsAdministrator($userID)) {
                    return $this->showAccessDenied();
                }
                $userInfo = $this->model->userData($userID, '*', true);
                return $this->ajaxResponseForm([
                    'url' => $this->adminAuthURL($userInfo['user_id'], $userInfo['last_login'], $userInfo['email'])
                ]);
            }
            default:
            {
                $this->app->hook('users.admin.ajax.default.action', $action, $this);
            }
        }

        return $this->ajaxResponse(Errors::IMPOSSIBLE);
    }

    /**
     * Формирование кнопки для авторизации на фронтенде
     * @param array $data
     * @return mixed|string
     */
    public function adminAuthBlock($data = [])
    {
        if (! empty($data['admin'])) {
            return '';
        }
        if (empty($data['user_id'])) {
            return '';
        }
        $getData = false;
        foreach (['last_login', 'email'] as $f) {
            if (empty($data[$f])) {
                $getData = true;
                break;
            }
        }
        if ($getData) {
            $user = $this->model->userData($data['user_id'], ['user_id', 'last_login', 'email']);
            if (empty($user['user_id'])) {
                return '';
            }
            $data = array_merge($user, $data);
        }
        $data['link'] = $this->adminAuthURL($data['user_id'], $data['last_login'], $data['email']);
        if (empty($data['link'])) {
            return '';
        }

        return $this->template('admin/admin.auth', $data);
    }

    /**
     * Валидация пользователя
     * @param array $data @ref данные
     * @param int $userID ID пользователя
     * @return void
     */
    protected function validateUserData(array &$data, $userID)
    {
        $params = [
            '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'],
            'email'     => TYPE_NOTAGS,
            'login'     => TYPE_NOTAGS,
            'geo_city'  => TYPE_UINT,
            'addr_addr' => [TYPE_NOTAGS, 'len' => 400, 'len.sys' => 'users.form.addr.limit'],
            'company_id'   => TYPE_UINT, # ID компании
            'sex'       => TYPE_UINT,
            'contacts'  => TYPE_ARRAY_NOTAGS,
            'phones'    => TYPE_ARRAY_NOTAGS,
            'site'      => TYPE_NOTAGS,
            'about'     => TYPE_NOTAGS,
            'group_id'  => TYPE_ARRAY_UINT,
        ];
        $channels = Sendmail::channels();
        if (! $this->registerPhone([static::REGISTER_TYPE_PHONE, static::REGISTER_TYPE_BOTH])) {
            unset($channels[Sendmail::CHANNEL_SMS]);
        }
        foreach ($channels as $k => $v) {
            $f = 'enotify' . ($k == Sendmail::CHANNEL_EMAIL ? '' : '_' . $k);
            $params[$f] = TYPE_ARRAY_UINT;
        }

        if ($userID) {
            $params['changepass'] = TYPE_BOOL;
            $params['password'] = TYPE_NOTRIM;
            $params['im_noreply'] = TYPE_BOOL;
        } else {
            $params['password'] = TYPE_NOTRIM;
            $params['password2'] = TYPE_NOTRIM;
        }

        if ($this->registerPhone([static::REGISTER_TYPE_PHONE, static::REGISTER_TYPE_BOTH])) {
            $params['phone_number'] = TYPE_NOTAGS;
        }

        if ($this->profileBirthdate) {
            $params['birthdate'] = TYPE_ARRAY_UINT;
        }

        $this->input->postm($params, $data);

        if (! $userID) {
            $data['admin'] = 0;
        }

        if ($this->isPOST()) {
            # номер телефона
            if ($this->registerPhone([static::REGISTER_TYPE_PHONE, static::REGISTER_TYPE_BOTH]) && (!empty($data['phone_number']) || !$userID)) {
                if (! $this->input->isPhoneNumber($data['phone_number'])) {
                    $this->errors->set(_t('@users', 'Incorrect phone number'), 'phone_number');
                }
            }
            if (! empty($data['phone_number'])) {
                if ($this->model->userPhoneExists($data['phone_number'], $userID)) {
                    $this->errors->set(_t('@users', 'User with specified phone number was already registered'), 'phone_number');
                }
            }

            # email (для авторизации)
            if ($this->registerPhone([static::REGISTER_TYPE_EMAIL, static::REGISTER_TYPE_BOTH]) && (!empty($data['email']) || !$userID)) {
                if (! $this->input->isEmail($data['email'])) {
                    $this->errors->set(_t('@users', 'Incorrect email'), 'email');
                }
            }
            if (! empty($data['email'])) {
                if ($this->model->userEmailExists($data['email'], $userID)) {
                    $this->errors->set(_t('@users', 'Email already exists'), 'email');
                }
            }

            # login
            if (! $this->isLoginCorrect($data['login'])) {
                $this->errors->set(_t('@users', 'Enter a correct login'), 'login');
            } elseif ($this->model->userLoginExists($data['login'], $userID)) {
                $this->errors->set(_t('@users', 'Login already exists'), 'login');
            }

            if ($userID) {
                if ($data['changepass']) {
                    if (mb_strlen($data['password']) < $this->passwordMinLength) {
                        $this->errors->set(_t('@users', 'New password is too short'), 'password');
                    } else {
                        $dataCurrent = $this->model->userData($userID, ['password_salt']);
                        $data['password'] = $this->security->passwordHash($data['password'], $dataCurrent['password_salt']);
                    }
                } else {
                    unset($data['password']);
                }
                unset($data['changepass']);
            } else {
                if (mb_strlen($data['password']) < $this->passwordMinLength) {
                    $this->errors->set(_t('@users', 'Specified password is too short'), 'password');
                } elseif ($data['password'] != $data['password2']) {
                    $this->errors->set(_t('@users', 'Password confirmation failed'), 'password');
                }
                unset($data['password2']);
            }

            # пол
            if (!in_array($data['sex'], [static::SEX_MALE, static::SEX_FEMALE])) {
                $data['sex'] = static::SEX_MALE;
            }

            $this->cleanUserData($data);
        }

        # уведомления
        foreach ($channels as $k => $v) {
            $f = 'enotify' . ($k == Sendmail::CHANNEL_EMAIL ? '' : '_' . $k);
            $data[$f]  = array_sum($data[$f]);
        }
    }

    /**
     * Подготавливаем options для списков групп
     * @param array $data @ref
     * @param mixed $exceptGroup
     * @param array $activeGroupsID ID активных групп (в которых состоит пользователь)
     * @return void
     */
    protected function prepareGroupsOptions(array &$data, $exceptGroup, $activeGroupsID = [])
    {
        $data['active_options'] = '';
        $data['exists_options'] = '';
        $groupsData = $this->model->groups($exceptGroup, false);
        $lang = $this->locale->current();

        foreach ($groupsData as $v) {
            $option = '<option value="' . $v['group_id'] . '" style="color:' . $v['color'] . ';">' . ($v['title'][$lang] ?? '') . '</option>';
            if (in_array($v['group_id'], $activeGroupsID)) {
                $data['active_options'] .= $option;
            } else {
                $data['exists_options'] .= $option;
            }
        }
    }
}