diff --git a/app/Controllers/Routing.php b/app/Controllers/Routing.php index be60343c..35cc706b 100644 --- a/app/Controllers/Routing.php +++ b/app/Controllers/Routing.php @@ -45,7 +45,9 @@ class Routing // регистрация if ($config['o_regs_allow'] == '1') { - $r->add('GET', '/registration', 'Registration:reg', 'Registration'); //???? + $r->add('GET', '/registration', 'Rules:confirmation', 'Register'); + $r->add('POST', '/registration/agree', 'Register:reg', 'RegisterForm'); + $r->add('GET', '/registration/activate/{id:\d+}/{key}/{hash}', 'Register:activate', 'RegActivate'); } } else { // выход diff --git a/app/Core/Container.php b/app/Core/Container.php index d04e827b..08b4c1ef 100644 --- a/app/Core/Container.php +++ b/app/Core/Container.php @@ -1,6 +1,6 @@ false, 'default' => '\'\'' ), + 'email_confirmed' => array( + 'datatype' => 'TINYINT(1)', + 'allow_null' => false, + 'default' => '0' + ), 'title' => array( 'datatype' => 'VARCHAR(50)', 'allow_null' => true diff --git a/app/Core/Mail.php b/app/Core/Mail.php index b0b86851..6675de4e 100644 --- a/app/Core/Mail.php +++ b/app/Core/Mail.php @@ -2,6 +2,8 @@ namespace ForkBB\Core; +use RuntimeException; + class Mail { /** @@ -14,6 +16,68 @@ class Mail */ protected $language; + /** + * @var array + */ + protected $to = []; + + /** + * @var array + */ + protected $headers = []; + + /** + * @var string + */ + protected $message; + + /** + * @var array + */ + protected $smtp; + + /** + * @var string + */ + protected $EOL; + + /** + * @var Resource + */ + protected $connect; + + /** + * var int + */ + protected $auth = 0; + + /** + * Конструктор + * @param mixed $host + * @param mixed $user + * @param mixed $pass + * @param mixed $ssl + * @param mixed $eol + */ + public function __construct($host, $user, $pass, $ssl, $eol) + { + if (is_string($host) && strlen(trim($host)) > 0 ) { + list ($host, $port) = explode(':', $host); + if (empty($port) || $port < 1 || $port > 65535) { + $port = 25; + } + $this->smtp = [ + 'host' => ($ssl ? 'ssl://' : '') . $host, + 'port' => (int) $port, + 'user' => (string) $user, + 'pass' => (string) $pass, + ]; + $this->EOL = "\r\n"; + } else { + $this->EOL = in_array($eol, ["\r\n", "\n", "\r"]) ? $eol : PHP_EOL; + } + } + /** * Валидация email * @param mixed $email @@ -23,9 +87,152 @@ class Mail { return is_string($email) && strlen($email) <= 80 + && trim($email) === $email && preg_match('%^.+@.+$%D', $email); } + /** + * Сброс + * @return Mail + */ + public function reset() + { + $this->to = []; + $this->headers = []; + $this->message = null; + return $this; + } + + /** + * Задает тему письма + * @param string $subject + * @return Mail + */ + public function setSubject($subject) + { + $this->headers['Subject'] = $this->encodeText(preg_replace('%[\x00-\x1F]%', '', trim($subject))); + return $this; + } + + /** + * Добавляет заголовок To + * @param string|array $email + * @param string $name + * @return Mail + */ + public function addTo($email, $name = null) + { + if (is_array($email)) { + } else { + $email = preg_split('%[,\n\r]%', (string) $email, -1, PREG_SPLIT_NO_EMPTY); + } + foreach($email as $cur) { + $cur = trim((string) $cur); + if ($this->valid($cur)) { + $this->to[] = $this->formatAddress($cur, $name); + } + } + return $this; + } + + /** + * Задает заголовок To + * @param string|array $email + * @param string $name + * @return Mail + */ + public function setTo($email, $name = null) + { + $this->to = []; + return $this->addTo($email, $name); + } + + /** + * Задает заголовок From + * @param string $email + * @param string $name + * @return Mail + */ + public function setFrom($email, $name = null) + { + if ($this->valid($email)) { + $this->headers['From'] = $this->formatAddress($email, $name); + } + return $this; + } + + /** + * Задает заголовок Reply-To + * @param string $email + * @param string $name + * @return Mail + */ + public function setReplyTo($email, $name = null) + { + if ($this->valid($email)) { + $this->headers['Reply-To'] = $this->formatAddress($email, $name); + } + return $this; + } + + /** + * Форматирование адреса + * @param string|array $email + * @param string $name + * @return string + */ + protected function formatAddress($email, $name = null) + { + $email = $this->filterEmail($email); + if (null === $name || ! is_string($name) || strlen(trim($name)) == 0) { + return $email; + } else { + $name = $this->encodeText($this->filterName($name)); + return sprintf('"%s" <%s>', $name, $email); + } + } + + /** + * Кодирование заголовка/имени + * @param string $str + * @return string + */ + protected function encodeText($str) + { + if (preg_match('%[^\x20-\x7F]%', $str)) { + return '=?UTF-8?B?' . base64_encode($str) . '?='; + } else { + return $str; + } + } + + /** + * Фильтрация email + * @param string $email + * @return string + */ + protected function filterEmail($email) + { + return preg_replace('%[\x00-\x1F",<>]%', '', $email); + } + + /** + * Фильтрация имени + * @param string $name + * @return string + */ + protected function filterName($name) + { + return strtr(trim($name), [ + "\r" => '', + "\n" => '', + "\t" => '', + '"' => '\'', + '<' => '[', + '>' => ']', + ]); + } + /** * Установка папки для поиска шаблонов писем * @param string $folder @@ -49,15 +256,229 @@ class Mail } /** - * Отправка письма - * @param string $email + * Задает сообщение по шаблону * @param string $tpl * @param array $data + * @throws \RuntimeException + * @return Mail + */ + public function setTpl($tpl, array $data) + { + $file = rtrim($this->folder, '\\/') . '/' . $this->language . '/mail/' . $tpl; + if (! file_exists($file)) { + throw new RuntimeException('Tpl not found'); + } + $tpl = trim(file_get_contents($file)); + foreach ($data as $key => $val) { + $tpl = str_replace('<' . $key . '>', (string) $val, $tpl); + } + list($subject, $tpl) = explode("\n", $tpl, 2); + if (! isset($tpl)) { + throw new RuntimeException('Tpl empty'); + } + $this->setSubject(substr($subject, 8)); + return $this->setMessage($tpl); + } + + /** + * Задает сообщение + * @param string $message + * @throws \RuntimeException + * @return Mail + */ + public function setMessage($message) + { + $this->message = str_replace("\0", $this->EOL, + str_replace(["\r\n", "\n", "\r"], "\0", + str_replace("\0", '', trim($message)))); +// $this->message = wordwrap ($this->message, 75, $this->EOL, false); + return $this; + } + + /** + * Отправляет письмо * @return bool */ - public function send($email, $tpl, array $data) + public function send() { - var_dump($data); + if (empty($this->to)) { + throw new RuntimeException('No recipient(s)'); + } + if (empty($this->headers['From'])) { + throw new RuntimeException('No sender'); + } + if (! isset($this->headers['Subject'])) { + throw new RuntimeException('Subject empty'); + } + if (trim($this->message) == '') { + throw new RuntimeException('Message empty'); + } + + $this->headers = array_replace($this->headers, [ + 'Date' => gmdate('r'), + 'MIME-Version' => '1.0', + 'Content-transfer-encoding' => '8bit', + 'Content-type' => 'text/plain; charset=utf-8', + 'X-Mailer' => 'ForkBB Mailer', + ]); + + if (is_array($this->smtp)) { + return $this->smtp(); + } else { + return $this->mail(); + } + } + + /** + * Отправка письма через функцию mail + * @return bool + */ + protected function mail() + { + $to = implode(', ', $this->to); + $subject = $this->headers['Subject']; + $headers = $this->headers; + unset($headers['Subject']); + $headers = $this->strHeaders($headers); + return @mail($to, $subject, $this->message, $headers); + } + + /** + * Переводит заголовки из массива в строку + * @param array $headers + * @return string + */ + protected function strHeaders(array $headers) + { + foreach ($headers as $key => &$value) { + $value = $key . ': ' . $value; + } + unset($value); + return join($this->EOL, $headers); + } + + /** + * Отправка письма через smtp + * @throws \RuntimeException + * @return bool + */ + protected function smtp() + { + // подлючение + if (! is_resource($this->connect)) { + if (($connect = @fsockopen($this->smtp['host'], $this->smtp['port'], $errno, $errstr, 5)) === false) { + throw new RuntimeException('Could not connect to smtp host "' . $this->smtp['host'] . '" (' . $errno . ') (' . $errstr . ')'); + } + stream_set_timeout($connect, 5); + $this->connect = $connect; + $this->smtpData(null, '220'); + } + + $message = $this->EOL . str_replace("\n.", "\n..", $this->EOL . $this->message) . $this->EOL . '.'; + $headers = $this->strHeaders($this->headers); + + // цикл по получателям + foreach ($this->to as $to) { + $this->smtpHello(); + $this->smtpData('MAIL FROM: <' . $this->getEmailFrom($this->headers['From']). '>', '250'); + $this->smtpData('RCPT TO: <' . $this->getEmailFrom($to) . '>', ['250', '251']); + $this->smtpData('DATA', '354'); + $this->smtpData('To: ' . $to . $this->EOL . $headers . $message, '250'); + $this->smtpData('NOOP', '250'); + } return true; } + + public function __destruct() + { + // завершение сеанса smtp + if (is_resource($this->connect)) { + $this->smtpData('QUIT', null); + @fclose($this->connect); + } + } + + /** + * Hello SMTP server + */ + protected function smtpHello() + { + switch ($this->auth) { + case 1: + $this->smtpData('EHLO ' . $this->hostname(), '250'); + return; + case 0: + if ($this->smtp['user'] != '' && $this->smtp['pass'] != '') { + $code = $this->smtpData('EHLO ' . $this->hostname(), ['250', '500', '501', '502', '550']); + if ($code === '250') { + $this->smtpData('AUTH LOGIN', '334'); + $this->smtpData(base64_encode($this->smtp['user']), '334'); + $this->smtpData(base64_encode($this->smtp['pass']), '235'); + $this->auth = 1; + return; + } + } + default: + $this->auth = -1; + $this->smtpData('HELO ' . $this->hostname(), '250'); + } + } + + /** + * @param string $data + * @param mixed $code + * @throws \RuntimeException + * @return string + */ + protected function smtpData($data, $code) + { +//var_dump($data); + if (is_string($data)) { + @fwrite($this->connect, $data . $this->EOL); + } + + $response = ''; +// while (! isset($get{3}) || $get{3} !== ' ') { + while (is_resource($this->connect) && !feof($this->connect)) { + if (($get = @fgets($this->connect, 512)) === false) { + throw new RuntimeException('Couldn\'t get mail server response codes'); + } + $response .= $get; + if (isset($get{3}) && $get{3} === ' ') { + $return = substr($get, 0, 3); + break; + } + } +//var_dump($response); + if ($code !== null && ! in_array($return, (array) $code)) { + throw new RuntimeException('Unable to send email. Response of the SMTP server: "'.$get.'"'); + } + return $return; + } + + /** + * Выделяет email из заголовка + * @param string $str + * @return string + */ + protected function getEmailFrom($str) + { + $match = explode('" <', $str); + if (count($match) == 2 && substr($match[1], -1) == '>') { + return rtrim($match[1], '>'); + } else { + return $str; + } + } + + /** + * Возвращает имя сервера или его ip + * @return string + */ + protected function hostname() + { + return empty($_SERVER['SERVER_NAME']) + ? (isset($_SERVER['SERVER_ADDR']) ? '[' . $_SERVER['SERVER_ADDR'] . ']' : '[127.0.0.1]') + : $_SERVER['SERVER_NAME']; + } } diff --git a/app/Core/Router.php b/app/Core/Router.php index 01e59be5..ed3a40e4 100644 --- a/app/Core/Router.php +++ b/app/Core/Router.php @@ -71,14 +71,15 @@ class Router /** * Проверка url на принадлежность форуму - * @param string $url + * @param mixed $url * @param string $defMarker * @param array $defArgs * @return string */ public function validate($url, $defMarker, array $defArgs = []) { - if (parse_url($url, PHP_URL_HOST) === $this->host + if (is_string($url) + && parse_url($url, PHP_URL_HOST) === $this->host && ($route = $this->route('GET', rawurldecode(parse_url($url, PHP_URL_PATH)))) && $route[0] === self::OK ) { diff --git a/app/Models/Actions/CacheGenerator.php b/app/Models/Actions/CacheGenerator.php index 97ed10eb..dc45e6a5 100644 --- a/app/Models/Actions/CacheGenerator.php +++ b/app/Models/Actions/CacheGenerator.php @@ -65,14 +65,11 @@ class CacheGenerator $search_for = $replace_with = []; for ($i = 0; $i < $num_words; $i++) { list($search_for[$i], $replace_with[$i]) = $this->db->fetch_row($result); - $search_for[$i] = '%(?<=[^\p{L}\p{N}])('.str_replace('\*', '[\p{L}\p{N}]*?', preg_quote($search_for[$i], '%')).')(?=[^\p{L}\p{N}])%iu'; + $search_for[$i] = '%(?db->free_result($result); - return [ - 'search_for' => $search_for, - 'replace_with' => $replace_with - ]; + return [$search_for, $replace_with]; } /** diff --git a/app/Models/Actions/CheckBans.php b/app/Models/Actions/CheckBans.php index 9a654c1f..706d45d9 100644 --- a/app/Models/Actions/CheckBans.php +++ b/app/Models/Actions/CheckBans.php @@ -12,6 +12,11 @@ class CheckBans */ protected $c; + /** + * Содержит массив с описание бана для проверяемого юзера + */ + protected $ban; + /** * Конструктор * @param Container $container @@ -27,54 +32,72 @@ class CheckBans */ public function check() { - $bans = $this->c->bans; $user = $this->c->user; // Для админов и при отсутствии банов прекращаем проверку - if ($user->isAdmin || empty($bans)) { - return null; + if ($user->isAdmin) { + return null; + } elseif ($user->isGuest) { + $banned = $this->isBanned(null, null, $user->ip); + } else { + $banned = $this->isBanned($user->username, $user->email, $user->ip); } - // Add a dot or a colon (depending on IPv4/IPv6) at the end of the IP address to prevent banned address - // 192.168.0.5 from matching e.g. 192.168.0.50 - $userIp = $user->ip; - $add = strpos($userIp, '.') !== false ? '.' : ':'; - $userIp .= $add; + if ($banned) { + $this->c->Online->delete($user); //???? а зачем это надо? + return $this->ban; + } - $username = mb_strtolower($user->username); + return null; + } - $banned = false; + /** + * Проверяет наличие бана на основании имени юзера, email и(или) ip + * Удаляет просроченные баны + * @param string $username + * @param string $email + * @param string $userIp + * @return int + */ + public function isBanned($username = null, $email = null, $userIp = null) + { + $bans = $this->c->bans; + if (empty($bans)) { + return 0; + } + if (isset($username)) { + $username = mb_strtolower($username, 'UTF-8'); + } + if (isset($userIp)) { + // Add a dot or a colon (depending on IPv4/IPv6) at the end of the IP address to prevent banned address + // 192.168.0.5 from matching e.g. 192.168.0.50 + $add = strpos($userIp, '.') !== false ? '.' : ':'; + $userIp .= $add; + } + + $banned = 0; $remove = []; + $now = time(); - foreach ($bans as $cur) - { - // Has this ban expired? - if ($cur['expire'] != '' && $cur['expire'] <= time()) - { + foreach ($bans as $cur) { + if ($cur['expire'] != '' && $cur['expire'] < $now) { $remove[] = $cur['id']; continue; - } elseif ($banned) { - continue; - } - - if (! $user->isGuest) { - if ($cur['username'] != '' && $username == mb_strtolower($cur['username'])) { - $banned = $cur; - continue; - } elseif ($cur['email'] != '' && $user->email == $cur['email']) { - $banned = $cur; - continue; - } - } - - if ($cur['ip'] != '') - { - $ips = explode(' ', $cur['ip']); - foreach ($ips as $ip) { + } elseif (isset($username) && $cur['username'] != '' && $username == mb_strtolower($cur['username'])) { + $this->ban = $cur; + $banned = 1; + break; + } elseif (isset($email) && $cur['email'] != '' && $email == $cur['email']) { + $this->ban = $cur; + $banned = 2; + break; + } elseif (isset($userIp) && $cur['ip'] != '') { + foreach (explode(' ', $cur['ip']) as $ip) { $ip .= $add; if (substr($userIp, 0, strlen($ip)) == $ip) { - $banned = $cur; - break; + $this->ban = $cur; + $banned = 3; + break 2; } } } @@ -87,14 +110,6 @@ class CheckBans $db->query('DELETE FROM '.$db->prefix.'bans WHERE id IN (' . implode(',', $remove) . ')') or error('Unable to delete expired ban', __FILE__, __LINE__, $db->error()); $this->c->{'bans update'}; } - - if ($banned) - { - //???? а зачем это надо? - $this->c->Online->delete($user); - return $banned; - } - - return null; + return $banned; } } diff --git a/app/Models/Pages/Auth.php b/app/Models/Pages/Auth.php index ff1fa265..fcbe8ec5 100644 --- a/app/Models/Pages/Auth.php +++ b/app/Models/Pages/Auth.php @@ -2,6 +2,9 @@ namespace ForkBB\Models\Pages; +use ForkBB\Models\Validator; +use ForkBB\Models\User; + class Auth extends Page { /** @@ -22,6 +25,12 @@ class Auth extends Page */ protected $index = 'login'; + /** + * Для передачи User из vCheckEmail() в forgetPost() + * @var User + */ + protected $tmpUser; + /** * Выход пользователя * @param array $args @@ -37,7 +46,7 @@ class Auth extends Page $this->c->Online->delete($this->c->user); $this->c->UserMapper->updateLastVisit($this->c->user); - $this->c->Lang->load('login'); + $this->c->Lang->load('auth'); return $this->c->Redirect->setPage('Index')->setMessage(__('Logout redirect')); } @@ -48,7 +57,7 @@ class Auth extends Page */ public function login(array $args) { - $this->c->Lang->load('login'); + $this->c->Lang->load('auth'); if (! isset($args['_username'])) { $args['_username'] = ''; @@ -62,15 +71,15 @@ class Auth extends Page __('Login'), ]; $this->data = [ - 'username' => $args['_username'], 'formAction' => $this->c->Router->link('Login'), 'formToken' => $this->c->Csrf->create('Login'), 'forgetLink' => $this->c->Router->link('Forget'), 'regLink' => $this->config['o_regs_allow'] == '1' - ? $this->c->Router->link('Registration') + ? $this->c->Router->link('Register') : null, - 'formRedirect' => $args['_redirect'], - 'formSave' => ! empty($args['_save']) + 'username' => $args['_username'], + 'redirect' => $args['_redirect'], + 'save' => ! empty($args['_save']) ]; return $this; @@ -82,93 +91,78 @@ class Auth extends Page */ public function loginPost() { - $this->c->Lang->load('login'); + $this->c->Lang->load('auth'); - $v = $this->c->Validator; - $v->setRules([ + $v = $this->c->Validator->addValidators([ + 'login_process' => [$this, 'vLoginProcess'], + ])->setRules([ 'token' => 'token:Login', 'redirect' => 'referer:Index', - 'username' => ['required|string|min:2|max:25', __('Username')], - 'password' => ['required|string', __('Password')], + 'username' => ['required|string', __('Username')], + 'password' => ['required|string|login_process', __('Password')], 'save' => 'checkbox', ]); - $ok = $v->validation($_POST); - $data = $v->getData(); - $this->iswev = $v->getErrors(); - - if ($ok && ! $this->loginProcess($data['username'], $data['password'], $data['save'])) { - $this->iswev['v'][] = __('Wrong user/pass'); - $ok = false; - } - - if ($ok) { - return $this->c->Redirect->setUrl($data['redirect'])->setMessage(__('Login redirect')); + if ($v->validation($_POST)) { + return $this->c->Redirect->setUrl($v->redirect)->setMessage(__('Login redirect')); } else { + $this->iswev = $v->getErrors(); return $this->login([ - '_username' => $data['username'], - '_redirect' => $data['redirect'], - '_save' => $data['save'], + '_username' => $v->username, + '_redirect' => $v->redirect, + '_save' => $v->save, ]); } } /** - * Вход на форум - * @param string $username + * Проверка по базе и вход на форум + * @param Validator $v * @param string $password - * @param bool $save - * @return bool + * @param int $type + * @return array */ - protected function loginProcess($username, $password, $save) + public function vLoginProcess(Validator $v, $password, $type) { - $user = $this->c->UserMapper->getUser($username, 'username'); - if (null == $user) { - return false; - } - - $authorized = false; - $hash = $user->password; - $update = []; - - // For FluxBB by Visman 1.5.10.74 and above - if (strlen($hash) == 40) { - if (hash_equals($hash, sha1($password . $this->c->SALT1))) { - $hash = password_hash($password, PASSWORD_DEFAULT); - $update['password'] = $hash; - $authorized = true; - } + $error = false; + if (! empty($v->getErrors())) { + } elseif (! ($user = $this->c->UserMapper->getUser($v->username, 'username')) instanceof User) { + $error = __('Wrong user/pass'); + } elseif ($user->isUnverified) { + $error = [__('Account is not activated'), 'w']; } else { - $authorized = password_verify($password, $hash); - } + $authorized = false; + $hash = $user->password; + $update = []; + // For FluxBB by Visman 1.5.10.74 and above + if (strlen($hash) == 40) { + if (hash_equals($hash, sha1($password . $this->c->SALT1))) { + $hash = password_hash($password, PASSWORD_DEFAULT); + $update['password'] = $hash; + $authorized = true; + } + } else { + $authorized = password_verify($password, $hash); + } + // ошибка в пароле + if (! $authorized) { + $error = __('Wrong user/pass'); + } else { + // перезаписываем ip админа и модератора - Visman + if ($user->isAdmMod + && $this->config['o_check_ip'] + && $user->registrationIp != $this->c->user->ip + ) { + $update['registration_ip'] = $this->c->user->ip; + } + // изменения юзера в базе + $this->c->UserMapper->updateUser($user->id, $update); - if (! $authorized) { - return false; + $this->c->Online->delete($this->c->user); + $this->c->UserCookie->setUserCookie($user->id, $hash, $v->save); + } } - - // Update the status if this is the first time the user logged in - if ($user->isUnverified) { - $update['group_id'] = (int) $this->config['o_default_user_group']; - } - - // перезаписываем ip админа и модератора - Visman - if ($user->isAdmMod - && $this->config['o_check_ip'] - && $user->registrationIp != $this->c->user->ip - ) { - $update['registration_ip'] = $this->c->user->ip; - } - - // изменения юзера в базе - $this->c->UserMapper->updateUser($user->id, $update); - // обновления кэша - if (isset($update['group_id'])) { - $this->c->{'users_info update'}; - } - $this->c->Online->delete($this->c->user); - $this->c->UserCookie->setUserCookie($user->id, $hash, $save); - - return true; + return [$password, $type, $error]; } /** @@ -178,8 +172,6 @@ class Auth extends Page */ public function forget(array $args) { - $this->c->Lang->load('login'); - $this->nameTpl = 'password_reset'; $this->onlinePos = 'password_reset'; @@ -187,13 +179,15 @@ class Auth extends Page $args['_email'] = ''; } + $this->c->Lang->load('auth'); + $this->titles = [ __('Password reset'), ]; $this->data = [ - 'email' => $args['_email'], 'formAction' => $this->c->Router->link('Forget'), 'formToken' => $this->c->Csrf->create('Forget'), + 'email' => $args['_email'], ]; return $this; @@ -205,52 +199,75 @@ class Auth extends Page */ public function forgetPost() { - $this->c->Lang->load('login'); + $this->c->Lang->load('auth'); - $v = $this->c->Validator; - $v->setRules([ + $v = $this->c->Validator->addValidators([ + 'check_email' => [$this, 'vCheckEmail'], + ])->setRules([ 'token' => 'token:Forget', - 'email' => 'required|email', + 'email' => 'required|string:trim,lower|email|check_email', ])->setMessages([ - 'email' => __('Invalid email'), + 'email.email' => __('Invalid email'), ]); - $ok = $v->validation($_POST); - $data = $v->getData(); - $this->iswev = $v->getErrors(); - - if ($ok && ($user = $this->c->UserMapper->getUser($data['email'], 'email')) === null) { - $this->iswev['v'][] = __('Invalid email'); - $ok = false; - } - if ($ok && ! empty($user->lastEmailSent) && time() - $user->lastEmailSent < 3600) { - $this->iswev['e'][] = __('Email flood', (int) (($user->lastEmailSent + 3600 - time()) / 60)); - $ok = false; - } - - if (! $ok) { + if (! $v->validation($_POST)) { + $this->iswev = $v->getErrors(); return $this->forget([ - '_email' => $data['email'], + '_email' => $v->email, ]); } - $mail = $this->c->Mail; - $mail->setFolder($this->c->DIR_LANG) - ->setLanguage($user->language); + $key = 'p' . $this->c->Secury->randomPass(79); + $hash = $this->c->Secury->hash($v->email . $key); + $link = $this->c->Router->link('ChangePassword', ['email' => $v->email, 'key' => $key, 'hash' => $hash]); + $tplData = [ + 'fRootLink' => $this->c->Router->link('Index'), + 'fMailer' => __('Mailer', $this->config['o_board_title']), + 'username' => $this->tmpUser->username, + 'link' => $link, + ]; + $mail = $this->c->Mail->reset() + ->setFolder($this->c->DIR_LANG) + ->setLanguage($this->tmpUser->language) + ->setTo($v->email, $this->tmpUser->username) + ->setFrom($this->config['o_webmaster_email'], __('Mailer', $this->config['o_board_title'])) + ->setTpl('password_reset.tpl', $tplData); - $key = 'p' . $this->c->Secury->randomPass(75); - $hash = $this->c->Secury->hash($data['email'] . $key); - $link = $this->c->Router->link('ChangePassword', ['email' => $data['email'], 'key' => $key, 'hash' => $hash]); - $tplData = ['link' => $link]; - - if ($mail->send($data['email'], 'password_reset.tpl', $tplData)) { - $this->c->UserMapper->updateUser($user->id, ['activate_string' => $key, 'last_email_sent' => time()]); + if ($mail->send()) { + $this->c->UserMapper->updateUser($this->tmpUser->id, ['activate_string' => $key, 'last_email_sent' => time()]); return $this->c->Message->message(__('Forget mail', $this->config['o_admin_email']), false, 200); } else { return $this->c->Message->message(__('Error mail', $this->config['o_admin_email']), true, 200); } } + /** + * Дополнительная проверка email + * @param Validator $v + * @param string $username + * @param int $type + * @return array + */ + public function vCheckEmail(Validator $v, $email, $type) + { + $error = false; + // есть ошибки + if (! empty($v->getErrors())) { + // email забанен + } elseif ($this->c->CheckBans->isBanned(null, $email) > 0) { + $error = __('Banned email'); + // нет пользователя с таким email + } elseif (! ($user = $this->c->UserMapper->getUser($email, 'email')) instanceof User) { + $error = __('Invalid email'); + // за последний час уже был запрос на этот email + } elseif (! empty($user->lastEmailSent) && time() - $user->lastEmailSent < 3600) { + $error = [__('Email flood', (int) (($user->lastEmailSent + 3600 - time()) / 60)), 'e']; + } else { + $this->tmpUser = $user; + } + return [$email, $type, $error]; + } + /** * Подготовка данных для формы изменения пароля * @param array $args @@ -267,7 +284,7 @@ class Auth extends Page // что-то пошло не так if (! hash_equals($args['hash'], $this->c->Secury->hash($args['email'] . $args['key'])) || ! $this->c->Mail->valid($args['email']) - || ($user = $this->c->UserMapper->getUser($args['email'], 'email')) === null + || ! ($user = $this->c->UserMapper->getUser($args['email'], 'email')) instanceof User || empty($user->activateString) || $user->activateString{0} !== 'p' || ! hash_equals($user->activateString, $args['key']) @@ -276,8 +293,7 @@ class Auth extends Page } } - $this->c->Lang->load('login'); - $this->c->Lang->load('profile'); + $this->c->Lang->load('auth'); $this->titles = [ __('Change pass'), @@ -300,7 +316,7 @@ class Auth extends Page // что-то пошло не так if (! hash_equals($args['hash'], $this->c->Secury->hash($args['email'] . $args['key'])) || ! $this->c->Mail->valid($args['email']) - || ($user = $this->c->UserMapper->getUser($args['email'], 'email')) === null + || ! ($user = $this->c->UserMapper->getUser($args['email'], 'email')) instanceof User || empty($user->activateString) || $user->activateString{0} !== 'p' || ! hash_equals($user->activateString, $args['key']) @@ -308,18 +324,18 @@ class Auth extends Page return $this->c->Message->message(__('Bad request'), false); } - $this->c->Lang->load('login'); - $this->c->Lang->load('profile'); + $this->c->Lang->load('auth'); $v = $this->c->Validator; $v->setRules([ 'token' => 'token:ChangePassword', - 'password' => ['required|string|min:8', __('New pass')], - 'password2' => 'required|same:password', + 'password' => ['required|string|min:8|password', __('New pass')], + 'password2' => ['required|same:password', __('Confirm new pass')], ])->setArguments([ 'token' => $args, ])->setMessages([ - 'password2' => __('Pass not match'), + 'password.password' => __('Pass format'), + 'password2.same' => __('Pass not match'), ]); if (! $v->validation($_POST)) { @@ -329,7 +345,7 @@ class Auth extends Page } $data = $v->getData(); - $this->c->UserMapper->updateUser($user->id, ['password' => password_hash($data['password'], PASSWORD_DEFAULT), 'activate_string' => null]); + $this->c->UserMapper->updateUser($user->id, ['password' => password_hash($data['password'], PASSWORD_DEFAULT), 'email_confirmed' => 1, 'activate_string' => null]); $this->iswev['s'][] = __('Pass updated'); return $this->login(['_redirect' => $this->c->Router->link('Index')]); diff --git a/app/Models/Pages/Page.php b/app/Models/Pages/Page.php index 26f49b2c..55b45684 100644 --- a/app/Models/Pages/Page.php +++ b/app/Models/Pages/Page.php @@ -165,6 +165,8 @@ abstract class Page return $this->data + [ 'pageTitle' => $this->pageTitle(), 'pageHeads' => $this->pageHeads(), + 'fLang' => __('lang_identifier'), + 'fDirection' => __('lang_direction'), 'fTitle' => $this->config['o_board_title'], 'fDescription' => $this->config['o_board_desc'], 'fNavigation' => $this->fNavigation(), @@ -182,14 +184,24 @@ abstract class Page protected function getIswev() { if ($this->config['o_maintenance'] == '1') { - $user = $this->c->user; - if ($user->isAdmMod) { - $this->iswev['w'][] = '' . __('Maintenance mode enabled') . ''; + if ($this->c->user->isAdmin) { + $this->iswev['w'][] = __('Maintenance mode enabled', $this->c->Router->link('AdminOptions', ['#' => 'maintenance'])); } } return $this->iswev; } + /** + * Установка info, success, warning, error, validation информации из вне + * @param array $iswev + * @return Page + */ + public function setIswev(array $iswev) + { + $this->iswev = $iswev; + return $this; + } + /** * Формирует title страницы * @return string @@ -207,7 +219,7 @@ abstract class Page */ protected function pageHeads() { - return []; + return []; //???? } /** @@ -245,7 +257,7 @@ abstract class Page } if ($user->isGuest) { - $nav['register'] = ['register.php', __('Register')]; + $nav['register'] = [$r->link('Register'), __('Register')]; $nav['login'] = [$r->link('Login'), __('Login')]; } else { $nav['profile'] = [$r->link('User', [ @@ -294,11 +306,11 @@ abstract class Page * Заглушка * @param string $name * @param array $arguments - * @return Page + * @throws \RuntimeException */ public function __call($name, array $arguments) { - return $this; + throw new RuntimeException("'{$name}' method is not"); } /** @@ -308,7 +320,7 @@ abstract class Page */ protected function size($size) { - $units = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB'); + $units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']; for ($i = 0; $size > 1024; $i++) { $size /= 1024; diff --git a/app/Models/Pages/Register.php b/app/Models/Pages/Register.php new file mode 100644 index 00000000..ff3764d9 --- /dev/null +++ b/app/Models/Pages/Register.php @@ -0,0 +1,251 @@ +c->Lang->load('register'); + + $v = $this->c->Validator->addValidators([ + 'check_email' => [$this, 'vCheckEmail'], + 'check_username' => [$this, 'vCheckUsername'], + ])->setRules([ + 'token' => 'token:RegisterForm', + 'agree' => 'required|token:Register', + 'on' => 'integer', + 'email' => ['required_with:on|string:trim,lower|email|check_email', __('Email')], + 'username' => ['required_with:on|string:trim|min:2|max:25|login|check_username', __('Username')], + 'password' => ['required_with:on|string|min:8|password', __('Password')], + ])->setMessages([ + 'agree.required' => ['cancel', 'cancel'], + 'agree.token' => [__('Bad agree', $this->c->Router->link('Register')), 'w'], + 'password.password' => __('Pass format'), + 'username.login' => __('Login format'), + ]); + + // завершение регистрации + if ($v->validation($_POST) && $v->on === 1) { + return $this->regEnd($v); + } + + $this->iswev = $v->getErrors(); + + // нет согласия с правилами + if (isset($this->iswev['cancel'])) { + return $this->c->Redirect->setPage('Index')->setMessage(__('Reg cancel redirect')); + } + + $this->titles = [ + __('Register'), + ]; + $this->data = [ + 'formAction' => $this->c->Router->link('RegisterForm'), + 'formToken' => $this->c->Csrf->create('RegisterForm'), + 'agree' => $v->agree, + 'on' => '1', + 'email' => $v->email, + 'username' => $v->username, + ]; + + return $this; + } + + /** + * Дополнительная проверка email + * @param Validator $v + * @param string $username + * @param int $type + * @return array + */ + public function vCheckEmail(Validator $v, $email, $type) + { + $error = false; + // email забанен + if ($this->c->CheckBans->isBanned(null, $email) > 0) { + $error = __('Banned email'); + // найден хотя бы 1 юзер с таким же email + } elseif (empty($v->getErrors()) && $this->c->UserMapper->getUser($email, 'email') !== 0) { + $error = __('Dupe email'); + } + return [$email, $type, $error]; + } + + /** + * Дополнительная проверка username + * @param Validator $v + * @param string $username + * @param int $type + * @return array + */ + public function vCheckUsername(Validator $v, $username, $type) + { + $username = preg_replace('%\s+%su', ' ', $username); + $error = false; + // username = Гость + if (preg_match('%^(guest|' . preg_quote(__('Guest'), '%') . ')$%iu', $username)) { + $error = __('Username guest'); + // цензура + } elseif ($this->config['o_censoring'] == '1' && censor_words($username) !== $username) { + $error = __('Username censor'); + // username забанен + } elseif ($this->c->CheckBans->isBanned($username) > 0) { + $error = __('Banned username'); + // есть пользователь с похожим именем + } elseif (empty($v->getErrors()) && ! $this->c->UserMapper->isUnique($username)) { + $error = __('Username not unique'); + } + return [$username, $type, $error]; + } + + /** + * Завершение регистрации + * @param array @data + * @return Page + */ + protected function regEnd(Validator $v) + { + if ($this->config['o_regs_verify'] == '1') { + $groupId = PUN_UNVERIFIED; + $key = 'w' . $this->c->Secury->randomPass(79); + $visit = 0; + } else { + $groupId = $this->config['o_default_user_group']; + $key = null; + $visit = time(); //???? + } + + $newUserId = $this->c->UserMapper->newUser(new User([ + 'group_id' => $groupId, + 'username' => $v->username, + 'password' => password_hash($v->password, PASSWORD_DEFAULT), + 'email' => $v->email, + 'email_confirmed' => 0, + 'last_visit' => $visit, + 'activate_string' => $key, + ], $this->c)); + + // обновление статистики по пользователям + if ($this->config['o_regs_verify'] != '1') { + $this->c->{'users_info update'}; + } + + // уведомление о регистрации + if ($this->config['o_mailing_list'] != '' && $this->config['o_regs_report'] == '1') { + $tplData = [ + 'fTitle' => $this->config['o_board_title'], + 'fRootLink' => $this->c->Router->link('Index'), + 'fMailer' => __('Mailer', $this->config['o_board_title']), + 'username' => $v->username, + 'userLink' => $this->c->Router->link('User', ['id' => $newUserId, 'name' => $v->username]), + ]; + $mail = $this->c->Mail->reset() + ->setFolder($this->c->DIR_LANG) + ->setLanguage($this->config['o_default_lang']) + ->setTo($this->config['o_mailing_list']) + ->setFrom($this->config['o_webmaster_email'], __('Mailer', $this->config['o_board_title'])) + ->setTpl('new_user.tpl', $tplData) + ->send(); + } + + $this->c->Lang->load('register'); + + // отправка письма активации аккаунта + if ($this->config['o_regs_verify'] == '1') { + $hash = $this->c->Secury->hash($newUserId . $key); + $link = $this->c->Router->link('RegActivate', ['id' => $newUserId, 'key' => $key, 'hash' => $hash]); + $tplData = [ + 'fTitle' => $this->config['o_board_title'], + 'fRootLink' => $this->c->Router->link('Index'), + 'fMailer' => __('Mailer', $this->config['o_board_title']), + 'username' => $v->username, + 'link' => $link, + ]; + $mail = $this->c->Mail->reset() + ->setFolder($this->c->DIR_LANG) + ->setLanguage($this->c->user->language) + ->setTo($v->email) + ->setFrom($this->config['o_webmaster_email'], __('Mailer', $this->config['o_board_title'])) + ->setTpl('welcome.tpl', $tplData); + + // письмо активации аккаунта отправлено + if ($mail->send()) { + return $this->c->Message->message(__('Reg email', $this->config['o_admin_email']), false, 200); + // форма сброса пароля + } else { + return $this->c->Auth->setIswev([ + 'w' => [ + __('Error welcom mail', $this->config['o_admin_email']), + ], + ])->forget([ + '_email' => $v->email, + ]); + } + // форма логина + } else { + return $this->c->Auth->setIswev([ + 's' => [ + __('Reg complete'), + ], + ])->login([ + '_username' => $v->username, + ]); + } + } + + /** + * Активация аккаунта + * @param array $args + * @return Page + */ + public function activate(array $args) + { + if (! hash_equals($args['hash'], $this->c->Secury->hash($args['id'] . $args['key'])) + || ! ($user = $this->c->UserMapper->getUser($args['id'])) instanceof User + || empty($user->activateString) + || $user->activateString{0} !== 'w' + || ! hash_equals($user->activateString, $args['key']) + ) { + return $this->c->Message->message(__('Bad request'), false); + } + + $this->c->UserMapper->updateUser($user->id, ['group_id' => $this->config['o_default_user_group'], 'email_confirmed' => 1, 'activate_string' => null]); + $this->c->{'users_info update'}; + + $this->c->Lang->load('register'); + + return $this->c->Auth->setIswev([ + 's' => [ + __('Reg complete'), + ], + ])->login([ + '_username' => $user->username, + ]); + } +} diff --git a/app/Models/Pages/Rules.php b/app/Models/Pages/Rules.php index e8bb8937..d785c24e 100644 --- a/app/Models/Pages/Rules.php +++ b/app/Models/Pages/Rules.php @@ -32,7 +32,33 @@ class Rules extends Page __('Forum rules'), ]; $this->data = [ - 'Rules' => $this->config['o_rules_message'], + 'title' => __('Forum rules'), + 'rules' => $this->config['o_rules_message'], + 'formAction' => null, + ]; + return $this; + } + + /** + * Подготавливает данные для шаблона + * @return Page + */ + public function confirmation() + { + $this->index = 'register'; + $this->c->Lang->load('register'); + + $this->titles = [ + __('Forum rules'), + ]; + $this->data = [ + 'title' => __('Forum rules'), + 'rules' => $this->config['o_rules'] == '1' ? + $this->config['o_rules_message'] + : __('If no rules'), + 'formAction' => $this->c->Router->link('RegisterForm'), + 'formToken' => $this->c->Csrf->create('RegisterForm'), + 'formHash' => $this->c->Csrf->create('Register'), ]; return $this; } diff --git a/app/Models/User.php b/app/Models/User.php index b9b20bdb..bb9365a0 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -19,16 +19,6 @@ class User extends AbstractModel */ protected $config; - /** - * @var UserCookie - */ - protected $userCookie; - - /** - * @var DB - */ - protected $db; - /** * Время * @var int @@ -43,8 +33,6 @@ class User extends AbstractModel $this->now = time(); $this->c = $container; $this->config = $container->config; - $this->userCookie = $container->UserCookie; - $this->db = $container->DB; parent::__construct($data); } diff --git a/app/Models/UserMapper.php b/app/Models/UserMapper.php index e0fbfcb0..6599af4b 100644 --- a/app/Models/UserMapper.php +++ b/app/Models/UserMapper.php @@ -90,7 +90,8 @@ class UserMapper * Получение пользователя по условию * @param int|string * @param string $field - * @return null|User + * @return int|User + * @throws \InvalidArgumentException */ public function getUser($value, $field = 'id') { @@ -105,13 +106,13 @@ class UserMapper $where = 'u.email=\'' . $this->db->escape($value) . '\''; break; default: - return null; + throw new InvalidArgumentException('Field not supported'); } $result = $this->db->query('SELECT u.*, g.* FROM '.$this->db->prefix.'users AS u LEFT JOIN '.$this->db->prefix.'groups AS g ON u.group_id=g.g_id WHERE '.$where) or error('Unable to fetch user information', __FILE__, __LINE__, $this->db->error()); // найдено несколько пользователей if ($this->db->num_rows($result) !== 1) { - return null; + return $this->db->num_rows($result); } $user = $this->db->fetch_assoc($result); @@ -119,14 +120,25 @@ class UserMapper // найден гость if ($user['id'] == 1) { - return null; + return 1; } return new User($user, $this->c); } /** - * Обновить данные юзера + * Проверка на уникальность имени пользователя + * @param string $username + * @return bool + */ + public function isUnique($username) + { + $result = $this->db->query('SELECT username FROM '.$this->db->prefix.'users WHERE (UPPER(username)=UPPER(\''.$this->db->escape($username).'\') OR UPPER(username)=UPPER(\''.$this->db->escape(preg_replace('%[^\p{L}\p{N}]%u', '', $username)).'\'))') or error('Unable to fetch user info', __FILE__, __LINE__, $this->db->error()); + return ! $this->db->num_rows($result); + } + + /** + * Обновить данные пользователя * @param int $id * @param array $update */ @@ -152,4 +164,16 @@ class UserMapper $this->db->query('UPDATE '.$this->db->prefix.'users SET '.implode(', ', $set).' WHERE id='.$id) or error('Unable to update user data', __FILE__, __LINE__, $this->db->error()); } + /** + * Создание нового пользователя + * @param User $user + * @throws + * @return int + */ + public function newUser(User $user) + { + $this->db->query('INSERT INTO '.$this->db->prefix.'users (username, group_id, password, email, email_confirmed, email_setting, timezone, dst, language, style, registered, registration_ip, last_visit, activate_string) VALUES(\''.$this->db->escape($user->username).'\', '.$user->groupId.', \''.$this->db->escape($user->password).'\', \''.$this->db->escape($user->email).'\', '.$user->emailConfirmed.', '.$this->config['o_default_email_setting'].', '.$this->config['o_default_timezone'].' , '.$this->config['o_default_dst'].', \''.$this->db->escape($user->language).'\', \''.$user->style.'\', '.time().', \''.$this->db->escape($this->getIpAddress()).'\', '.$user->lastVisit.', \''.$this->db->escape($user->activateString).'\')') or error('Unable to create user', __FILE__, __LINE__, $this->db->error()); + $new_uid = $this->db->insert_id(); //???? + return $new_uid; + } } diff --git a/app/Models/Validator.php b/app/Models/Validator.php index d97b5881..a2cb5d35 100644 --- a/app/Models/Validator.php +++ b/app/Models/Validator.php @@ -12,6 +12,7 @@ class Validator const T_NUMERIC = 2; const T_INT = 3; const T_ARRAY = 4; + const T_BOOLEAN = 5; /** * Контейнер @@ -19,6 +20,11 @@ class Validator */ protected $c; + /** + * @var array + */ + protected $validators; + /** * @var array */ @@ -27,7 +33,7 @@ class Validator /** * @var array */ - protected $data; + protected $result; /** * @var array @@ -49,11 +55,9 @@ class Validator */ protected $errors; - /** - * Тип текущего поля при валидации - * @var int - */ - protected $curType; + protected $fields; + protected $status; + protected $raw; /** * Конструктор @@ -62,6 +66,35 @@ class Validator public function __construct(Container $container) { $this->c = $container; + $this->validators = [ + 'array' => [$this, 'vArray'], + 'checkbox' => [$this, 'vCheckbox'], + 'email' => [$this, 'vEmail'], + 'integer' => [$this, 'vInteger'], + 'login' => [$this, 'vLogin'], + 'max' => [$this, 'vMax'], + 'min' => [$this, 'vMin'], + 'numeric' => [$this, 'vNumeric'], + 'password' => [$this, 'vPassword'], + 'referer' => [$this, 'vReferer'], + 'regex' => [$this, 'vRegex'], + 'required' => [$this, 'vRequired'], + 'required_with' => [$this, 'vRequiredWith'], + 'same' => [$this, 'vSame'], + 'string' => [$this, 'vString'], + 'token' => [$this, 'vToken'], + ]; + } + + /** + * Добавление новых валидаторов + * @param array $validators + * @param Validator + */ + public function addValidators(array $validators) + { + $this->validators = array_replace($this->validators, $validators); + return $this; } /** @@ -73,27 +106,27 @@ class Validator public function setRules(array $list) { $this->rules = []; - $this->data = []; + $this->result = []; $this->alias = []; $this->errors = []; $this->arguments = []; + $this->fields = []; foreach ($list as $field => $raw) { $rules = []; // псевдоним содержится в списке правил if (is_array($raw)) { - $this->aliases[$field] = $raw[1]; - $raw = $raw[0]; + list($raw, $this->aliases[$field]) = $raw; } // перебор правил для текущего поля - $rawRules = explode('|', $raw); - foreach ($rawRules as $rule) { + foreach (explode('|', $raw) as $rule) { $tmp = explode(':', $rule, 2); - if (! method_exists($this, $tmp[0] . 'Rule')) { - throw new RuntimeException('Rule not found'); + if (empty($this->validators[$tmp[0]])) { + throw new RuntimeException($tmp[0] . ' validator not found'); } $rules[$tmp[0]] = isset($tmp[1]) ? $tmp[1] : ''; } $this->rules[$field] = $rules; + $this->fields[$field] = $field; } return $this; } @@ -127,7 +160,7 @@ class Validator */ public function setAliases(array $aliases) { - $this->aliases = $aliases; + $this->aliases = array_replace($this->aliases, $aliases); return $this; } @@ -143,38 +176,62 @@ class Validator if (empty($this->rules)) { throw new RuntimeException('Rules not found'); } - $ok = true; $this->errors = []; - // перебор всех полей - foreach ($this->rules as $field => $rules) { - $error = false; - $this->curType = self::T_UNKNOWN; - // обязательное поле отсутствует - if (! isset($raw[$field]) && isset($rules['required'])) { - $rule = 'required'; - $attr = $rules['required']; - $args = $this->getArguments($field, $rule); - list($value, $error) = $this->requiredRule('', $attr, $args); - } else { - $value = isset($raw[$field]) - ? $this->c->Secury->replInvalidChars($raw[$field]) - : null; - // перебор правил для текущего поля - foreach ($rules as $rule => $attr) { - $args = $this->getArguments($field, $rule); - $method = $rule . 'Rule'; - list($value, $error) = $this->$method($value, $attr, $args); - // ошибок нет - if (false === $error) { - continue; - } - break; - } - } - $ok = $this->error($error, $field, $rule, $attr, $ok); - $this->data[$field] = $value; + $this->status = []; + $this->raw = $raw; + foreach ($this->fields as $field) { + $this->$field; } - return $ok; + $this->raw = null; + return empty($this->errors); + } + + /** + * Проверяет поле согласно заданным правилам + * Возвращает значение запрашиваемого поля + * @param string + * @return mixed + * @throws \RuntimeException + */ + public function __get($field) + { + if (isset($this->status[$field])) { + return $this->result[$field]; + } elseif (empty($this->rules[$field])) { + throw new RuntimeException("No rules for '{$field}' field"); + } + + $value = null; + if (! isset($this->raw[$field]) && isset($this->rules[$field]['required'])) { + $rules = ['required' => '']; + } else { + $rules = $this->rules[$field]; + if (isset($this->raw[$field])) { + $value = $this->c->Secury->replInvalidChars($this->raw[$field]); + } + } + + $error = false; + $type = self::T_UNKNOWN; + foreach ($rules as $validator => $attr) { + $args = $this->getArguments($field, $validator); + list($value, $type, $error) = $this->validators[$validator]($this, $value, $type, $attr, $args); + // ошибок нет + if (false === $error) { + continue; + } + break; + } + + if (! is_bool($error)) { + $this->error($error, $field, $validator, $attr); + $this->status[$field] = false; + } else { + $this->status[$field] = true; + } + + $this->result[$field] = $value; + return $value; } /** @@ -185,8 +242,8 @@ class Validator */ protected function getArguments($field, $rule) { - if (isset($this->arguments[$field . '.'. $rule])) { - return $this->arguments[$field . '.'. $rule]; + if (isset($this->arguments[$field . '.' . $rule])) { + return $this->arguments[$field . '.' . $rule]; } elseif (isset($this->arguments[$field])) { return $this->arguments[$field]; } else { @@ -200,14 +257,9 @@ class Validator * @param string $field * @param string $rule * @param string $attr - * @param bool $ok - * return bool */ - protected function error($error, $field, $rule, $attr, $ok) + protected function error($error, $field, $rule, $attr) { - if (is_bool($error)) { - return $ok; - } // псевдоним имени поля $alias = isset($this->aliases[$field]) ? $this->aliases[$field] : $field; // текст ошибки @@ -223,7 +275,19 @@ class Validator $error = $error[0]; } $this->errors[$type][] = __($error, [':alias' => $alias, ':attr' => $attr]); - return false; + } + + /** + * Возвращает статус проверки поля + * @param string $field + * @return bool + */ + public function getStatus($field) + { + if (! isset($this->status[$field])) { + $this->$field; + } + return $this->status[$field]; } /** @@ -234,10 +298,10 @@ class Validator */ public function getData() { - if (empty($this->data)) { + if (empty($this->result)) { throw new RuntimeException('Data not found'); } - return $this->data; + return $this->result; } /** @@ -249,175 +313,221 @@ class Validator return $this->errors; } - /** - * Правило "required" - * @param mixed $value - * @param string $attrs - * @param mixed $args - * @return array - */ - protected function requiredRule($value, $attr, $args) - { - $f = function () use ($value) { - if (is_string($value)) { - $this->curType = self::T_STRING; - return isset($value{0}); - } elseif (is_array($value)) { - $this->curType = self::T_ARRAY; - return ! empty($value); - } else { - return null !== $value; - } - }; - if ($f()) { - if (is_numeric($value)) { - if (is_int(0 + $value)) { - $this->curType = self::T_INT; - } else { - $this->curType = self::T_NUMERIC; - } - } - return [$value, false]; - } else { - return [$attr, 'The :alias is required']; - } - } - - protected function stringRule($value, $attr) + protected function vRequired($v, $value, $type) { if (is_string($value)) { - $this->curType = self::T_STRING; - return [$value, false]; + if (strlen(trim($value)) > 0) { + return [$value, $v::T_STRING, false]; + } + } elseif (is_array($value)) { + if (! empty($value)) { + return [$value, $v::T_ARRAY, false]; + } + } elseif (null !== $value) { + if (is_int($value)) { + $type = $v::T_INT; + } elseif (is_numeric($value)) { + $type = $v::T_NUMERIC; + } + return [$value, $type, false]; + } + return [null, $type, 'The :alias is required']; + } + + protected function vRequiredWith($v, $value, $type, $attr) + { + foreach (explode(',', $attr) as $field) { + if (null !== $v->$field) { + return $this->vRequired($v, $value, $type); + } + } + list(, , $error) = $this->vRequired($v, $value, $type); + if (false === $error) { + return [null, $type, 'The :alias is not required']; } else { - return [$attr, 'The :alias must be string']; + return [$value, $type, false]; } } - protected function numericRule($value, $attr) + protected function vString($v, $value, $type, $attr) { - if (is_numeric($value)) { - $this->curType = self::T_NUMERIC; - return [0 + $value, false]; + if (null === $value) { + return [null, $type, false]; + } elseif (is_string($value)) { + foreach(explode(',', $attr) as $action) { + switch ($action) { + case 'trim': + $value = trim($value); + break; + case 'lower': + $value = mb_strtolower($value, 'UTF-8'); + break; + } + } + return [$value, $v::T_STRING, false]; } else { - return [$attr, 'The :alias must be numeric']; + return [null, $type, 'The :alias must be string']; } } - protected function intRule($value, $attr) + protected function vNumeric($v, $value, $type) { - if (is_numeric($value) && is_int(0 + $value)) { - $this->curType = self::T_INT; - return [(int) $value, false]; + if (null === $value) { + return [null, $type, false]; + } elseif (is_numeric($value)) { + return [0 + $value, $v::T_NUMERIC, false]; } else { - return [$attr, 'The :alias must be integer']; + return [null, $type, 'The :alias must be numeric']; } } - protected function arrayRule($value, $attr) + protected function vInteger($v, $value, $type) { - if (is_array($value)) { - $this->curType = self::T_ARRAY; - return [$value, false]; + if (null === $value) { + return [null, $type, false]; + } elseif (is_numeric($value) && is_int(0 + $value)) { + return [(int) $value, $v::T_INT, false]; } else { - return [$attr, 'The :alias must be array']; + return [null, $type, 'The :alias must be integer']; } } - protected function minRule($value, $attr) + protected function vArray($v, $value, $type) { - switch ($this->curType) { + if (null === $value) { + return [null, $type, false]; + } elseif (is_array($value)) { + return [$value, $v::T_ARRAY, false]; + } else { + return [null, $type, 'The :alias must be array']; + } + } + + protected function vMin($v, $value, $type, $attr) + { + if (null === $value) { + return [null, $type, false]; + } + switch ($type) { case self::T_STRING: - if (mb_strlen($value) < $attr) { - return [$value, 'The :alias minimum is :attr characters']; + if (mb_strlen($value, 'UTF-8') < $attr) { + return [$value, $type, 'The :alias minimum is :attr characters']; } break; case self::T_NUMERIC: case self::T_INT: if ($value < $attr) { - return [$value, 'The :alias minimum is :attr']; + return [$value, $type, 'The :alias minimum is :attr']; } break; case self::T_ARRAY: if (count($value) < $attr) { - return [$value, 'The :alias minimum is :attr elements']; + return [$value, $type, 'The :alias minimum is :attr elements']; } break; default: - return ['', 'The :alias minimum is :attr']; + return [null, $type, 'The :alias minimum is :attr']; break; } - return [$value, false]; + return [$value, $type, false]; } - protected function maxRule($value, $attr) + protected function vMax($v, $value, $type, $attr) { - switch ($this->curType) { + if (null === $value) { + return [null, $type, false]; + } + switch ($type) { case self::T_STRING: - if (mb_strlen($value) > $attr) { - return [$value, 'The :alias maximum is :attr characters']; + if (mb_strlen($value, 'UTF-8') > $attr) { + return [$value, $type, 'The :alias maximum is :attr characters']; } break; case self::T_NUMERIC: case self::T_INT: if ($value > $attr) { - return [$value, 'The :alias maximum is :attr']; + return [$value, $type, 'The :alias maximum is :attr']; } break; case self::T_ARRAY: if (count($value) > $attr) { - return [$value, 'The :alias maximum is :attr elements']; + return [$value, $type, 'The :alias maximum is :attr elements']; } break; default: - return ['', 'The :alias maximum is :attr']; + return [null, $type, 'The :alias maximum is :attr']; break; } - return [$value, false]; + return [$value, $type, false]; } - protected function tokenRule($value, $attr, $args) + protected function vToken($v, $value, $type, $attr, $args) { if (! is_array($args)) { $args = []; } - if (is_string($value) && $this->c->Csrf->verify($value, $attr, $args)) { - return [$value, false]; + $value = (string) $value; + if ($this->c->Csrf->verify($value, $attr, $args)) { + return [$value, $type, false]; } else { - return ['', ['Bad token', 'e']]; + return [$value, $type, ['Bad token', 'e']]; } } - protected function checkboxRule($value, $attr) + protected function vCheckbox($v, $value) { - return [! empty($value), false]; //???? + return [! empty($value), $v::T_BOOLEAN, false]; } - protected function refererRule($value, $attr, $args) + protected function vReferer($v, $value, $type, $attr, $args) { if (! is_array($args)) { $args = []; } - return [$this->c->Router->validate($value, $attr), false]; + return [$this->c->Router->validate($value, $attr, $args), $type, false]; } - protected function emailRule($value, $attr) + protected function vEmail($v, $value, $type) { - if ($this->c->Mail->valid($value)) { - return [$value, false]; + if (null === $value) { + return [$value, $type, false]; + } elseif ($this->c->Mail->valid($value)) { + return [$value, $type, false]; } else { if (! is_string($value)) { $value = (string) $value; } - return [$value, 'The :alias is not valid email']; + return [$value, $type, 'The :alias is not valid email']; } } - protected function sameRule($value, $attr) + protected function vSame($v, $value, $type, $attr) { - if (isset($this->data[$attr]) && $value === $this->data[$attr]) { - return [$value, false]; + if (! $v->getStatus($attr) || $value === $v->$attr) { + return [$value, $type, false]; } else { - return [$value, 'The :alias must be same with original']; + return [null, $type, 'The :alias must be same with original']; } } + + protected function vRegex($v, $value, $type, $attr) + { + if (null === $value) { + return [$value, $type, false]; + } elseif ($type === $v::T_STRING && preg_match($attr, $value)) { + return [$value, $type, false]; + } else { + return [null, $type, 'The :alias is not valid format']; + } + } + + protected function vPassword($v, $value, $type) + { + return $this->vRegex($v, $value, $type, '%^(?=.*\p{N})(?=.*\p{Lu})(?=.*\p{Ll})(?=.*[^\p{N}\p{L}])%u'); + } + + protected function vLogin($v, $value, $type) + { + return $this->vRegex($v, $value, $type, '%^\p{L}[\p{L}\p{N}\x20\._-]+$%uD'); + } } diff --git a/app/bootstrap.php b/app/bootstrap.php index 9f21e71c..3ea3a91b 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -34,8 +34,9 @@ if (!defined('PUN_SEARCH_MAX_WORD')) if (!defined('FORUM_MAX_COOKIE_SIZE')) define('FORUM_MAX_COOKIE_SIZE', 4048); -$loader = require __DIR__ . '/../vendor/autoload.php'; -$loader->setPsr4('ForkBB\\', __DIR__ . '/'); +//$loader = +require __DIR__ . '/../vendor/autoload.php'; +//$loader->setPsr4('ForkBB\\', __DIR__ . '/'); if (file_exists(__DIR__ . '/config/main.php')) { $container = new Container(include __DIR__ . '/config/main.php'); @@ -64,15 +65,13 @@ while (! $page instanceof Page && $cur = array_pop($controllers)) { $page = $container->$cur; } -if ($page instanceof Page) { //???? - if ($page->getDataForOnline(true)) { - $container->Online->handle($page); - } - $tpl = $container->View->setPage($page)->outputPage(); - if (defined('PUN_DEBUG')) { - $debug = $container->Debug->debug(); - $debug = $container->View->setPage($debug)->outputPage(); - $tpl = str_replace('', $debug, $tpl); - } - exit($tpl); +if ($page->getDataForOnline(true)) { + $container->Online->handle($page); } +$tpl = $container->View->setPage($page)->outputPage(); +if (defined('PUN_DEBUG')) { + $debug = $container->Debug->debug(); + $debug = $container->View->setPage($debug)->outputPage(); + $tpl = str_replace('', $debug, $tpl); +} +exit($tpl); diff --git a/app/lang/English/login.po b/app/lang/English/auth.po similarity index 64% rename from app/lang/English/login.po rename to app/lang/English/auth.po index 68b563cb..92313c10 100644 --- a/app/lang/English/login.po +++ b/app/lang/English/auth.po @@ -12,6 +12,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Language: en\n" +msgid "Sign in" +msgstr "Sign in" + +msgid "Banned email" +msgstr "The email address you entered is banned in this forum." + +msgid "Account is not activated" +msgstr "Account is not activated." + msgid "Wrong user/pass" msgstr "Wrong username and/or password." @@ -39,11 +48,29 @@ msgstr "Remember me" msgid "Forget mail" msgstr "An email has been sent to the specified address with instructions on how to change your password. If it does not arrive you can contact the forum administrator at %1$s." -msgid "Error mail" -msgstr "When sending email there was an error. Try later or contact the forum administrator at %1$s." - msgid "Email flood" msgstr "This account has already requested a password reset in the past hour. Please wait %s minutes before trying again." msgid "Pass not match" msgstr "Passwords do not match." + +msgid "Change pass" +msgstr "Change password" + +msgid "Change password" +msgstr "Change password" + +msgid "New pass" +msgstr "New password" + +msgid "Confirm new pass" +msgstr "Confirm new password" + +msgid "Pass format" +msgstr "Password must contain the digit, uppercase and lowercase letters, symbol different from the digits and letters." + +msgid "Pass info" +msgstr "Passwords must be at least 8 characters long. Passwords are case sensitive." + +msgid "Pass updated" +msgstr "Your password has been updated. You can now login with your new password." diff --git a/app/lang/English/common.po b/app/lang/English/common.po index 4a5da6da..861efbbe 100644 --- a/app/lang/English/common.po +++ b/app/lang/English/common.po @@ -24,9 +24,15 @@ msgstr "." msgid "lang_thousands_sep" msgstr "," +msgid "Forum rules" +msgstr "Forum rules" + msgid "Redirecting to index" msgstr "Redirecting to Index page." +msgid "Error mail" +msgstr "When sending email there was an error. Please try again later or contact the forum administrator at %1$s." + msgid "Bad token" msgstr "Bad token." @@ -451,7 +457,7 @@ msgid "New reports" msgstr "There are new reports" msgid "Maintenance mode enabled" -msgstr "Maintenance mode is enabled!" +msgstr "" msgid "Size unit B" msgstr "%s B" diff --git a/app/lang/English/mail/new_user.tpl b/app/lang/English/mail/new_user.tpl index 30164e87..6b963930 100644 --- a/app/lang/English/mail/new_user.tpl +++ b/app/lang/English/mail/new_user.tpl @@ -1,12 +1,12 @@ Subject: Alert - New registration -User '' registered in the forums at +User '' registered in the forums at -User profile: +User profile: To administer this account, please visit the following page: -- - Mailer + Mailer (Do not reply to this message) diff --git a/app/lang/English/mail/password_reset.tpl b/app/lang/English/mail/password_reset.tpl index b408927b..fd5fc76c 100644 --- a/app/lang/English/mail/password_reset.tpl +++ b/app/lang/English/mail/password_reset.tpl @@ -2,13 +2,11 @@ Subject: New password requested Hello , -You have requested to have a new password assigned to your account in the discussion forum at . If you didn't request this or if you don't want to change your password you should just ignore this message. Only if you visit the activation page below will your password be changed. - -Your new password is: +You have requested to have a new password assigned to your account in the discussion forum at To change your password, please visit the following page: - + -- - Mailer + Mailer (Do not reply to this message) diff --git a/app/lang/English/mail/welcome.tpl b/app/lang/English/mail/welcome.tpl index 779a5745..f70c2550 100644 --- a/app/lang/English/mail/welcome.tpl +++ b/app/lang/English/mail/welcome.tpl @@ -1,12 +1,11 @@ -Subject: Welcome to ! +Subject: Welcome to ! -Thank you for registering in the forums at . Your account details are: +Thank you for registering in the forums at . -Username: -Password: +Your username: -Login at to activate the account. +Go to activate your account. -- - Mailer + Mailer (Do not reply to this message) diff --git a/app/lang/English/profile.po b/app/lang/English/profile.po index ac94cf20..68973bbf 100644 --- a/app/lang/English/profile.po +++ b/app/lang/English/profile.po @@ -78,6 +78,9 @@ msgstr "New password" msgid "Confirm new pass" msgstr "Confirm new password" +msgid "Pass format" +msgstr "Password must contain the digit, uppercase and lowercase letters, symbol different from the digits and letters." + msgid "Pass info" msgstr "Passwords must be at least 8 characters long. Passwords are case sensitive." diff --git a/app/lang/English/register.po b/app/lang/English/register.po new file mode 100644 index 00000000..e70e3fd9 --- /dev/null +++ b/app/lang/English/register.po @@ -0,0 +1,70 @@ +# +msgid "" +msgstr "" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Project-Id-Version: ForkBB\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: ForkBB \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: en\n" + +msgid "Sign up" +msgstr "Sign up" + +msgid "If no rules" +msgstr "Treat other people the way you want to be treated yourself." + +msgid "Bad agree" +msgstr "ID forms obsolete. Please begin registration again." + +msgid "Reg cancel redirect" +msgstr "No you agree with the forum rules. Registration cancelled. Redirecting …" + +msgid "Registration flood" +msgstr "A new user was registered with the same IP address as you within the last hour. To prevent registration flooding, at least an hour has to pass between registrations from the same IP. Sorry for the inconvenience." + +msgid "Agree" +msgstr "Agree" + +msgid "Username guest" +msgstr "The username guest is reserved. Please choose another username." + +msgid "Username censor" +msgstr "The username you entered contains one or more censored words. Please choose another username." + +msgid "Banned username" +msgstr "The username you entered is banned in this forum. Please choose another username." + +msgid "Username not unique" +msgstr "The username you entered is not unique. Please choose another username." + +msgid "Banned email" +msgstr "The email address you entered is banned in this forum. Please choose another email address." + +msgid "Dupe email" +msgstr "Someone else is already registered with that email address. Please choose another email address." + +msgid "Reg email" +msgstr "Thank you for registering. A letter with a link to activate your account was sent to the specified address. If it doesn't arrive you can contact the forum administrator at %1$s." + +msgid "Reg complete" +msgstr "Registration complete. You can log in the forums." + +msgid "Email info" +msgstr "Please enter a valid email address. It will be used to activate your account." + +msgid "Pass format" +msgstr "Password must contain the digit, uppercase and lowercase letters, symbol different from the digits and letters." + +msgid "Pass info" +msgstr "Passwords must be at least 8 characters long. Passwords are case sensitive." + +msgid "Login format" +msgstr "The username must begin with a letter. May contain letters, numbers, spaces, dots, dashes and underscores." + +msgid "Error welcom mail" +msgstr "When sending email there was an error. Please use the password reset form or contact the forum administrator at %1$s." diff --git a/app/lang/Russian/login.po b/app/lang/Russian/auth.po similarity index 65% rename from app/lang/Russian/login.po rename to app/lang/Russian/auth.po index a94a2d0e..52bdef29 100644 --- a/app/lang/Russian/login.po +++ b/app/lang/Russian/auth.po @@ -12,6 +12,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" +msgid "Sign in" +msgstr "Войти" + +msgid "Banned email" +msgstr "Введенный почтовый адрес заблокирован." + +msgid "Account is not activated" +msgstr "Аккаунт не активирован." + msgid "Wrong user/pass" msgstr "Неверное имя и/или пароль. Имя и пароль чувствительны к регистру!" @@ -39,11 +48,29 @@ msgstr "Запомнить меня" msgid "Forget mail" msgstr "Письмо с инструкцией по изменению пароля было выслано на указанный вами почтовый адрес. Если вы не получите его, свяжитесь с администрацией форума по адресу %1$s." -msgid "Error mail" -msgstr "При отправки письма возникла ошибка. Попробуйте позже или свяжитесь с администратором форума по адресу %1$s." - msgid "Email flood" msgstr "Для этой учетной записи недавно уже запрашивали сброс пароля. Пожалуйста, подождите %s минут, прежде чем повторить попытку." msgid "Pass not match" msgstr "Пароли не совпадают." + +msgid "Change pass" +msgstr "Смена пароля" + +msgid "Change password" +msgstr "Сменить пароль" + +msgid "New pass" +msgstr "Новый пароль" + +msgid "Confirm new pass" +msgstr "Ещё раз" + +msgid "Pass format" +msgstr "Пароль должен содержать цифру, строчную и прописную буквы, символ отличающийся от цифр и букв." + +msgid "Pass info" +msgstr "Пароль должен состоять минимум из 8 символов. Пароль чувствителен к регистру вводимых букв." + +msgid "Pass updated" +msgstr "Ваш пароль изменён. Вы можете войти на форум с новым паролем." diff --git a/app/lang/Russian/common.po b/app/lang/Russian/common.po index bc8b43a5..f109ad3b 100644 --- a/app/lang/Russian/common.po +++ b/app/lang/Russian/common.po @@ -24,9 +24,15 @@ msgstr "." msgid "lang_thousands_sep" msgstr "," +msgid "Forum rules" +msgstr "Правила форума" + msgid "Redirecting to index" msgstr "Перенаправление на главную страницу форума." +msgid "Error mail" +msgstr "При отправке письма возникла ошибка. Пожалуйста, повторите попытку позже или свяжитесь с администратором форума по адресу %1$s." + msgid "Bad request" msgstr "Неверный запрос. Ссылка, по которой вы перешли, является неверной или просроченной." @@ -451,7 +457,7 @@ msgid "New reports" msgstr "Есть новые сигналы" msgid "Maintenance mode enabled" -msgstr "Включен режим обслуживания!" +msgstr "Включен режим обслуживания!" msgid "Size unit B" msgstr "%s байт" diff --git a/app/lang/Russian/mail/new_user.tpl b/app/lang/Russian/mail/new_user.tpl index 94f3923b..a70f0246 100644 --- a/app/lang/Russian/mail/new_user.tpl +++ b/app/lang/Russian/mail/new_user.tpl @@ -1,12 +1,12 @@ Subject: Внимание - Новая регистрация -Пользователь '' зарегистрировался на форуме по адресу +Пользователь '' зарегистрировался на форуме по адресу -Адрес профиля пользователя: +Адрес профиля пользователя: Для управления этой учетной записью, пожалуйста, посетите следующую страницу: -- -Отправитель -(Не отвечайте на это сообщение) \ No newline at end of file +Отправитель +(Не отвечайте на это сообщение) diff --git a/app/lang/Russian/mail/password_reset.tpl b/app/lang/Russian/mail/password_reset.tpl index 3db7b7f4..0ae09262 100644 --- a/app/lang/Russian/mail/password_reset.tpl +++ b/app/lang/Russian/mail/password_reset.tpl @@ -1,14 +1,12 @@ Subject: Запрос на смену пароля -Здравствуйте , +Здравствуйте, . -Кто-то, возможно вы, сделал запрос на смену пароля аккаунта на форуме . Если это не вы или если вы не хотите менять пароль, просто проигнорируйте это письмо. Ваш пароль изменится только если вы посетите активационную ссылку. +Кто-то, возможно вы, сделал запрос на смену пароля аккаунта на форуме -Ваш новый пароль: - -Чтобы установить этот пароль, пожалуйста пройдите по этой ссылке: - +Чтобы сменить пароль, вам нужно перейти по ссылке: + -- -Отправитель -(Не отвечайте на это сообщение) \ No newline at end of file +Отправитель +(Не отвечайте на это сообщение) diff --git a/app/lang/Russian/mail/welcome.tpl b/app/lang/Russian/mail/welcome.tpl index 3e0a895f..a79ab3c5 100644 --- a/app/lang/Russian/mail/welcome.tpl +++ b/app/lang/Russian/mail/welcome.tpl @@ -1,12 +1,11 @@ -Subject: Добро пожаловать на ! +Subject: Добро пожаловать на ! -Добро пожаловать на форум по адресу . Детали вашего аккаунта: +Добро пожаловать на форум по адресу . -Пользователь: -Пароль: +Ваше имя пользователя: -Залогиньтесь на странице чтобы активировать аккаунт. +Перейдите по ссылке чтобы активировать аккаунт. -- -Отправитель +Отправитель (Не отвечайте на это сообщение) diff --git a/app/lang/Russian/profile.po b/app/lang/Russian/profile.po index d18efc4d..f67a5a1e 100644 --- a/app/lang/Russian/profile.po +++ b/app/lang/Russian/profile.po @@ -78,6 +78,9 @@ msgstr "Новый пароль" msgid "Confirm new pass" msgstr "Ещё раз" +msgid "Pass format" +msgstr "Пароль должен содержать цифру, строчную и прописную буквы, символ отличающийся от цифр и букв." + msgid "Pass info" msgstr "Пароль должен состоять минимум из 8 символов. Пароль чувствителен к регистру вводимых букв." diff --git a/app/lang/Russian/register.po b/app/lang/Russian/register.po new file mode 100644 index 00000000..ecce4fcb --- /dev/null +++ b/app/lang/Russian/register.po @@ -0,0 +1,70 @@ +# +msgid "" +msgstr "" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Project-Id-Version: ForkBB\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: ForkBB \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ru\n" + +msgid "Sign up" +msgstr "Зарегистрироваться" + +msgid "If no rules" +msgstr "Относитесь к другим так, как хотите, чтобы относились к вам." + +msgid "Bad agree" +msgstr "Идентификатор формы устарел. Пожалуйста, начните регистрацию заново." + +msgid "Reg cancel redirect" +msgstr "Нет вашего согласия с правилами форума. Регистрация отменена. Переадресация …" + +msgid "Registration flood" +msgstr "Недавно с вашего IP адреса был зарегистрирован новый пользователь. Должно пройти не менее часа до следующей регистрации с этого IP адреса. Извините за неудобства." + +msgid "Agree" +msgstr "Принимаю правила" + +msgid "Username guest" +msgstr "Гость - зарезервированное имя. Пожалуйста, выберите другое имя." + +msgid "Username censor" +msgstr "Выбранное имя пользователя содержит запрещенные слова. Пожалуйста, выберите другое имя." + +msgid "Banned username" +msgstr "Введенное имя пользователя заблокировано. Пожалуйста, выберите другое имя." + +msgid "Username not unique" +msgstr "Выбранное имя пользователя не уникально. Пожалуйста, выберите другое имя." + +msgid "Banned email" +msgstr "Введенный почтовый адрес заблокирован. Пожалуйста, выберите другой адрес." + +msgid "Dupe email" +msgstr "Введенный почтовый адрес уже кем-то используется. Пожалуйста, выберите другой адрес." + +msgid "Reg email" +msgstr "Спасибо за регистрацию. Письмо с ссылкой для активации аккаунта было отправлено на указанный почтовый адрес. Если оно не дойдет, свяжитесь с администратором форума по адресу %1$s." + +msgid "Reg complete" +msgstr "Регистрация завершена. Вы можете войти на форум." + +msgid "Email info" +msgstr "Укажите действующий почтовый адрес. Он будет использован для активации аккаунта." + +msgid "Pass format" +msgstr "Пароль должен содержать цифру, строчную и прописную буквы, символ отличающийся от цифр и букв." + +msgid "Pass info" +msgstr "Пароль должен состоять минимум из 8 символов. Пароль чувствителен к регистру вводимых букв." + +msgid "Login format" +msgstr "Имя пользователя должно начинаться с буквы. Может содержать буквы, цифры, пробел, точку, дефис и знак подчеркивания." + +msgid "Error welcom mail" +msgstr "При отправке письма возникла ошибка. Пожалуйста, воспользуйтесь формой восстановления пароля или свяжитесь с администратором форума по адресу %1$s." diff --git a/app/templates/change_password.tpl b/app/templates/change_password.tpl index 338c3fcb..291c7abb 100644 --- a/app/templates/change_password.tpl +++ b/app/templates/change_password.tpl @@ -1,15 +1,21 @@ @extends('layouts/main') diff --git a/app/templates/layouts/main.tpl b/app/templates/layouts/main.tpl index 710d22ed..f88f75fc 100644 --- a/app/templates/layouts/main.tpl +++ b/app/templates/layouts/main.tpl @@ -1,5 +1,5 @@ - + diff --git a/app/templates/layouts/redirect.tpl b/app/templates/layouts/redirect.tpl index 97fbaa29..65460d5a 100644 --- a/app/templates/layouts/redirect.tpl +++ b/app/templates/layouts/redirect.tpl @@ -1,5 +1,5 @@ - + diff --git a/app/templates/login.tpl b/app/templates/login.tpl index 26663860..da8129a7 100644 --- a/app/templates/login.tpl +++ b/app/templates/login.tpl @@ -1,24 +1,32 @@ @extends('layouts/main')