. * * @copyright (c) Visman * @license The MIT License (MIT) */ declare(strict_types=1); namespace ForkBB\Models\User; use ForkBB\Models\Action; use ForkBB\Models\User\User; class IsUniqueName extends Action { /** * Добавляет экранированный символ в конец каждого элемента $input дописывая их в $output */ protected function addSymbol(array $input, string $symbol, array $output = []): array { if ('#%' === $symbol) { $symbol = '%'; } else { $symbol = \str_replace(['#', '_', '%'], ['##', '#_', '#%'], $symbol); } if (empty($input)) { $input = ['']; } foreach ($input as $str) { $output[] = $str . $symbol; } return $output; } /** * Строит массив вариантов для сранения LIKE */ protected function variants(string $str): array { \preg_match_all('%.%us', $str, $matches); $result = []; foreach ($matches[0] as $i => $symbol) { $tmp = []; if (isset($symbol[1])) { if ($i > 1) { return $this->addSymbol($result, '#%'); // добавить % (без #) в конец каждого элемента } $symbolL = \mb_strtolower($symbol, 'UTF-8'); $symbolU = \mb_strtoupper($symbol, 'UTF-8'); if ($symbolL !== $symbol) { $tmp = $this->addSymbol($result, $symbolL, $tmp); } if ($symbolU !== $symbol) { $tmp = $this->addSymbol($result, $symbolU, $tmp); } } $result = $this->addSymbol($result, $symbol, $tmp); } return $result; } /** * Проверка на уникальность имени пользователя */ public function isUniqueName(User $user): bool { $name7bit = 0 === \preg_match('%[\x80-\xFF]%', $user->username); $norm7bit = 0 === \preg_match('%[\x80-\xFF]%', $user->username_normal); $like = 'LIKE'; switch ($this->c->DB->getType()) { // case 'mysql': // break; case 'pgsql': $like = 'ILIKE'; // case 'sqlite': default: // UTF-8 не нужен if ($name7bit && $norm7bit) { break; } // бд поддерживает UTF-8 сравнение без учета регистра if ($this->c->config->insensitive()) { break; } $vars = [(int) $user->id]; $query = 'SELECT u.username, u.username_normal FROM ::users AS u WHERE u.id!=?i AND ('; $sptr = ''; $arr = $this->variants($user->username); foreach ($arr as $value) { $vars[] = $value; $query .= "{$sptr}u.username {$like} ?s ESCAPE '#'"; $sptr = ' OR '; } $arr = $this->variants($user->username_normal); foreach ($arr as $value) { $vars[] = $value; $query .= "{$sptr}u.username_normal {$like} ?s ESCAPE '#'"; $sptr = ' OR '; } $query .= ')'; $nameL = \mb_strtolower($user->username, 'UTF-8'); $nameU = \mb_strtoupper($user->username, 'UTF-8'); $normL = \mb_strtolower($user->username_normal, 'UTF-8'); $normU = \mb_strtoupper($user->username_normal, 'UTF-8'); $stmt = $this->c->DB->query($query, $vars); while (false !== ($row = $stmt->fetch())) { if ( \mb_strtolower($row['username'], 'UTF-8') === $nameL || \mb_strtoupper($row['username'], 'UTF-8') === $nameU || \mb_strtolower($row['username_normal'], 'UTF-8') === $normL || \mb_strtoupper($row['username_normal'], 'UTF-8') === $normU ) { $stmt->closeCursor(); return false; } } return true; } $vars = [ ':id' => (int) $user->id, ':name' => \str_replace(['#', '_', '%'], ['##', '#_', '#%'], $user->username), ':norm' => \str_replace(['#', '_', '%'], ['##', '#_', '#%'], $user->username_normal), ]; $query = "SELECT 1 FROM ::users AS u WHERE u.id!=?i:id AND ( u.username {$like} ?s:name ESCAPE '#' OR u.username_normal {$like} ?s:norm ESCAPE '#' )"; $result = $this->c->DB->query($query, $vars)->fetchAll(); return ! \count($result); } }