Mail, Auth, Register, UserMapper, Validator

This commit is contained in:
Visman 2017-03-02 15:45:43 +07:00
parent 841694800d
commit a1f5f61a50
43 changed files with 1621 additions and 444 deletions

View file

@ -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 {
// выход

View file

@ -1,6 +1,6 @@
<?php
/**
* based on code Container https://github.com/artoodetoo/container
* based on Container https://github.com/artoodetoo/container
* by artoodetoo
*/
namespace ForkBB\Core;

View file

@ -1520,6 +1520,11 @@ foreach ($styles as $temp)
'allow_null' => false,
'default' => '\'\''
),
'email_confirmed' => array(
'datatype' => 'TINYINT(1)',
'allow_null' => false,
'default' => '0'
),
'title' => array(
'datatype' => 'VARCHAR(50)',
'allow_null' => true

View file

@ -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'];
}
}

View file

@ -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
) {

View file

@ -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] = '%(?<![\p{L}\p{N}])('.str_replace('\*', '[\p{L}\p{N}]*?', preg_quote($search_for[$i], '%')).')(?![\p{L}\p{N}])%iu';
}
$this->db->free_result($result);
return [
'search_for' => $search_for,
'replace_with' => $replace_with
];
return [$search_for, $replace_with];
}
/**

View file

@ -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;
}
}

View file

@ -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')]);

View file

@ -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'][] = '<a href="' . $this->c->Router->link('AdminOptions', ['#' => 'maintenance']). '">' . __('Maintenance mode enabled') . '</a>';
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;

View file

@ -0,0 +1,251 @@
<?php
namespace ForkBB\Models\Pages;
use ForkBB\Models\Validator;
use ForkBB\Models\User;
class Register extends Page
{
/**
* Имя шаблона
* @var string
*/
protected $nameTpl = 'register';
/**
* Позиция для таблицы онлайн текущего пользователя
* @var null|string
*/
protected $onlinePos = 'register';
/**
* Указатель на активный пункт навигации
* @var string
*/
protected $index = 'register';
/**
* Обработчик регистрации
* @retrun Page
*/
public function reg()
{
$this->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,
]);
}
}

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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');
}
}

View file

@ -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('<!-- debuginfo -->', $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('<!-- debuginfo -->', $debug, $tpl);
}
exit($tpl);

View file

@ -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 <a href=\"mailto:%1$s\">%1$s</a>."
msgid "Error mail"
msgstr "When sending email there was an error. Try later or contact the forum administrator at <a href=\"mailto:%1$s\">%1$s</a>."
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."

View file

@ -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 <a href=\"mailto:%1$s\">%1$s</a>."
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 "<a href=\"%s\"Maintenance mode is enabled!</a>"
msgid "Size unit B"
msgstr "%s B"

View file

@ -1,12 +1,12 @@
Subject: Alert - New registration
User '<username>' registered in the forums at <base_url>
User '<username>' registered in the forums at <fRootLink>
User profile: <profile_url>
User profile: <userLink>
To administer this account, please visit the following page:
<admin_url>
--
<board_mailer> Mailer
<fMailer> Mailer
(Do not reply to this message)

View file

@ -2,13 +2,11 @@ Subject: New password requested
Hello <username>,
You have requested to have a new password assigned to your account in the discussion forum at <base_url>. 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: <new_password>
You have requested to have a new password assigned to your account in the discussion forum at <fRootLink>
To change your password, please visit the following page:
<activation_url>
<link>
--
<board_mailer> Mailer
<fMailer> Mailer
(Do not reply to this message)

View file

@ -1,12 +1,11 @@
Subject: Welcome to <board_title>!
Subject: Welcome to <fTitle>!
Thank you for registering in the forums at <base_url>. Your account details are:
Thank you for registering in the forums at <fRootLink>.
Username: <username>
Password: <password>
Your username: <username>
Login at <login_url> to activate the account.
Go <link> to activate your account.
--
<board_mailer> Mailer
<fMailer> Mailer
(Do not reply to this message)

View file

@ -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."

View file

@ -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 <mio.visman@yandex.ru>\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 <a href=\"%s\">registration</a> 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 <a href=\"mailto:%1$s\">%1$s</a>."
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 <a href=\"mailto:%1$s\">%1$s</a>."

View file

@ -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 "Письмо с инструкцией по изменению пароля было выслано на указанный вами почтовый адрес. Если вы не получите его, свяжитесь с администрацией форума по адресу <a href=\"mailto:%1$s\">%1$s</a>."
msgid "Error mail"
msgstr "При отправки письма возникла ошибка. Попробуйте позже или свяжитесь с администратором форума по адресу <a href=\"mailto:%1$s\">%1$s</a>."
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 "Ваш пароль изменён. Вы можете войти на форум с новым паролем."

View file

@ -24,9 +24,15 @@ msgstr "."
msgid "lang_thousands_sep"
msgstr ","
msgid "Forum rules"
msgstr "Правила форума"
msgid "Redirecting to index"
msgstr "Перенаправление на главную страницу форума."
msgid "Error mail"
msgstr "При отправке письма возникла ошибка. Пожалуйста, повторите попытку позже или свяжитесь с администратором форума по адресу <a href=\"mailto:%1$s\">%1$s</a>."
msgid "Bad request"
msgstr "Неверный запрос. Ссылка, по которой вы перешли, является неверной или просроченной."
@ -451,7 +457,7 @@ msgid "New reports"
msgstr "Есть новые сигналы"
msgid "Maintenance mode enabled"
msgstr "Включен режим обслуживания!"
msgstr "<a href=\"%s\">Включен режим обслуживания!</a>"
msgid "Size unit B"
msgstr "%s байт"

View file

@ -1,12 +1,12 @@
Subject: Внимание - Новая регистрация
Пользователь '<username>' зарегистрировался на форуме по адресу <base_url>
Пользователь '<username>' зарегистрировался на форуме по адресу <fRootLink>
Адрес профиля пользователя: <profile_url>
Адрес профиля пользователя: <userLink>
Для управления этой учетной записью, пожалуйста, посетите следующую страницу:
<admin_url>
--
Отправитель <board_mailer>
(Не отвечайте на это сообщение)
Отправитель <fMailer>
(Не отвечайте на это сообщение)

View file

@ -1,14 +1,12 @@
Subject: Запрос на смену пароля
Здравствуйте <username>,
Здравствуйте, <username>.
Кто-то, возможно вы, сделал запрос на смену пароля аккаунта на форуме <base_url>. Если это не вы или если вы не хотите менять пароль, просто проигнорируйте это письмо. Ваш пароль изменится только если вы посетите активационную ссылку.
Кто-то, возможно вы, сделал запрос на смену пароля аккаунта на форуме <fRootLink>
Ваш новый пароль: <new_password>
Чтобы установить этот пароль, пожалуйста пройдите по этой ссылке:
<activation_url>
Чтобы сменить пароль, вам нужно перейти по ссылке:
<link>
--
Отправитель <board_mailer>
(Не отвечайте на это сообщение)
Отправитель <fMailer>
(Не отвечайте на это сообщение)

View file

@ -1,12 +1,11 @@
Subject: Добро пожаловать на <board_title>!
Subject: Добро пожаловать на <fTitle>!
Добро пожаловать на форум по адресу <base_url>. Детали вашего аккаунта:
Добро пожаловать на форум по адресу <fRootLink>.
Пользователь: <username>
Пароль: <password>
Ваше имя пользователя: <username>
Залогиньтесь на странице <login_url> чтобы активировать аккаунт.
Перейдите по ссылке <link> чтобы активировать аккаунт.
--
Отправитель <board_mailer>
Отправитель <fMailer>
(Не отвечайте на это сообщение)

View file

@ -78,6 +78,9 @@ msgstr "Новый пароль"
msgid "Confirm new pass"
msgstr "Ещё раз"
msgid "Pass format"
msgstr "Пароль должен содержать цифру, строчную и прописную буквы, символ отличающийся от цифр и букв."
msgid "Pass info"
msgstr "Пароль должен состоять минимум из 8 символов. Пароль чувствителен к регистру вводимых букв."

View file

@ -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 <mio.visman@yandex.ru>\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 "Идентификатор формы устарел. Пожалуйста, начните <a href=\"%s\">регистрацию</a> заново."
msgid "Reg cancel redirect"
msgstr "Нет вашего согласия с правилами форума. Регистрация отменена. Переадресация &hellip;"
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 "Спасибо за регистрацию. Письмо с ссылкой для активации аккаунта было отправлено на указанный почтовый адрес. Если оно не дойдет, свяжитесь с администратором форума по адресу <a href=\"mailto:%1$s\">%1$s</a>."
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 "При отправке письма возникла ошибка. Пожалуйста, воспользуйтесь формой восстановления пароля или свяжитесь с администратором форума по адресу <a href=\"mailto:%1$s\">%1$s</a>."

View file

@ -1,15 +1,21 @@
@extends('layouts/main')
<section class="f-main f-login">
<div class="f-wdiv">
<div class="f-lrdiv">
<h2>{!! __('Change pass') !!}</h2>
<form class="f-form" method="post" action="{!! $formAction !!}">
<input type="hidden" name="token" value="{!! $formToken !!}">
<label class="f-child1" for="id-password">{!! __('New pass') !!}</label>
<input required id="id-password" type="password" name="password" pattern=".{8,}" autofocus="autofocus" tabindex="1">
<label class="f-child1" for="id-password2">{!! __('Confirm new pass') !!}</label>
<input required id="id-password2" type="password" name="password2" pattern=".{8,}" tabindex="2">
<label class="f-child2">{!! __('Pass info') !!}</label>
<input class="f-btn" type="submit" name="login" value="{!! __('Save') !!}" tabindex="3">
<div>
<label class="f-child1 f-req" for="id-password">{!! __('New pass') !!}</label>
<input required class="f-ctrl" id="id-password" type="password" name="password" pattern=".{8,}" autofocus="autofocus" tabindex="1">
</div>
<div>
<label class="f-child1 f-req" for="id-password2">{!! __('Confirm new pass') !!}</label>
<input required class="f-ctrl" id="id-password2" type="password" name="password2" pattern=".{8,}" tabindex="2">
<label class="f-child4">{!! __('Pass format') !!} {!! __('Pass info') !!}</label>
</div>
<div>
<input class="f-btn" type="submit" name="login" value="{!! __('Change password') !!}" tabindex="3">
</div>
</form>
</div>
</section>

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="{!! $fLang !!}" dir="{!! $fDirection !!}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="{!! $fLang !!} dir="{!! $fDirection !!}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

View file

@ -1,24 +1,32 @@
@extends('layouts/main')
<section class="f-main f-login">
<div class="f-wdiv">
<div class="f-lrdiv">
<h2>{!! __('Login') !!}</h2>
<form class="f-form" method="post" action="{!! $formAction !!}">
<input type="hidden" name="token" value="{!! $formToken !!}">
<input type="hidden" name="redirect" value="{{ $formRedirect }}">
<label class="f-child1" for="id-username">{!! __('Username') !!}</label>
<input required id="id-username" type="text" name="username" value="{{ $username }}" maxlength="25" autofocus="autofocus" spellcheck="false" tabindex="1">
<label class="f-child1" for="id-password">{!! __('Password') !!}<a class="f-forgetlink" href="{!! $forgetLink !!}" tabindex="5">{!! __('Forgotten pass') !!}</a></label>
<input required id="id-password" type="password" name="password" tabindex="2">
@if($formSave)
<input type="hidden" name="redirect" value="{{ $redirect }}">
<div>
<label class="f-child1 f-req" for="id-username">{!! __('Username') !!}</label>
<input required class="f-ctrl" id="id-username" type="text" name="username" value="{{ $username }}" maxlength="25" autofocus="autofocus" spellcheck="false" tabindex="1">
</div>
<div>
<label class="f-child1 f-req" for="id-password">{!! __('Password') !!}<a class="f-forgetlink" href="{!! $forgetLink !!}" tabindex="5">{!! __('Forgotten pass') !!}</a></label>
<input required class="f-ctrl" id="id-password" type="password" name="password" tabindex="2">
</div>
<div>
@if($save)
<label class="f-child2"><input type="checkbox" name="save" value="1" tabindex="3" checked="checked">{!! __('Remember me') !!}</label>
@else
<label class="f-child2"><input type="checkbox" name="save" value="1" tabindex="3">{!! __('Remember me') !!}</label>
@endif
<input class="f-btn" type="submit" name="login" value="{!! __('Login') !!}" tabindex="4">
</div>
<div>
<input class="f-btn" type="submit" name="login" value="{!! __('Sign in') !!}" tabindex="4">
</div>
</form>
</div>
@if($regLink)
<div class="f-wdiv">
<div class="f-lrdiv">
<p class="f-child3"><a href="{!! $regLink !!}" tabindex="6">{!! __('Not registered') !!}</a></p>
</div>
@endif

View file

@ -1,13 +1,17 @@
@extends('layouts/main')
<section class="f-main f-login">
<div class="f-wdiv">
<div class="f-lrdiv">
<h2>{!! __('Password reset') !!}</h2>
<form class="f-form" method="post" action="{!! $formAction !!}">
<input type="hidden" name="token" value="{!! $formToken !!}">
<label class="f-child1" for="id-email">{!! __('Email') !!}</label>
<input required id="id-email" type="text" name="email" value="{{ $email }}" maxlength="80" autofocus="autofocus" spellcheck="false" tabindex="1">
<label class="f-child2">{!! __('Password reset info') !!}</label>
<input class="f-btn" type="submit" name="submit" value="{!! __('Submit') !!}" tabindex="2">
<div>
<label class="f-child1 f-req" for="id-email">{!! __('Email') !!}</label>
<input required class="f-ctrl" id="id-email" type="text" name="email" value="{{ $email }}" maxlength="80" pattern=".+@.+" autofocus="autofocus" spellcheck="false" tabindex="1">
<label class="f-child4">{!! __('Password reset info') !!}</label>
</div>
<div>
<input class="f-btn" type="submit" name="submit" value="{!! __('Send email') !!}" tabindex="2">
</div>
</form>
</div>
</section>

View file

@ -0,0 +1,29 @@
@extends('layouts/main')
<section class="f-main f-register">
<div class="f-lrdiv">
<h2>{!! __('Register') !!}</h2>
<form class="f-form" method="post" action="{!! $formAction !!}">
<input type="hidden" name="token" value="{!! $formToken !!}">
<input type="hidden" name="agree" value="{!! $agree !!}">
<input type="hidden" name="on" value="{!! $on !!}">
<div>
<label class="f-child1 f-req" for="id-email">{!! __('Email') !!}</label>
<input required class="f-ctrl" id="id-email" type="text" name="email" value="{{ $email }}" maxlength="80" pattern=".+@.+" autofocus="autofocus" spellcheck="false" tabindex="1">
<label class="f-child4 f-fhint">{!! __('Email info') !!}</label>
</div>
<div>
<label class="f-child1 f-req" for="id-username">{!! __('Username') !!}</label>
<input required class="f-ctrl" id="id-username" type="text" name="username" value="{{ $username }}" maxlength="25" pattern=".{2,25}" spellcheck="false" tabindex="2">
<label class="f-child4 f-fhint">{!! __('Login format') !!}</label>
</div>
<div>
<label class="f-child1 f-req" for="id-password">{!! __('Password') !!}</label>
<input required class="f-ctrl" id="id-password" type="password" name="password" pattern=".{8,}" tabindex="3">
<label class="f-child4 f-fhint">{!! __('Pass format') !!} {!! __('Pass info') !!}</label>
</div>
<div>
<input class="f-btn" type="submit" name="register" value="{!! __('Sign up') !!}" tabindex="5">
</div>
</form>
</div>
</section>

View file

@ -1,5 +1,18 @@
@extends('layouts/main')
<section class="f-main f-rules">
<h2>{!! __('Forum rules') !!}</h2>
<div>{!! $Rules !!}</div>
<h2>{!! $title !!}</h2>
<div id="id-rules">{!! $rules !!}</div>
@if($formAction)
<div class="f-lrdiv">
<form class="f-form" method="post" action="{!! $formAction !!}">
<input type="hidden" name="token" value="{!! $formToken !!}">
<div>
<label class="f-child2"><input type="checkbox" name="agree" value="{!! $formHash !!}" tabindex="1">{!! __('Agree') !!}</label>
</div>
<div>
<input class="f-btn" type="submit" name="register" value="{!! __('Register') !!}" tabindex="2">
</div>
</form>
</div>
@endif
</section>

View file

@ -11,6 +11,11 @@
"homepage": "https://github.com/MioVisman"
}
],
"autoload": {
"psr-4": {
"ForkBB\\": "app/"
}
},
"require": {
"php": ">=5.6.0",
"artoodetoo/dirk": "dev-master"

4
composer.lock generated
View file

@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "6c899de021c8c6537d723a4f7d26f06d",
"content-hash": "b734e8bdbede86eb0dbd047bb31d3a03",
"hash": "ad22a23d7b225fa300553d8d0dcdaeb9",
"content-hash": "2eea8744cdbc34c8408e6d137176a8df",
"packages": [
{
"name": "artoodetoo/dirk",

View file

@ -669,6 +669,7 @@ switch ($stage)
$db->alter_field('users', 'password', 'VARCHAR(255)', false, '') or error('Unable to alter password field', __FILE__, __LINE__, $db->error());
$db->add_field('user', 'u_mark_all_read', 'INT(10) UNSIGNED', true) or error('Unable to add u_mark_all_read field', __FILE__, __LINE__, $db->error());
$db->add_field('user', 'email_confirmed', 'TINYINT(1)', false, 0, 'email') or error('Unable to add email_confirmed field', __FILE__, __LINE__, $db->error());
$db->add_field('online', 'o_position', 'VARCHAR(100)', false, '') or error('Unable to add o_position field', __FILE__, __LINE__, $db->error());
$db->add_index('online', 'o_position_idx', array('o_position')) or error('Unable to add o_position_idx index', __FILE__, __LINE__, $db->error());

View file

@ -706,15 +706,11 @@ function delete_post($post_id, $topic_id)
function censor_words($text)
{
global $container;
static $search_for, $replace_with;
// If not already built in a previous call, build an array of censor words and their replacement text
if (!isset($search_for)) {
list($search_for, $replace_with) = $container->get('censoring');
}
list($search_for, $replace_with) = $container->censoring;
if (!empty($search_for)) {
$text = substr(ucp_preg_replace($search_for, $replace_with, ' '.$text.' '), 1, -1);
$text = preg_replace($search_for, $replace_with, $text);
}
return $text;

View file

@ -194,6 +194,10 @@ select {
content: ", ";
}
.f-req:after {
content: " *";
color: red;
}
/* Меню */
@ -510,7 +514,7 @@ select {
padding: 0.625rem;
}
.f-rules > div {
#id-rules {
padding: 0.625rem;
border: 0.0625rem solid #AA7939;
background-color: #F8F4E3;
@ -992,26 +996,27 @@ li + li .f-btn {
}
}
/*********/
/* Логин */
/*********/
.f-login .f-wdiv {
/*********************/
/* Логин/Регистрация */
/*********************/
.f-lrdiv {
margin: 1rem auto;
max-width: 20rem;
border: 0.0625rem solid #AA7939;
border-radius: 0.1875rem;
}
.f-login h2 {
.f-lrdiv h2 {
padding: 0.625rem;
text-align: center;
}
.f-login .f-form {
.f-lrdiv .f-form {
padding: 0.625rem;
}
.f-login .f-form > input {
.f-lrdiv .f-ctrl,
.f-lrdiv .f-btn {
padding: 0.5rem;
font-size: 1rem;
display: block;
@ -1020,7 +1025,10 @@ li + li .f-btn {
box-sizing: border-box;
}
.f-login .f-form > label {
.f-lrdiv .f-child1,
.f-lrdiv .f-child2,
.f-lrdiv .f-child3,
.f-lrdiv .f-child4 {
display: block;
width: 100%;
}
@ -1031,28 +1039,53 @@ li + li .f-btn {
font-weight: normal;
}
.f-login .f-child1 {
.f-lrdiv .f-child1 {
font-weight: bold;
}
.f-login .f-child1:after {
content: ":";
}
.f-login .f-child2 {
.f-lrdiv .f-child2 {
font-size: 0.875rem;
}
.f-login .f-child2 > input {
.f-lrdiv .f-child2 > input {
margin: 0 0.625rem 0 0;
}
.f-login .f-btn {
.f-lrdiv .f-btn {
margin: 1rem 0 0.375rem 0;
}
.f-login .f-child3 {
.f-lrdiv .f-child3 {
padding: 0.625rem;
text-align: center;
font-size: 0.875rem;
}
.f-lrdiv .f-child4 {
font-size: 0.8125rem;
text-align: justify;
}
.f-ctrl {
border: 0.0625rem solid #AA7939;
}
.f-ctrl:focus {
box-shadow: inset 0px 0px 0.25rem 0 rgba(170,121,57,0.5), 0px 0px 0.25rem 0 rgba(170,121,57,0.5);
}
.f-ctrl + .f-fhint {
overflow: hidden;
max-height: 0;
-webkit-transition: max-height 0.5s, margin 0.5s;
-webkit-transition-delay: 0.2s;
transition: max-height 0.5s, margin 0.5s;
transition-delay: 0.2s;
}
.f-ctrl:focus + .f-fhint,
.f-ctrl:active + .f-fhint {
margin-top: -0.375rem;
margin-bottom: 1rem;
max-height: 1000rem;
}

View file

@ -7,4 +7,5 @@ $baseDir = dirname($vendorDir);
return array(
'R2\\Templating\\' => array($vendorDir . '/artoodetoo/dirk/src'),
'ForkBB\\' => array($baseDir . '/app'),
);

View file

@ -11,6 +11,10 @@ class ComposerStaticInit90ad93c7251d4f60daa9e545879c49e7
array (
'R2\\Templating\\' => 14,
),
'F' =>
array (
'ForkBB\\' => 7,
),
);
public static $prefixDirsPsr4 = array (
@ -18,6 +22,10 @@ class ComposerStaticInit90ad93c7251d4f60daa9e545879c49e7
array (
0 => __DIR__ . '/..' . '/artoodetoo/dirk/src',
),
'ForkBB\\' =>
array (
0 => __DIR__ . '/../..' . '/app',
),
);
public static function getInitializer(ClassLoader $loader)