<?php namespace bff\modules\users; use Dev; use func; use User; use Model as BaseModel; /** * @property Base|\UsersBase $controller */ class Model extends BaseModel implements Consts { /** @var array СпиÑок шифруемых полей в таблице static::TABLE_USERS */ public $cryptUsers = []; /** * СпиÑок полей данных о пользователе запрашиваемых Ð´Ð»Ñ ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð² ÑеÑÑии * @var array * обÑзательные: id, member, admin, login, email, password, password_salt, name, avatar, sex */ protected $userSessionDataKeys = [ 'user_id as id', 'member', 'login', 'password', 'password_salt', 'email', 'name', 'phone', 'avatar', 'sex', 'activated', 'blocked', 'blocked_reason', 'admin', 'last_login', ]; /** * User model * @param int|null $id * @param array $columns * @param array $with * @return \bff\modules\users\models\User | \modules\users\models\User | \bff\db\illuminate\Model | array */ public function user($id = null, $columns = ['*'], array $with = []) { $model = $this->model('User'); if (! empty($id)) { return $model->one($id, $columns, $with); } return $model; } /** * Формируем ÑпиÑок пользователей * @param array $filter фильтр ÑпиÑка * @param array $dataKeys ÑпиÑок требуемых полей * @param bool $countOnly только подÑчет кол-ва * @param string $limit * @param string $orderBy * @return mixed */ public function usersList(array $filter, array $dataKeys = [], $countOnly = false, $limit = '', $orderBy = '') { if (! empty($this->cryptUsers)) { foreach ($filter as $k => $v) { if (in_array($k, $this->cryptUsers)) { unset($filter[$k]); $filter[':' . $k] = ['BFF_DECRYPT(' . $this->wrapColumn($k) . ') = :' . $k, ':' . $k => $v]; } } } $filter = $this->prepareFilter($filter); if ($countOnly) { if (trim($filter['where']) === '') { return $this->db->select_rows_count(static::TABLE_USERS); } return $this->db->one_data('SELECT COUNT(user_id) FROM ' . static::TABLE_USERS . ' INNER JOIN ' . static::TABLE_USERS_STAT . ' USING (user_id) ' . $filter['where'], $filter['bind']); } if (empty($dataKeys)) { $dataKeys = ['*']; $assocKey = 'user_id'; if (! empty($this->cryptUsers)) { foreach ($this->cryptUsers as $k => $v) { $dataKeys[] = 'BFF_DECRYPT(' . $this->wrapColumn($k) . ') as ' . $this->wrapColumn($k); } } } else { if (! empty($this->cryptUsers)) { foreach ($dataKeys as $k => $v) { if (in_array($v, $this->cryptUsers)) { $dataKeys[$k] = 'BFF_DECRYPT(' . $this->wrapColumn($v) . ') as ' . $this->wrapColumn($v); } } } $assocKey = (in_array('user_id', $dataKeys) ? 'user_id' : false); } if (is_numeric($limit)) { $limit = $this->db->prepareLimit(0, $limit); } return $this->db->select_key('SELECT ' . join(',', $this->wrapColumn($dataKeys)) . ' FROM ' . static::TABLE_USERS . ' INNER JOIN ' . static::TABLE_USERS_STAT . ' USING (user_id) ' . $filter['where'] . ' GROUP BY user_id ' . (!empty($orderBy) ? ' ORDER BY ' . $orderBy : '') . $limit, $assocKey, $filter['bind']); } /** * Получаем данные о пользователе по фильтру * @param array|int $filter фильтр * @param mixed $dataKeys ключи необходимых данных * @param array $bind * @return array|mixed */ public function userDataByFilter($filter, $dataKeys = '*', $bind = []) { if (!is_array($filter)) { $filter = ['user_id' => $filter]; } if (! empty($this->cryptUsers)) { foreach ($filter as $k => $v) { if (in_array($k, $this->cryptUsers)) { unset($filter[$k]); $filter[':' . $k] = ['BFF_DECRYPT(' . $this->wrapColumn($k) . ') = :' . $k, ':' . $k => $v]; } } } $filter = $this->prepareFilter($filter); if (empty($dataKeys)) { return []; } else { if ($dataKeys == '*') { $dataKeys = ['*']; if (! empty($this->cryptUsers)) { foreach ($this->cryptUsers as $k => $v) { $dataKeys[] = 'BFF_DECRYPT(' . $this->wrapColumn($k) . ') as ' . $this->wrapColumn($k); } } } else { if (!is_array($dataKeys)) { $dataKeys = [$dataKeys]; } if (! empty($this->cryptUsers)) { foreach ($dataKeys as $k => $v) { if (is_string($k)) { if (in_array($k, $this->cryptUsers)) { $dataKeys[$k] = 'BFF_DECRYPT(' . $this->wrapColumn($k) . ') as ' . $this->wrapColumn($v); } } else { if (in_array($v, $this->cryptUsers)) { $dataKeys[$k] = 'BFF_DECRYPT(' . $this->wrapColumn($v) . ') as ' . $this->wrapColumn($v); } } } } } } if (! empty($bind)) { $filter['bind'] = array_merge($bind, $filter['bind']); } $data = $this->db->one_array('SELECT ' . join(',', $this->wrapColumn($dataKeys)) . ' FROM ' . static::TABLE_USERS . ' INNER JOIN ' . static::TABLE_USERS_STAT . ' USING (user_id) ' . $filter['where'] . ' LIMIT 1', $filter['bind']); if (isset($data['contacts']) && method_exists($this->controller, 'contactsToArray')) { $data['contacts'] = $this->controller->contactsToArray($data['contacts']); } return $data; } /** * Получаем данные о пользователе Ð´Ð»Ñ ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð² ÑеÑÑии * @param array|int $filter фильтр * @param bool $withGroups получать информацию о группах, в которых ÑоÑтоит пользователь * @return mixed */ public function userSessionData($filter, $withGroups = false) { $data = $this->userDataByFilter($filter, $this->userSessionDataKeys); if (! empty($data) && $withGroups) { $groups = $this->userGroups($data['id'], true); if (empty($groups) && $data['member'] == 1) { $groups = [static::GROUPID_MEMBER => static::GROUPID_MEMBER]; } $data['groups'] = $groups; } return $data; } /** * Получаем password-hash Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¿Ð¾ логину|email|ID Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ * @param mixed $login login|email|user_id Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ * @param string $byField поле Ð´Ð»Ñ Ð¿ÐµÑ€Ð²Ð¾Ð³Ð¾ параметра - login, email, user_id * @return mixed */ public function userPassword($login, $byField = 'login') { $data = $this->userDataByFilter([$byField => $login], ['password']); return $data['password'] ?? ''; } /** * Получаем текущий Ð±Ð°Ð»Ð°Ð½Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ * @param int $userID ID Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ * @return float */ public function userBalance($userID) { $data = $this->userDataByFilter($userID, ['balance']); return floatval($data['balance'] ?? 0); } /** * ОбновлÑем Ñчетчик Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ * @param mixed $userID * @param string $key * @param int $amount * @param bool $incrementDecrement true +/-, false - set new amount * @return bool */ public function userCounterSave($userID, string $key, int $amount, bool $incrementDecrement = true) { if ($amount < 0 && $incrementDecrement) { $update = [$key . ' = (CASE WHEN ' . $key . ' >= ' . abs($amount) . ' THEN ' . $key . ' + (' . $amount . ') ELSE ' . $key . ' END)']; } else { $update = [$key . ' = ' . ($incrementDecrement ? $key . ' + (' . $amount . ')' : $amount)]; } return $this->userSave($userID, false, $update); } /** * СохранÑем данные Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ * @param int|array $userID ID Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ * @param array|bool $data данные * @param array $dataStat динамичеÑкие данные * @param array $bind доп. параметры запроÑÑ‹ Ð´Ð»Ñ bind'a * @return bool */ public function userSave($userID, $data, $dataStat = [], array $bind = []) { $res = true; $conditions = ['user_id' => $userID]; if (! empty($data)) { $res = $this->db->update(static::TABLE_USERS, $data, $conditions, $bind, $this->cryptUsers); $res = !empty($res); } if (! empty($dataStat)) { $this->db->update(static::TABLE_USERS_STAT, $dataStat, $conditions, $bind, $this->cryptUsers); } $this->app->hook('users.user.save', $userID, ['data' => &$data, 'dataStat' => &$dataStat]); return $res; } /** * Создаем Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ * @param array $data данные Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ * @param array $groupsId ID групп, в которые входит пользователь * @return int ID Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ */ public function userCreate($data, array $groupsId = []) { # login is required $data['login'] = $data['login'] ?? $this->userLoginGenerate(); $groupsId = $groupsId ?: [static::GROUPID_MEMBER]; $data['member'] = (in_array(static::GROUPID_MEMBER, $groupsId) ? 1 : 0); $data['admin'] = ($this->group()->where(['group_id' => $groupsId, 'adminpanel' => 1])->exists() ? 1 : 0); $data['user_id_ex'] = func::generator(6); $data['created'] = $this->now(); $data['created_ip'] = $data['created_ip'] ?? $this->request->remoteAddress(true); if (isset($data['contacts']) && is_array($data['contacts'])) { $data['contacts'] = json_encode($data['contacts']); } if (isset($data['phones']) && is_array($data['phones'])) { $data['phones'] = serialize($data['phones']); } $id = $this->db->insert(static::TABLE_USERS, $data, 'user_id', [], $this->cryptUsers); if ($id > 0) { if ($groupsId) { $this->userToGroups($id, $groupsId, false); } $this->db->insert(static::TABLE_USERS_STAT, ['user_id' => $id], false, [], $this->cryptUsers); } else { $id = 0; } if ($id > 0) { $this->app->hook('users.user.create', $id, ['data' => &$data, 'groups' => $groupsId]); } return $id; } /** * ПроверÑем наличие Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¿Ð¾ логину * @param string $login логин * @param int $exceptUserID ID пользователÑ, которого Ñледует иÑключить из проверки * @param array $reservedLogins ÑпиÑок зарезервированных логинов * @return bool */ public function userLoginExists($login, $exceptUserID = 0, array $reservedLogins = []) { if (! empty($reservedLogins) && in_array($login, $reservedLogins, true)) { return true; } $filter = ['login' => $login]; $bind = []; if (! empty($exceptUserID)) { $filter[':userIdExcept'] = 'user_id != :userId '; $bind[':userId'] = $exceptUserID; } $res = $this->userDataByFilter($filter, 'user_id', $bind); return (!empty($res)); } /** * Генерируем уникальный логин (Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¾Ð¹ на наличие) * @param string $prefix префикÑ, по-умолчанию "u" * @param bool $increment * @return string */ public function userLoginGenerate($prefix = 'u', $increment = false) { if ($increment) { $done = false; $i = 1; do { $login = $prefix . ($i > 1 ? $i : ''); $i++; $res = $this->db->one_data('SELECT user_id FROM ' . static::TABLE_USERS . ' WHERE login = :login', [ ':login' => $login, ]); if (empty($res)) { $done = true; } else { # не нашли за 5 итераций, генерируем упрощенным вариантом if ($i >= 5) { $login = $this->userLoginGenerate($prefix, false); $done = true; } } } while (!$done); } else { $done = false; do { $login = $prefix . mt_rand(123456789, 987654321); $res = $this->db->one_data('SELECT user_id FROM ' . static::TABLE_USERS . ' WHERE login = :login', [ ':login' => $login, ]); if (empty($res)) { $done = true; } } while (!$done); } return $login; } /** * ПроверÑем наличие Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¿Ð¾ email-адреÑу * @param string $email email Ð°Ð´Ñ€ÐµÑ * @param int $exceptUserID ID пользователÑ, которого Ñледует иÑключить из проверки * @return bool */ public function userEmailExists($email, $exceptUserID = 0) { $res = $this->userDataByEmail($email, 'user_id', $exceptUserID); return !empty($res); } /** * Данные Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¿Ð¾ email-адреÑу * @param string $email email Ð°Ð´Ñ€ÐµÑ * @param string|array $dataKeys ключи необходимых данных * @param int $exceptUserID ID пользователÑ, которого Ñледует иÑключить из поиÑка * @return array|mixed данные Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð»Ð¸ false */ public function userDataByEmail($email, $dataKeys = '*', $exceptUserID = 0) { $bind = []; $filter = []; if ($this->userEmailCrypted()) { $filter[':emailCheck'] = ['BFF_DECRYPT(email) = :email', ':email' => $email]; } else { $filter['email'] = $email; } if (! empty($exceptUserID)) { $filter[':userIdExcept'] = 'user_id != :userId'; $bind[':userId'] = $exceptUserID; } return $this->userDataByFilter($filter, $dataKeys, $bind); } /** * ИÑпользуетÑÑ Ð»Ð¸ шифрование Ð¿Ð¾Ð»Ñ email в таблице static::TABLE_USERS * @return bool */ public function userEmailCrypted() { return (!empty($this->cryptUsers) && in_array('email', $this->cryptUsers)); } /** * ПроверÑем наличие Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¿Ð¾ номеру телефона * @param string $phoneNumber номер телефона * @param int $exceptUserID ID пользователÑ, которого Ñледует иÑключить из проверки * @param string $fieldName Ð¸Ð¼Ñ Ð¿Ð¾Ð»Ñ Ñ‚ÐµÐ»ÐµÑ„Ð¾Ð½Ð° в БД * @return bool */ public function userPhoneExists($phoneNumber, $exceptUserID = 0, $fieldName = 'phone_number') { $filter = []; if (! empty($this->cryptUsers) && in_array($fieldName, $this->cryptUsers)) { $filter[':phone'] = ['BFF_DECRYPT(' . $fieldName . ') = :phone', ':phone' => $phoneNumber]; } else { $filter[$fieldName] = $phoneNumber; } $bind = []; if (! empty($exceptUserID)) { $filter[':userIdExcept'] = 'user_id != :userId '; $bind[':userId'] = $exceptUserID; } $res = $this->userDataByFilter($filter, 'user_id', $bind); return (!empty($res)); } // --------------------------------------------------------------------------- // Группы пользователей /** * Включаем Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð² группу, по keyword'у группы * @param int $userID ID Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ * @param string $groupKeyword keyword группы * @param bool $outgoinFromGroups открепить Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¾Ñ‚ закрепленных за ним групп * @return bool */ public function userToGroup($userID, $groupKeyword, $outgoinFromGroups = true) { # получаем ID группы по $groupKeyword $groupID = (int)$this->db->one_data( 'SELECT group_id FROM ' . static::TABLE_USERS_GROUPS . ' WHERE keyword = :keyword LIMIT 1', [':keyword' => $groupKeyword] ); if (! $groupID) { return false; } return $this->userToGroups($userID, $groupID, $outgoinFromGroups); } /** * Включаем Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð² группы * @param int $userID ID Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ * @param array|int $groupID ID группы (неÑкольких групп) * @param bool $outgoinFromGroups иÑключить Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð· текущих групп перед включением в новые * @return bool */ public function userToGroups($userID, $groupID, $outgoinFromGroups = true) { if (! is_array($groupID)) { $groupID = [$groupID]; } # откреплÑем Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¾Ñ‚ закрепленных за ним групп if ($outgoinFromGroups) { $this->userFromGroups($userID, null); } else { # иÑключаем группы в которых пользователь уже ÑоÑтоит $groupsCurrent = $this->userGroups($userID); if (! empty($groupsCurrent)) { foreach ($groupsCurrent as $v) { $exists = array_search($v['group_id'], $groupID); if ($exists !== false) { unset($groupID[$exists]); } } } } $groupsIn = []; $now = $this->now(); foreach ($groupID as $id) { $id = intval($id); if ($id <= 0) { continue; } $groupsIn[] = [ 'user_id' => $userID, 'group_id' => $id, 'created' => $now, ]; } if (! empty($groupsIn)) { return $this->db->multiInsert(static::TABLE_USER_IN_GROUPS, $groupsIn); } return false; } /** * ИÑключаем Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð· групп * @param int $userID ID Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ * @param array|int|null $groupID ID групп или null(иÑключаем из вÑех групп) * @param bool $keywords true - указаны keyword'Ñ‹ групп * @return mixed */ public function userFromGroups($userID, $groupID = null, $keywords = false) { if (! empty($groupID)) { if (! is_array($groupID)) { $groupID = [$groupID]; } # иÑключаем Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð· указанных групп if ($keywords) { $groupID = $this->db->select_one_column('SELECT group_id FROM ' . static::TABLE_USERS_GROUPS . ' WHERE ' . $this->db->prepareIN('keyword', $groupID, false, false, false)); if (empty($groupID)) { return 0; } } return $this->db->delete(static::TABLE_USER_IN_GROUPS, ['user_id' => $userID, 'group_id' => $groupID]); } else { # иÑключаем Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð· вÑех групп return $this->db->delete(static::TABLE_USER_IN_GROUPS, ['user_id' => $userID]); } } /** * ИÑключаем неÑкольких пользователей из групп * @param array|int $usersID ID пользователей * @param array|null $groupID * @param bool $keywords true - указаны keyword'Ñ‹ групп * @return mixed */ public function usersFromGroups($usersID, $groupID = null, $keywords = false) { if (is_array($usersID)) { if (isset($groupID)) { if (!is_array($groupID)) { $groupID = [$groupID]; } # иÑключаем указанных пользователей из указанных групп if ($keywords) { $groupID = $this->db->select_one_column('SELECT group_id FROM ' . static::TABLE_USERS_GROUPS . ' WHERE ' . $this->db->prepareIN('keyword', $groupID, false, false, false)); if (empty($groupID)) { return 0; } } return $this->db->delete(static::TABLE_USER_IN_GROUPS, ['user_id' => $usersID, 'group_id' => $groupID]); } else { # иÑключаем указанных пользователей из вÑех групп в которые они входÑÑ‚ return $this->db->delete(static::TABLE_USER_IN_GROUPS, ['user_id' => $usersID]); } } # иÑключаем одного Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð· групп return $this->userFromGroups($usersID, $groupID); } /** * ЯвлÑетÑÑ Ð»Ð¸ пользователь Ñупер-админиÑтратором * @param int $userID ID Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ * @return bool */ public function userIsSuperAdmin($userID) { return ((bool)$this->db->one_data('SELECT UIG.group_id FROM ' . static::TABLE_USER_IN_GROUPS . ' UIG WHERE UIG.user_id = :user_id AND UIG.group_id = :group_id LIMIT 1', [':user_id' => $userID, ':group_id' => static::GROUPID_SUPERADMIN])); } /** * ЯвлÑетÑÑ Ð»Ð¸ пользователь админиÑтратором (входит ли хотÑ-бы в одну группу Ñ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð½Ñ‹Ð¼ доÑтупом в админ. панель) * @param int $userID ID Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ * @return bool */ public function userIsAdministrator($userID) { return ((bool)$this->db->one_data('SELECT UIG.group_id FROM ' . static::TABLE_USER_IN_GROUPS . ' UIG, ' . static::TABLE_USERS_GROUPS . ' G WHERE UIG.user_id = :user_id AND UIG.group_id = G.group_id AND G.adminpanel = 1 LIMIT 1', [':user_id' => $userID])); } /** * User group model * @param int|null $id * @param array $columns * @param array $with * @return \bff\modules\users\models\Group | \modules\users\models\Group | bff\db\illuminate\Model | array */ public function group($id = null, $columns = ['*'], array $with = []) { $model = $this->model('Group'); if (! empty($id)) { return $model->one($id, $columns, $with); } return $model; } /** * Получаем ÑпиÑок групп пользователей * @param array|string|null $exceptGroup иÑключить группы Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð½Ñ‹Ð¼Ð¸ keyword/id * @param bool $withoutAdminpanelAccess только группы без доÑтупа в админ-панель * @return mixed */ public function groups($exceptGroup = null, $withoutAdminpanelAccess = false) { $filter = []; if (! empty($exceptGroup)) { if (!is_array($exceptGroup)) { $exceptGroup = [$exceptGroup]; } $exceptGroupID = []; foreach ($exceptGroup as $k => $group) { if (is_int($group)) { $exceptGroupID[] = $group; unset($exceptGroup[$k]); } } if (! empty($exceptGroupID)) { $filter[] = $this->db->prepareIN('G.group_id', $exceptGroupID, true, false, true); } if (! empty($exceptGroup)) { $filter[] = $this->db->prepareIN('G.keyword', $exceptGroup, true, false, false); } } if ($withoutAdminpanelAccess) { $filter[] = 'G.adminpanel = 0'; } $data = $this->db->select('SELECT G.* FROM ' . static::TABLE_USERS_GROUPS . ' G ' . (!empty($filter) ? ' WHERE ' . join(' AND ', $filter) : '') . ' ORDER BY G.group_id'); if (empty($data)) { return []; } foreach ($data as &$v) { $v['title'] = json_decode($v['title'], true); if (! is_array($v['title'])) { $v['title'] = []; } } unset($v); return $data; } /** * Получаем ÑпиÑок групп Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ * @param int $userID ID Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ * @param bool $onlyKeywords только keyword'Ñ‹ групп * @param array $opts * @return array */ public function userGroups($userID, $onlyKeywords = false, array $opts = []) { $opts = $this->defaults($opts, [ 'lang' => $this->locale->current(), ]); $data = $this->db->select('SELECT G.* FROM ' . static::TABLE_USERS_GROUPS . ' G, ' . static::TABLE_USER_IN_GROUPS . ' UIG WHERE UIG.user_id = :id AND UIG.group_id = G.group_id ORDER BY G.group_id ASC', [':id' => $userID]); if (empty($data)) { return []; } if ($onlyKeywords) { $keywords = []; foreach ($data as $group) { $keywords[intval($group['group_id'])] = $group['keyword']; } return $keywords; } foreach ($data as & $v) { $v['title'] = json_decode($v['title'], true); $v['title'] = $v['title'][$opts['lang']] ?? ''; } unset($v); return $data; } /** * Получаем группы, в которых еÑÑ‚ÑŒ пользователи * @param bool $withAdminpanelAccess Ñ Ð´Ð¾Ñтупом в админ-панель * @param string $transparentByKey ключ, по которому выполнÑем группировку результата * @return array|mixed */ public function usersGroups($withAdminpanelAccess = false, $transparentByKey = 'user_id') { $lang = $this->locale->current(); $data = $this->db->select('SELECT G.*, U.user_id FROM ' . static::TABLE_USERS . ' U, ' . static::TABLE_USER_IN_GROUPS . ' UIG, ' . static::TABLE_USERS_GROUPS . ' G WHERE ' . ($withAdminpanelAccess ? 'G.adminpanel=1 AND ' : '') . ' UIG.group_id = G.group_id AND U.user_id = UIG.user_id ORDER BY G.group_id'); foreach ($data as & $v) { $v['title'] = json_decode($v['title'], true); $v['title'] = $v['title'][$lang] ?? ''; } unset($v); return (!empty($transparentByKey) ? func::array_transparent($data, $transparentByKey, false) : $data); } /** * ПроверÑем наличие группы по ключу * @param string $groupKeyword ключ группы * @param int $exceptGroupID ID группы, которую Ñледует иÑключить из проверки * @return bool */ public function groupKeywordExists($groupKeyword, $exceptGroupID = 0) { if (empty($groupKeyword)) { return true; } $bind = [':keyword' => $groupKeyword]; if (! empty($exceptGroupID)) { $bind[':gid'] = $exceptGroupID; } return ((int)$this->db->one_data('SELECT group_id FROM ' . static::TABLE_USERS_GROUPS . ' WHERE keyword = :keyword' . (!empty($exceptGroupID) ? ' AND group_id != :gid' : '') . ' LIMIT 1', $bind) > 0); } /** * Создаем/обновлÑем группу * @param int $groupID ID группы или 0 * @param array $data данные * @return bool|int */ public function groupSave($groupID, array $data) { $data['modified'] = $this->now(); if (is_array($data['title'])) { $data['title'] = json_encode($data['title'], JSON_UNESCAPED_UNICODE); } if ($groupID) { return $this->db->update(static::TABLE_USERS_GROUPS, $data, ['group_id' => $groupID]); } $data['created'] = $this->now(); return $this->db->insert(static::TABLE_USERS_GROUPS, $data); } /** * Получаем данные о группе * @param int $groupID ID группы * @return array|mixed */ public function groupData($groupID) { $data = $this->db->one_array( 'SELECT * FROM ' . static::TABLE_USERS_GROUPS . ' WHERE group_id = :id LIMIT 1', [ ':id' => $groupID, ] ); if (! empty($data)) { $data['title'] = json_decode($data['title'], true); if (! is_array($data['title'])) { $data['title'] = []; } } return $data; } /** * УдалÑем группу (иÑключаем пользователей ÑоÑтоÑщих в ней) * @param int $groupID ID группы * @return bool */ public function groupDelete($groupID): bool { $res = $this->db->delete(static::TABLE_USERS_GROUPS, ['group_id' => $groupID]); if (! empty($res)) { $this->db->delete(static::TABLE_USERS_GROUPS_ACCESS, ['group_id' => $groupID]); $this->db->delete(static::TABLE_USER_IN_GROUPS, ['group_id' => $groupID]); return true; } return false; } /** * GroupAccess model * @param int|null $id * @param array $columns * @param array $with * @return \bff\modules\users\models\GroupAccess | \bff\db\illuminate\Model | array */ public function groupAccess($id = null, $columns = ['*'], array $with = []) { $model = $this->model('GroupAccess'); if (! empty($id)) { return $model->one($id, $columns, $with); } return $model; } /** * Groups permissions to check * @param int $userID * @param array|null $groupsID * @return array */ public function userGroupsPermissions($userID, ?array $groupsID = null) { if (empty($userID)) { return []; } if (is_null($groupsID)) { $groupsID = $this->db->select_rows_column(static::TABLE_USER_IN_GROUPS, 'group_id', [ 'user_id' => $userID, ]); } if (empty($groupsID)) { return []; } $access = $this->groupAccess() ->where(['group_id' => $groupsID]) ->get() ->toArray(); if (empty($access)) { return []; } $result = []; foreach ($access as $v) { $result[ $v['module'] ][] = $v['scope']; } return $result; } /** * СохранÑем права доÑтупа группы * @param int $groupID ID группы * @param array $permissions права доÑтупа * @return void */ public function groupAccessSave($groupID, $permissions) { $scope = $this->modulesScopesListing(); $access = $this->groupAccess() ->where('group_id', $groupID) ->get() ->toArray(); foreach ($permissions as $v) { if (empty($v['module']) || empty($v['scope'])) { continue; } foreach ($scope as $vv) { if ($vv['module'] != $v['module']) { continue; } if ($vv['scope'] != $v['scope']) { continue; } $exist = false; foreach ($access as $vvv) { if ($vvv['module'] != $v['module']) { continue; } if ($vvv['scope'] != $v['scope']) { continue; } $exist = true; break; } if ($exist) { if (empty($v['allow'])) { $this->groupAccess() ->where([ 'group_id' => $groupID, 'module' => $v['module'], 'scope' => $v['scope'], ]) ->delete() ; } } else { if (! empty($v['allow'])) { $this->groupAccess() ->fill([ 'group_id' => $groupID, 'module' => $v['module'], 'scope' => $v['scope'], ]) ->save(); } } break; } } } public function modulesScopesListing(array $filter = []) { $result = []; $modules = $this->app->getModulesList(); # Init all modules + plugins (init all scopes) foreach ($modules as $k => $v) { $this->app->module($k); } $plugins = Dev::getPluginsList(); $i = 1; $append = function ($pid, $scopes) use (&$result, &$i) { $pid['parent'] = 0; $p = false; foreach ($scopes as $v) { if (empty($v['id']) || mb_strpos($v['id'], ':') !== false) { continue; } if ($p === false) { $result[$i] = $pid; $p = $i; $i++; } $result[$i++] = [ 'module' => $pid['module'], 'scope' => $v['id'], 'title' => $v['title'] ?? '', 'parent' => $p, ]; } }; foreach ($modules as $k => $v) { $module = $this->app->module($k); if (! $module) { continue; } $scopes = $module->accessScopes()->listing(); if (empty($scopes)) { continue; } $title = $module->module_title ?? ''; if (empty($title)) { $title = $k; } $append([ 'module' => $k, 'scope' => $k, 'title' => $title, ], $scopes); } foreach ($plugins as $k => $v) { /** @var \Plugin $v*/ if (! $v || ! method_exists($v, 'accessScopes')) { continue; } $scopes = $v->accessScopes()->listing(); if (empty($scopes)) { continue; } $title = $v->getTitle(); if (empty($title)) { $title = $k; } $append([ 'module' => $k, 'scope' => $k, 'title' => $title, ], $scopes); } return $result; } /** * Ban model * @param int|null $id * @param array $columns * @param array $with * @return \bff\modules\users\models\Ban | \bff\db\illuminate\Model | array */ public function ban($id = null, $columns = ['*'], array $with = []) { $model = $this->model('Ban'); if (! empty($id)) { return $model->one($id, $columns, $with); } return $model; } /** * Создание бан-правила * @param string $mode Режим бана: user, ip, email * @param array|string $ban Бан-items * @param int $banPeriod Период бана или 0 - поÑтоÑнный * @param string $banPeriodDate Период бана (дата, до которой банить) * @param int $exclude ИÑключение * @param string $description ОпиÑание бана * @param string $reason Причина бана (Ð¿Ð¾ÐºÐ°Ð·Ñ‹Ð²Ð°ÐµÐ¼Ð°Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»ÑŽ) * @return bool */ public function banCreate($mode, $ban, $banPeriod, $banPeriodDate, $exclude = 0, $description = '', $reason = '') { # УдалÑем проÑроченные баны $this->db->delete(static::TABLE_USERS_BANLIST, ['finished<' . time(), 'finished <> 0']); $ban = (!is_array($ban)) ? array_unique(explode(PHP_EOL, $ban)) : $ban; $currentTime = time(); # Переводим $banEnd в unixtime. 0 - поÑтоÑнный бан. if ($banPeriod) { if ($banPeriod != -1 || !$banPeriodDate) { $banEnd = max($currentTime, $currentTime + ($banPeriod) * 60); } else { $banPeriodDate = explode('-', $banPeriodDate); if ( sizeof($banPeriodDate) == 3 && ((int)$banPeriodDate[2] < 9999) && (strlen($banPeriodDate[2]) == 4) && (strlen($banPeriodDate[1]) == 2) && (strlen($banPeriodDate[0]) == 2) ) { $banEnd = max($currentTime, gmmktime(0, 0, 0, (int)$banPeriodDate[1], (int)$banPeriodDate[0], (int)$banPeriodDate[2])); } else { $this->errors->set(_t('users', 'The date must be in the format <kbd>DD-MM-YYYY</kbd>.')); return false; } } } else { $banEnd = 0; } $banlistResult = []; switch ($mode) { case 'user': { $type = 'uid'; $userLogins = []; foreach ($ban as $login) { $login = trim($login); if ($login !== '') { if ($login === User::login()) { $this->errors->set(_t('users', 'You cannot close access to yourself.')); } $userLogins[] = $login; } } if (! sizeof($userLogins)) { $this->errors->set(_t('users', 'Username not defined.')); return false; } $result = $this->db->select_one_column('SELECT id FROM ' . static::TABLE_USERS . ' WHERE ' . $this->db->prepareIN('login', $userLogins, false, false) . ' AND id <> :userId', [':userId' => User::id()]); if (! empty($result)) { foreach ($result as $uid) { $banlistResult[] = (int)$uid; } } else { $this->errors->set(_t('users', 'The requested users do not exist.')); } unset($result); } break; case 'ip': { $type = 'ip'; foreach ($ban as $banItem) { if (preg_match('#^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})[ ]*\-[ ]*([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$#', trim($banItem), $ip_range_explode)) { // This is an IP range $ip_1_counter = $ip_range_explode[1]; $ip_1_end = $ip_range_explode[5]; while ($ip_1_counter <= $ip_1_end) { $ip_2_counter = ($ip_1_counter == $ip_range_explode[1]) ? $ip_range_explode[2] : 0; $ip_2_end = ($ip_1_counter < $ip_1_end) ? 254 : $ip_range_explode[6]; if ($ip_2_counter == 0 && $ip_2_end == 254) { $ip_2_counter = 256; $ip_2_fragment = 256; $banlistResult[] = "$ip_1_counter.*"; } while ($ip_2_counter <= $ip_2_end) { $ip_3_counter = ($ip_2_counter == $ip_range_explode[2] && $ip_1_counter == $ip_range_explode[1]) ? $ip_range_explode[3] : 0; $ip_3_end = ($ip_2_counter < $ip_2_end || $ip_1_counter < $ip_1_end) ? 254 : $ip_range_explode[7]; if ($ip_3_counter == 0 && $ip_3_end == 254) { $ip_3_counter = 256; $ip_3_fragment = 256; $banlistResult[] = "$ip_1_counter.$ip_2_counter.*"; } while ($ip_3_counter <= $ip_3_end) { $ip_4_counter = ($ip_3_counter == $ip_range_explode[3] && $ip_2_counter == $ip_range_explode[2] && $ip_1_counter == $ip_range_explode[1]) ? $ip_range_explode[4] : 0; $ip_4_end = ($ip_3_counter < $ip_3_end || $ip_2_counter < $ip_2_end) ? 254 : $ip_range_explode[8]; if ($ip_4_counter == 0 && $ip_4_end == 254) { $ip_4_counter = 256; $ip_4_fragment = 256; $banlistResult[] = "$ip_1_counter.$ip_2_counter.$ip_3_counter.*"; } while ($ip_4_counter <= $ip_4_end) { $banlistResult[] = "$ip_1_counter.$ip_2_counter.$ip_3_counter.$ip_4_counter"; $ip_4_counter++; } $ip_3_counter++; } $ip_2_counter++; } $ip_1_counter++; } } else { if (preg_match('#^([0-9]{1,3})\.([0-9\*]{1,3})\.([0-9\*]{1,3})\.([0-9\*]{1,3})$#', trim($banItem)) || preg_match('#^[a-f0-9:]+\*?$#i', trim($banItem))) { # Ðормальный IP Ð°Ð´Ñ€ÐµÑ $banlistResult[] = trim($banItem); } else { if (preg_match('#^\*$#', trim($banItem))) { # Баним вÑе IP адреÑа $banlistResult[] = '*'; } else { if (preg_match('#^([\w\-_]\.?){2,}$#is', trim($banItem))) { # Ð˜Ð¼Ñ Ñ…Ð¾Ñта $ip_ary = gethostbynamel(trim($banItem)); if (! empty($ip_ary)) { foreach ($ip_ary as $ip) { if ($ip) { if (mb_strlen($ip) > 40) { continue; } $banlistResult[] = $ip; } } } $this->errors->set(_t('users', 'Could not determine the IP address of the specified host')); } else { $this->errors->set(_t('users', 'No IP address or hostname specified')); } } } } } } break; case 'email': { $type = 'email'; foreach ($ban as $email) { $email = trim($email); if (preg_match('#^.*?@*|(([a-z0-9\-]+\.)+([a-z]{2,3}))$#i', $email)) { if (strlen($email) > 100) { continue; } $banlistResult[] = $email; } } if (sizeof($ban) == 0) { $this->errors->set(_t('users', 'No valid email addresses found.')); return false; } } break; default: { $this->errors->set(_t('users', 'Mode not specified.')); return false; } } # Fetch currently set bans of the specified type and exclude state. Prevent duplicate bans. $result = $this->db->select_one_column("SELECT $type FROM " . static::TABLE_USERS_BANLIST . ' WHERE ' . ($type === 'uid' ? 'uid <> 0' : "$type <> ''") . ' AND exclude = ' . (int)$exclude); if (! empty($result)) { $banlistResultTemp = []; foreach ($result as $item) { $banlistResultTemp[] = $item; } $banlistResult = array_unique(array_diff($banlistResult, $banlistResultTemp)); unset($banlistResultTemp); } unset($result); # ЕÑÑ‚ÑŒ что банить if (sizeof($banlistResult)) { $exclude = (int)$exclude; $banEnd = (int)$banEnd; $description = (string)$description; $reason = (string)$reason; $insert = []; foreach ($banlistResult as $banItem) { $insert[] = [ $type => $banItem, 'started' => $currentTime, 'finished' => $banEnd, 'exclude' => $exclude, 'description' => $description, 'reason' => $reason, ]; } $this->db->multiInsert(static::TABLE_USERS_BANLIST, $insert); return true; } return false; } /** * УдалÑем правило бана * @param int|array $banID ID правила(правил) * @return void */ public function banDelete($banID) { # удалÑем проÑроченные баны $this->db->delete(static::TABLE_USERS_BANLIST, ['finished<' . time(), 'finished <> 0']); if (! is_array($banID)) { $banID = [$banID]; } $banID = array_map('intval', $banID); if (sizeof($banID)) { $this->db->delete(static::TABLE_USERS_BANLIST, ['id' => $banID]); } } /** * СпиÑок правил бана * @return mixed */ public function banListing() { return $this->db->select(' SELECT B.* FROM ' . static::TABLE_USERS_BANLIST . ' B WHERE (B.finished >= ' . time() . ' OR B.finished = 0) ORDER BY B.ip, B.email'); } }