瀏覽代碼

2018-03-04

Visman 7 年之前
父節點
當前提交
244e1649e9

+ 6 - 6
app/Controllers/Routing.php

@@ -77,7 +77,7 @@ class Routing
             // юзеры
             if ($user->g_view_users == '1') {
                 // список пользователей
-                $r->add('GET', '/userlist[/{page:[1-9]\d*}]', 'Userlist:view', 'Userlist');
+                $r->add('GET', '/userlist[/{sort:username|registered|num_posts}/{dir:ASC|DESC}/{group:\-1|[1-9]\d*}/{name}][/{page:[1-9]\d*}]', 'Userlist:view', 'Userlist');
                 // юзеры
                 $r->add('GET', '/user/{id:[1-9]\d*}/{name}', 'Profile:view', 'User'); //????
             }
@@ -127,10 +127,10 @@ class Routing
         }
 
         $uri = $_SERVER['REQUEST_URI'];
-        if (($pos = strpos($uri, '?')) !== false) {
-            $uri = substr($uri, 0, $pos);
+        if (($pos = \strpos($uri, '?')) !== false) {
+            $uri = \substr($uri, 0, $pos);
         }
-        $uri    = rawurldecode($uri);
+        $uri    = \rawurldecode($uri);
         $method = $_SERVER['REQUEST_METHOD'];
 
         $route = $r->route($method, $uri);
@@ -138,7 +138,7 @@ class Routing
         switch ($route[0]) {
             case $r::OK:
                 // ... 200 OK
-                list($page, $action) = explode(':', $route[1], 2);
+                list($page, $action) = \explode(':', $route[1], 2);
                 $page = $this->c->$page->$action($route[2], $method);
                 break;
             case $r::NOT_FOUND:
@@ -151,7 +151,7 @@ class Routing
                 break;
             case $r::METHOD_NOT_ALLOWED:
                 // ... 405 Method Not Allowed
-                $page = $this->c->Message->message('Bad request', true, 405, ['Allow: ' . implode(',', $route[1])]);
+                $page = $this->c->Message->message('Bad request', true, 405, ['Allow: ' . \implode(',', $route[1])]);
                 break;
             case $r::NOT_IMPLEMENTED:
                 // ... 501 Not implemented

+ 32 - 32
app/Core/Router.php

@@ -77,9 +77,9 @@ class Router
     public function __construct($base)
     {
         $this->baseUrl = $base;
-        $this->host    = parse_url($base, PHP_URL_HOST);
-        $this->prefix  = parse_url($base, PHP_URL_PATH);
-        $this->length  = strlen($this->prefix);
+        $this->host    = \parse_url($base, PHP_URL_HOST);
+        $this->prefix  = \parse_url($base, PHP_URL_PATH);
+        $this->length  = \strlen($this->prefix);
     }
 
     /**
@@ -93,9 +93,9 @@ class Router
      */
     public function validate($url, $defMarker, array $defArgs = [])
     {
-        if (is_string($url)
-            && parse_url($url, PHP_URL_HOST) === $this->host
-            && ($route = $this->route('GET', rawurldecode(parse_url($url, PHP_URL_PATH))))
+        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
         ) {
             if (isset($route[3])) {
@@ -119,7 +119,7 @@ class Router
     public function link($marker = null, array $args = [])
     {
         $result = $this->baseUrl;
-        $anchor = isset($args['#']) ? '#' . rawurlencode($args['#']) : '';
+        $anchor = isset($args['#']) ? '#' . \rawurlencode($args['#']) : '';
 
         // маркер пустой
         if (null === $marker) {
@@ -128,7 +128,7 @@ class Router
         } elseif (! isset($this->links[$marker])) {
             return $result . '/';
         // ссылка статична
-        } elseif (is_string($data = $this->links[$marker])) {
+        } elseif (\is_string($data = $this->links[$marker])) {
             return $result . $data . $anchor;
         }
 
@@ -140,7 +140,7 @@ class Router
             if (isset($args[$name])) {
                 // кроме page = 1
                 if ($name !== 'page' || $args[$name] !== 1) {
-                    $data['{' . $name . '}'] = rawurlencode(str_replace($this->subSearch, $this->subRepl, $args[$name]));
+                    $data['{' . $name . '}'] = \rawurlencode(\str_replace($this->subSearch, $this->subRepl, $args[$name]));
                     continue;
                 }
             }
@@ -151,12 +151,12 @@ class Router
             // значение не обязательно
             } else {
 //                $link = preg_replace('%\[[^\[\]{}]*{' . preg_quote($name, '%') . '}[^\[\]{}]*\]%', '', $link);
-                $link = preg_replace('%\[[^\[\]]*?{' . preg_quote($name, '%') . '}[^\[\]]*+(\[((?>[^\[\]]*+)|(?1))+\])*?\]%', '', $link);
+                $link = \preg_replace('%\[[^\[\]]*?{' . \preg_quote($name, '%') . '}[^\[\]]*+(\[((?>[^\[\]]*+)|(?1))+\])*?\]%', '', $link);
             }
         }
-        $link = str_replace(['[', ']'], '', $link);
+        $link = \str_replace(['[', ']'], '', $link);
 
-        return $result . strtr($link, $data) . $anchor;
+        return $result . \strtr($link, $data) . $anchor;
     }
 
     /**
@@ -176,8 +176,8 @@ class Router
         }
 
         if ($this->length) {
-            if (0 === strpos($uri, $this->prefix)) {
-                $uri = substr($uri, $this->length);
+            if (0 === \strpos($uri, $this->prefix)) {
+                $uri = \substr($uri, $this->length);
             } else {
                 return [self::NOT_FOUND];
             }
@@ -193,16 +193,16 @@ class Router
                 list($handler, $marker) = $this->statical[$uri]['GET'];
                 return [self::OK, $handler, [], $marker];
             } else {
-                $allowed = array_keys($this->statical[$uri]);
+                $allowed = \array_keys($this->statical[$uri]);
             }
         }
 
-        $pos = strpos($uri, '/', 1);
-        $base = false === $pos ? $uri : substr($uri, 0, $pos);
+        $pos = \strpos($uri, '/', 1);
+        $base = false === $pos ? $uri : \substr($uri, 0, $pos);
 
         if (isset($this->dynamic[$base])) {
             foreach ($this->dynamic[$base] as $pattern => $data) {
-                if (! preg_match($pattern, $uri, $matches)) {
+                if (! \preg_match($pattern, $uri, $matches)) {
                     continue;
                 }
 
@@ -211,14 +211,14 @@ class Router
                 } elseif ($head && isset($data['GET'])) {
                     list($handler, $keys, $marker) = $data['GET'];
                 } else {
-                    $allowed += array_keys($data);
+                    $allowed += \array_keys($data);
                     continue;
                 }
 
                 $args = [];
                 foreach ($keys as $key) {
-                    if (isset($matches[$key])) {
-                        $args[$key] = str_replace($this->subRepl, $this->subSearch, $matches[$key]);
+                    if (isset($matches[$key])) { // ???? может isset($matches[$key]{0}) тут поставить?
+                        $args[$key] = isset($matches[$key]{0}) ? \str_replace($this->subRepl, $this->subSearch, $matches[$key]) : null;
                     }
                 }
                 return [self::OK, $handler, $args, $marker];
@@ -241,7 +241,7 @@ class Router
      */
     public function add($method, $route, $handler, $marker = null)
     {
-        if (is_array($method)) {
+        if (\is_array($method)) {
             foreach ($method as $m) {
                 $this->methods[$m] = 1;
             }
@@ -251,14 +251,14 @@ class Router
 
         $link   = $route;
         $anchor = '';
-        if (false !== strpos($route, '#')) {
-            list($route, $anchor) = explode('#', $route, 2);
+        if (false !== \strpos($route, '#')) {
+            list($route, $anchor) = \explode('#', $route, 2);
             $anchor = '#' . $anchor;
         }
 
-        if (false === strpbrk($route, '{}[]')) {
+        if (false === \strpbrk($route, '{}[]')) {
             $data = null;
-            if (is_array($method)) {
+            if (\is_array($method)) {
                 foreach ($method as $m) {
                     $this->statical[$route][$m] = [$handler, $marker];
                 }
@@ -270,7 +270,7 @@ class Router
             if (false === $data) {
                 throw new InvalidArgumentException('Route is incorrect');
             }
-            if (is_array($method)) {
+            if (\is_array($method)) {
                 foreach ($method as $m) {
                     $this->dynamic[$data[0]][$data[1]][$m] = [$handler, $data[2], $marker];
                 }
@@ -297,7 +297,7 @@ class Router
      */
     protected function parse($route)
     {
-        $parts = preg_split('%([\[\]{}/])%', $route, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
+        $parts = \preg_split('%([\[\]{}/])%', $route, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
 
         $s = 1;
         $base = $parts[0];
@@ -325,11 +325,11 @@ class Router
                     case '{':
                         return false;
                     case '}':
-                        $data = explode(':', $buffer, 2);
+                        $data = \explode(':', $buffer, 2);
                         if (! isset($data[1])) {
                             $data[1] = '[^/\x00-\x1f]+';
                         }
-                        if ($data[0] === '' || $data[1] === '' || is_numeric($data[0]{0})) {
+                        if ($data[0] === '' || $data[1] === '' || \is_numeric($data[0]{0})) {
                             return false;
                         }
                         $pattern .= '(?P<' . $data[0] . '>' . $data[1] . ')';
@@ -346,7 +346,7 @@ class Router
                 switch ($part) {
                     case '/':
                         $first    = false;
-                        $pattern .= preg_quote($part, '%');
+                        $pattern .= \preg_quote($part, '%');
                         $temp    .= $part;
                         break;
                     default:
@@ -376,7 +376,7 @@ class Router
                     case '}':
                         return false;
                     default:
-                        $pattern .= preg_quote($part, '%');
+                        $pattern .= \preg_quote($part, '%');
                         $temp    .= $part;
                 }
             }

+ 47 - 47
app/Core/Validator.php

@@ -144,7 +144,7 @@ class Validator
      */
     public function addValidators(array $validators)
     {
-        $this->validators = array_replace($this->validators, $validators);
+        $this->validators = \array_replace($this->validators, $validators);
         return $this;
     }
 
@@ -162,13 +162,13 @@ class Validator
         foreach ($list as $field => $raw) {
             $suffix = null;
             // правило для элементов массива
-            if (strpos($field, '.') > 0) {
-                list($field, $suffix) = explode('.', $field, 2);
+            if (\strpos($field, '.') > 0) {
+                list($field, $suffix) = \explode('.', $field, 2);
             }
             $rules = [];
             // перебор правил для текущего поля
-            foreach (explode('|', $raw) as $rule) { //???? нужно экоанирование для разделителей
-                 $tmp = explode(':', $rule, 2);
+            foreach (\explode('|', $raw) as $rule) { //???? нужно экоанирование для разделителей
+                 $tmp = \explode(':', $rule, 2);
                  if (empty($this->validators[$tmp[0]])) {
                      throw new RuntimeException($tmp[0] . ' validator not found');
                  }
@@ -193,7 +193,7 @@ class Validator
      */
     public function addArguments(array $arguments)
     {
-        $this->arguments = array_replace($this->arguments, $arguments);
+        $this->arguments = \array_replace($this->arguments, $arguments);
         return $this;
     }
 
@@ -206,7 +206,7 @@ class Validator
      */
     public function addMessages(array $messages)
     {
-        $this->messages = array_replace($this->messages, $messages);
+        $this->messages = \array_replace($this->messages, $messages);
         return $this;
     }
 
@@ -219,7 +219,7 @@ class Validator
      */
     public function addAliases(array $aliases)
     {
-        $this->aliases = array_replace($this->aliases, $aliases);
+        $this->aliases = \array_replace($this->aliases, $aliases);
         return $this;
     }
 
@@ -318,7 +318,7 @@ class Validator
 
             $value = $this->validators[$validator]($this, $value, $attr, $this->getArguments($field, $validator));
 
-            array_pop($this->curData);
+            \array_pop($this->curData);
 
             if (null !== $this->error) {
                 break;
@@ -337,7 +337,7 @@ class Validator
      */
     public function addError($error, $type = 'v')
     {
-        if (empty($vars = end($this->curData))) {
+        if (empty($vars = \end($this->curData))) {
             throw new RuntimeException('The array of variables is empty');
         }
 
@@ -347,7 +347,7 @@ class Validator
             return;
         }
 
-        extract($vars);
+        \extract($vars);
 
         // псевдоним имени поля
         $alias = isset($this->aliases[$field]) ? $this->aliases[$field] : $field;
@@ -358,7 +358,7 @@ class Validator
         } elseif (isset($this->messages[$field])) {
             $error = $this->messages[$field];
         }
-        if (is_array($error)) {
+        if (\is_array($error)) {
             $type = $error[1];
             $error = $error[0];
         }
@@ -441,11 +441,11 @@ class Validator
 
     protected function vRequired($v, $value)
     {
-        if (is_string($value)) {
-            if (strlen(preg_replace('%^\s+|\s+$%u', '', $value)) > 0) {
+        if (\is_string($value)) {
+            if (\strlen(\preg_replace('%^\s+|\s+$%u', '', $value)) > 0) {
                 return $value;
             }
-        } elseif (is_array($value)) {
+        } elseif (\is_array($value)) {
             if (! empty($value)) {
                 return $value;
             }
@@ -458,7 +458,7 @@ class Validator
 
     protected function vRequiredWith($v, $value, $attr)  //???????????????????????
     {
-        foreach (explode(',', $attr) as $field) {
+        foreach (\explode(',', $attr) as $field) {
             if (null !== $this->__get($field)) {     // если есть хотя бы одно поле,
                 return $this->vRequired($v, $value); // то проверяем данное поле
             }                                        // на обязательное наличие
@@ -480,17 +480,17 @@ class Validator
     {
         if (null === $value) {
             return null;
-        } elseif (is_string($value)) {
-            foreach(explode(',', $attr) as $action) {
+        } elseif (\is_string($value)) {
+            foreach(\explode(',', $attr) as $action) {
                 switch ($action) {
                     case 'trim':
-                        $value = preg_replace('%^\s+|\s+$%u', '', $value);
+                        $value = \preg_replace('%^\s+|\s+$%u', '', $value);
                         break;
                     case 'lower':
-                        $value = mb_strtolower($value, 'UTF-8');
+                        $value = \mb_strtolower($value, 'UTF-8');
                         break;
                     case 'spaces':
-                        $value = preg_replace('%\s+%u', ' ', $value);
+                        $value = \preg_replace('%\s+%u', ' ', $value);
                         break;
                 }
             }
@@ -505,7 +505,7 @@ class Validator
     {
         if (null === $value) {
             return null;
-        } elseif (is_numeric($value)) {
+        } elseif (\is_numeric($value)) {
             return 0 + $value;
         } else {
             $this->addError('The :alias must be numeric');
@@ -517,7 +517,7 @@ class Validator
     {
         if (null === $value) {
             return null;
-        } elseif (is_numeric($value) && is_int(0 + $value)) {
+        } elseif (\is_numeric($value) && \is_int(0 + $value)) {
             return (int) $value;
         } else {
             $this->addError('The :alias must be integer');
@@ -527,14 +527,14 @@ class Validator
 
     protected function vArray($v, $value, $attr)
     {
-        if (null !== $value && ! is_array($value)) {
+        if (null !== $value && ! \is_array($value)) {
             $this->addError('The :alias must be array');
             return null;
         } elseif (! $attr) {
             return $value;
         }
 
-        if (empty($vars = end($this->curData))) {
+        if (empty($vars = \end($this->curData))) {
             throw new RuntimeException('The array of variables is empty');
         }
 
@@ -547,12 +547,12 @@ class Validator
 
     protected function recArray(&$value, &$result, $name, $rules, $field)
     {
-        $idxs = explode('.', $name);
-        $key  = array_shift($idxs);
-        $name = implode('.', $idxs);
+        $idxs = \explode('.', $name);
+        $key  = \array_shift($idxs);
+        $name = \implode('.', $idxs);
 
         if ('*' === $key) {
-            if (! is_array($value)) {
+            if (! \is_array($value)) {
                 return; //????
             }
 
@@ -564,7 +564,7 @@ class Validator
                 }
             }
         } else {
-            if (! array_key_exists($key, $value)) {
+            if (! \array_key_exists($key, $value)) {
                 return; //????
             }
 
@@ -580,18 +580,18 @@ class Validator
 
     protected function vMin($v, $value, $attr)
     {
-        if (is_string($value)) {
-            if ((strpos($attr, 'bytes') && strlen($value) < (int) $attr)
-                || mb_strlen($value, 'UTF-8') < $attr
+        if (\is_string($value)) {
+            if ((\strpos($attr, 'bytes') && \strlen($value) < (int) $attr)
+                || \mb_strlen($value, 'UTF-8') < $attr
             ) {
                 $this->addError('The :alias minimum is :attr characters');
             }
-        } elseif (is_numeric($value)) {
+        } elseif (\is_numeric($value)) {
             if (0 + $value < $attr) {
                 $this->addError('The :alias minimum is :attr');
             }
-        } elseif (is_array($value)) {
-            if (count($value) < $attr) {
+        } elseif (\is_array($value)) {
+            if (\count($value) < $attr) {
                 $this->addError('The :alias minimum is :attr elements');
             }
         } elseif (null !== $value) {
@@ -603,17 +603,17 @@ class Validator
 
     protected function vMax($v, $value, $attr)
     {
-        if (is_string($value)) {
-            if ((strpos($attr, 'bytes') && strlen($value) > (int) $attr)
-                || mb_strlen($value, 'UTF-8') > $attr
+        if (\is_string($value)) {
+            if ((\strpos($attr, 'bytes') && \strlen($value) > (int) $attr)
+                || \mb_strlen($value, 'UTF-8') > $attr
             ) {
                 $this->addError('The :alias maximum is :attr characters');
             }
-        } elseif (is_numeric($value)) {
+        } elseif (\is_numeric($value)) {
             if (0 + $value > $attr) {
                 $this->addError('The :alias maximum is :attr');
             }
-        } elseif (is_array($value)) {
+        } elseif (\is_array($value)) {
             if (count($value) > $attr) {
                 $this->addError('The :alias maximum is :attr elements');
             }
@@ -626,10 +626,10 @@ class Validator
 
     protected function vToken($v, $value, $attr, $args)
     {
-        if (! is_array($args)) {
+        if (! \is_array($args)) {
             $args = [];
         }
-        if (! is_string($value) || ! $this->c->Csrf->verify($value, $attr, $args)) {
+        if (! \is_string($value) || ! $this->c->Csrf->verify($value, $attr, $args)) {
             $this->addError('Bad token', 'e');
             return null;
         } else {
@@ -644,7 +644,7 @@ class Validator
 
     protected function vReferer($v, $value, $attr, $args)
     {
-        if (! is_array($args)) {
+        if (! \is_array($args)) {
             $args = [];
         }
         return $this->c->Router->validate($value, $attr, $args);
@@ -677,7 +677,7 @@ class Validator
     protected function vRegex($v, $value, $attr)
     {
         if (null !== $value
-            && (! is_string($value) || ! preg_match($attr, $value))
+            && (! is_string($value) || ! \preg_match($attr, $value))
         ) {
             $this->addError('The :alias is not valid format');
             return null;
@@ -698,7 +698,7 @@ class Validator
 
     protected function vIn($v, $value, $attr)
     {
-        if (null !== $value && ! in_array($value, explode(',', $attr))) {
+        if (null !== $value && ! \in_array($value, \explode(',', $attr))) {
             $this->addError('The :alias contains an invalid value');
         }
         return $value;
@@ -706,7 +706,7 @@ class Validator
 
     protected function vNotIn($v, $value, $attr)
     {
-        if (null !== $value && in_array($value, explode(',', $attr))) {
+        if (null !== $value && \in_array($value, \explode(',', $attr))) {
             $this->addError('The :alias contains an invalid value');
         }
         return $value;

+ 24 - 15
app/Models/Pages/Admin/Options.php

@@ -34,8 +34,8 @@ class Options extends Admin
                     'o_board_desc'            => 'string:trim|max:65000 bytes',
                     'o_default_timezone'      => 'required|string:trim|in:-12,-11,-10,-9.5,-9,-8.5,-8,-7,-6,-5,-4,-3.5,-3,-2,-1,0,1,2,3,3.5,4,4.5,5,5.5,5.75,6,6.5,7,8,8.75,9,9.5,10,10.5,11,11.5,12,12.75,13,14',
                     'o_default_dst'           => 'required|integer|in:0,1',
-                    'o_default_lang'          => 'required|string:trim|in:' . implode(',', $this->c->Func->getLangs()),
-                    'o_default_style'         => 'required|string:trim|in:' . implode(',', $this->c->Func->getStyles()),
+                    'o_default_lang'          => 'required|string:trim|in:' . \implode(',', $this->c->Func->getLangs()),
+                    'o_default_style'         => 'required|string:trim|in:' . \implode(',', $this->c->Func->getStyles()),
                     'o_time_format'           => 'required|string:trim|max:25',
                     'o_date_format'           => 'required|string:trim|max:25',
                     'o_timeout_visit'         => 'required|integer|min:0|max:99999',
@@ -49,6 +49,7 @@ class Options extends Admin
                     'o_topic_review'          => 'required|integer|min:0|max:50',
                     'o_disp_topics_default'   => 'required|integer|min:10|max:50',
                     'o_disp_posts_default'    => 'required|integer|min:10|max:50',
+                    'o_disp_users'            => 'required|integer|min:10|max:50',
                     'o_indent_num_spaces'     => 'required|integer|min:0|max:99',
                     'o_quote_depth'           => 'required|integer|min:0|max:9',
                     'o_quickpost'             => 'required|integer|in:0,1',
@@ -155,7 +156,7 @@ class Options extends Admin
      */
     public function vCheckDir(Validator $v, $dir)
     {
-        $dir = '/' . trim(str_replace(['\\', '.'], ['/', ''], $dir), '/'); //?????
+        $dir = '/' . \trim(\str_replace(['\\', '.'], ['/', ''], $dir), '/'); //?????
 
         return $dir;
     }
@@ -171,7 +172,7 @@ class Options extends Admin
      */
     public function vCheckEmpty(Validator $v, $value, $attr)
     {
-        if (0 !== $value && 0 === strlen($v->$attr)) {
+        if (0 !== $value && 0 === \strlen($v->$attr)) {
             $value = 0;
         }
         return $value;
@@ -203,9 +204,9 @@ class Options extends Admin
 
         $yn     = [1 => \ForkBB\__('Yes'), 0 => \ForkBB\__('No')];
         $langs  = $this->c->Func->getLangs();
-        $langs  = array_combine($langs, $langs);
+        $langs  = \array_combine($langs, $langs);
         $styles = $this->c->Func->getStyles();
-        $styles = array_combine($styles, $styles);
+        $styles = \array_combine($styles, $styles);
 
         $form['sets'][] = [
             'legend' => \ForkBB\__('Essentials subhead'),
@@ -297,7 +298,7 @@ class Options extends Admin
             ],
         ];
 
-        $timestamp = time() + ($this->user->timezone + $this->user->dst) * 3600;
+        $timestamp = \time() + ($this->user->timezone + $this->user->dst) * 3600;
         $time = \ForkBB\dt($timestamp, false, $config->o_date_format, $config->o_time_format, true, true);
         $date = \ForkBB\dt($timestamp, true, $config->o_date_format, $config->o_time_format, false, true);
 
@@ -385,14 +386,6 @@ class Options extends Admin
                     'title'  => \ForkBB\__('Clickable links label'),
                     'info'   => \ForkBB\__('Clickable links help'),
                 ],
-                'o_topic_review' => [
-                    'type'  => 'number',
-                    'min'   => 0,
-                    'max'   => 50,
-                    'value' => $config->o_topic_review,
-                    'title' => \ForkBB\__('Topic review label'),
-                    'info'  => \ForkBB\__('Topic review help'),
-                ],
                 'o_disp_topics_default' => [
                     'type'  => 'number',
                     'min'   => 10,
@@ -409,6 +402,22 @@ class Options extends Admin
                     'title' => \ForkBB\__('Posts per page label'),
                     'info'  => \ForkBB\__('Posts per page help'),
                 ],
+                'o_disp_users' => [
+                    'type'  => 'number',
+                    'min'   => 10,
+                    'max'   => 50,
+                    'value' => $config->o_disp_users,
+                    'title' => \ForkBB\__('Users per page label'),
+                    'info'  => \ForkBB\__('Users per page help'),
+                ],
+                'o_topic_review' => [
+                    'type'  => 'number',
+                    'min'   => 0,
+                    'max'   => 50,
+                    'value' => $config->o_topic_review,
+                    'title' => \ForkBB\__('Topic review label'),
+                    'info'  => \ForkBB\__('Topic review help'),
+                ],
                 'o_indent_num_spaces' => [
                     'type'  => 'number',
                     'min'   => 0,

+ 22 - 21
app/Models/Pages/Install.php

@@ -35,7 +35,7 @@ class Install extends Page
         $dbTypes = [];
         $pdoDrivers = PDO::getAvailableDrivers();
         foreach ($pdoDrivers as $type) {
-            if (file_exists($this->c->DIR_APP . '/Core/DB/' . ucfirst($type) . '.php')) {
+            if (\file_exists($this->c->DIR_APP . '/Core/DB/' . \ucfirst($type) . '.php')) {
                 switch ($type) {
                     case 'mysql':
                         $dbTypes['mysql_innodb'] = 'MySQL InnoDB (PDO)';
@@ -48,7 +48,7 @@ class Install extends Page
                         $dbTypes[$type]          = 'PostgreSQL (PDO)';
                         break;
                     default:
-                        $dbTypes[$type]          = ucfirst($type) . ' (PDO)';
+                        $dbTypes[$type]          = \ucfirst($type) . ' (PDO)';
                 }
             }
         }
@@ -84,7 +84,7 @@ class Install extends Page
         $this->c->Lang->load('install');
 
         // версия PHP
-        if (version_compare(PHP_VERSION, self::PHP_MIN, '<')) {
+        if (\version_compare(PHP_VERSION, self::PHP_MIN, '<')) {
             $this->fIswev = ['e', \ForkBB\__('You are running error', 'PHP', PHP_VERSION, $this->c->FORK_REVISION, self::PHP_MIN)];
         }
 
@@ -101,14 +101,14 @@ class Install extends Page
             $this->c->DIR_PUBLIC . '/img/avatars',
         ];
         foreach ($folders as $folder) {
-            if (! is_writable($folder)) {
-                $folder = str_replace(dirname($this->c->DIR_APP), '', $folder);
+            if (! \is_writable($folder)) {
+                $folder = \str_replace(\dirname($this->c->DIR_APP), '', $folder);
                 $this->fIswev = ['e', \ForkBB\__('Alert folder', $folder)];
             }
         }
 
         // доступность шаблона конфигурации
-        $config = @file_get_contents($this->c->DIR_CONFIG . '/main.dist.php');
+        $config = @\file_get_contents($this->c->DIR_CONFIG . '/main.dist.php');
         if (false === $config) {
             $this->fIswev = ['e', \ForkBB\__('No access to main.dist.php')];
         }
@@ -133,7 +133,7 @@ class Install extends Page
                     'check_host'   => [$this, 'vCheckHost'],
                     'rtrim_url'    => [$this, 'vRtrimURL']
                 ])->addRules([
-                    'dbtype'       => 'required|string:trim|in:' . implode(',', array_keys($this->dbTypes)),
+                    'dbtype'       => 'required|string:trim|in:' . \implode(',', \array_keys($this->dbTypes)),
                     'dbhost'       => 'required|string:trim|check_host',
                     'dbname'       => 'required|string:trim',
                     'dbuser'       => 'string:trim',
@@ -145,8 +145,8 @@ class Install extends Page
                     'title'        => 'required|string:trim|max:255',
                     'descr'        => 'string:trim|max:65000 bytes',
                     'baseurl'      => 'required|string:trim|rtrim_url',
-                    'defaultlang'  => 'required|string:trim|in:' . implode(',', $this->c->Func->getLangs()),
-                    'defaultstyle' => 'required|string:trim|in:' . implode(',', $this->c->Func->getStyles()),
+                    'defaultlang'  => 'required|string:trim|in:' . \implode(',', $this->c->Func->getLangs()),
+                    'defaultstyle' => 'required|string:trim|in:' . \implode(',', $this->c->Func->getStyles()),
                 ])->addAliases([
                     'dbtype'       => 'Database type',
                     'dbhost'       => 'Database server hostname',
@@ -172,7 +172,7 @@ class Install extends Page
             }
         }
 
-        if (count($langs) > 1) {
+        if (\count($langs) > 1) {
             $this->form1 = [
                 'action' => $this->c->Router->link('Install'),
                 'hidden' => [
@@ -183,7 +183,7 @@ class Install extends Page
                         'fields' => [
                             'installlang' => [
                                 'type'    => 'select',
-                                'options' => array_combine($langs, $langs),
+                                'options' => \array_combine($langs, $langs),
                                 'value'   => $this->user->language,
                                 'title'   => \ForkBB\__('Install language'),
                                 'info'    => \ForkBB\__('Choose install language info'),
@@ -387,7 +387,7 @@ class Install extends Page
      */
     public function vRtrimURL(Validator $v, $url)
     {
-        return rtrim($url, '/');
+        return \rtrim($url, '/');
     }
 
     /**
@@ -401,9 +401,9 @@ class Install extends Page
     public function vCheckPrefix(Validator $v, $prefix)
     {
         if (isset($prefix{0})) {
-            if (! preg_match('%^[a-z][a-z\d_]*$%i', $prefix)) {
+            if (! \preg_match('%^[a-z][a-z\d_]*$%i', $prefix)) {
                 $v->addError('Table prefix error');
-            } elseif ('sqlite' === $v->dbtype && 'sqlite_' === strtolower($prefix)) {
+            } elseif ('sqlite' === $v->dbtype && 'sqlite_' === \strtolower($prefix)) {
                 $v->addError('Prefix reserved');
             }
         }
@@ -438,7 +438,7 @@ class Install extends Page
                 $DBEngine = 'InnoDB';
             case 'mysql':
                 $this->DBEngine = $DBEngine;
-                if (preg_match('%^([^:]+):(\d+)$%', $dbhost, $matches)) {
+                if (\preg_match('%^([^:]+):(\d+)$%', $dbhost, $matches)) {
                     $this->c->DB_DSN = "mysql:host={$matches[1]};port={$matches[2]};dbname={$dbname};charset=utf8mb4";
                 } else {
                     $this->c->DB_DSN = "mysql:host={$dbhost};dbname={$dbname};charset=utf8mb4";
@@ -489,7 +489,7 @@ class Install extends Page
      */
     protected function installEnd(Validator $v)
     {
-        @set_time_limit(0);
+        @\set_time_limit(0);
         $this->c->Cache->clear();
 
         $this->c->DB->beginTransaction();
@@ -1034,7 +1034,7 @@ class Install extends Page
         }
         $this->c->DB->exec('UPDATE ::groups SET g_pm_limit=0 WHERE g_id=?i', [$this->c->GROUP_ADMIN]);
 
-        $ip = filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP) ?: 'unknow';
+        $ip = \filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP) ?: 'unknow';
         $this->c->DB->exec('INSERT INTO ::users (group_id, username, password, email) VALUES (?i, ?s, ?s, ?s)', [$this->c->GROUP_GUEST, \ForkBB\__('Guest '), \ForkBB\__('Guest '), \ForkBB\__('Guest ')]);
         $this->c->DB->exec('INSERT INTO ::users (group_id, username, password, email, language, style, num_posts, last_post, registered, registration_ip, last_visit) VALUES (?i, ?s, ?s, ?s, ?s, ?s, ?i, ?i, ?i, ?s, ?i)', [$this->c->GROUP_ADMIN, $v->username, password_hash($v->password, PASSWORD_DEFAULT), $v->email, $v->defaultlang, $v->defaultstyle, 1, $now, $now, $ip, $now]);
 
@@ -1061,6 +1061,7 @@ class Install extends Page
             'o_topic_review'          => 15,
             'o_disp_topics_default'   => 30,
             'o_disp_posts_default'    => 25,
+            'o_disp_users'            => 50,
             'o_indent_num_spaces'     => 4,
             'o_quote_depth'           => 3,
             'o_quickpost'             => 1,
@@ -1075,7 +1076,7 @@ class Install extends Page
             'o_regs_report'           => 0,
             'o_default_email_setting' => 1,
             'o_mailing_list'          => $v->email,
-            'o_avatars'               => in_array(strtolower(@ini_get('file_uploads')), ['on', 'true', '1']) ? 1 : 0,
+            'o_avatars'               => \in_array(\strtolower(@\ini_get('file_uploads')), ['on', 'true', '1']) ? 1 : 0,
             'o_avatars_dir'           => '/img/avatars',
             'o_avatars_width'         => 60,
             'o_avatars_height'        => 60,
@@ -1168,7 +1169,7 @@ class Install extends Page
 
         $this->c->DB->commit();
 
-        $config = @file_get_contents($this->c->DIR_CONFIG . '/main.dist.php');
+        $config = @\file_get_contents($this->c->DIR_CONFIG . '/main.dist.php');
         if (false === $config) {
             throw new RuntimeException('No access to main.dist.php.');
         }
@@ -1185,9 +1186,9 @@ class Install extends Page
             '_COOKIE_KEY2_'   => $this->c->Secury->randomPass(mt_rand(20,30)),
         ];
         foreach ($repl as $key => $val) {
-            $config = str_replace($key, addslashes($val), $config);
+            $config = \str_replace($key, \addslashes($val), $config);
         }
-        $result = file_put_contents($this->c->DIR_CONFIG . '/main.php', $config);
+        $result = \file_put_contents($this->c->DIR_CONFIG . '/main.php', $config);
         if (false === $result) {
             throw new RuntimeException('No write to main.php');
         }

+ 40 - 7
app/Models/Pages/Userlist.php

@@ -11,8 +11,6 @@ class Userlist extends Page
 {
     use CrumbTrait;
 
-    protected $usersPerPage = 2; // ???? в конфиг!
-
     /**
      * Список пользователей
      *
@@ -25,18 +23,53 @@ class Userlist extends Page
     {
         $this->c->Lang->load('userlist');
 
-        $ids    = $this->c->users->filter([], []);
+        $v = $this->c->Validator->reset()
+            ->addRules([
+                'sort'  => 'string|in:username,registered,num_posts',
+                'dir'   => 'string|in:ASC,DESC',
+                'group' => 'integer|min:-1|max:9999999999|not_in:0,' . $this->c->GROUP_GUEST,
+                'name'  => 'string:trim|min:1|max:25',
+            ]);
+
+        $error = true;
+        if ($v->validation($args)) {
+            $count = (int) (null === $v->sort)
+                   + (int) (null === $v->dir)
+                   + (int) (null === $v->group)
+                   + (int) (null === $v->name);
+
+            if (0 === $count || 4 === $count) {
+                $error = false;
+            }
+        }
+        if ($error) {
+            return $this->c->Message->message('Bad request');
+        }
+
+        $filters = [];
+        if ($v->group < 1) {
+            $filters['group_id'] = ['!=', 0];
+        } else {
+            $filters['group_id'] = ['=', $v->group];
+        }
+        if (null !== $v->name && '*' !== $v->name) {
+            $filters['username'] = ['LIKE', $v->name];
+        }
+
+        $order  = $v->sort ? [$v->sort => $v->dir] : [];
+
+        $ids    = $this->c->users->filter($filters, $order);
         $number = \count($ids);
         $page   = isset($args['page']) ? (int) $args['page'] : 1;
-        $pages  = $number ? (int) \ceil($number / $this->usersPerPage) : 1;
+        $pages  = $number ? (int) \ceil($number / $this->c->config->o_disp_users) : 1;
 
         if ($page > $pages) {
             return $this->c->Message->message('Bad request');
         }
 
         if ($number) {
-            $this->startNum = ($page - 1) * $this->usersPerPage;
-            $ids = \array_slice($ids, $this->startNum, $this->usersPerPage);
+            $this->startNum = ($page - 1) * $this->c->config->o_disp_users;
+            $ids = \array_slice($ids, $this->startNum, $this->c->config->o_disp_users);
             $this->userList = $this->c->users->load($ids);
         } else {
             $this->startNum = 0;
@@ -51,7 +84,7 @@ class Userlist extends Page
         $this->robots       = 'noindex';
 //        $this->form         = $form;
         $this->crumbs       = $this->crumbs([$this->c->Router->link('Userlist'), \ForkBB\__('User_list')]);
-        $this->pagination   = $this->c->Func->paginate($pages, $page, 'Userlist');
+        $this->pagination   = $this->c->Func->paginate($pages, $page, 'Userlist', $args);
 
         return $this;
     }

+ 26 - 6
app/Models/User/Filter.php

@@ -20,18 +20,18 @@ class Filter extends Action
      */
     public function filter(array $filters, array $order = [])
     {
-        $fileds  = $this->c->dbMap->users;
+        $fields  = $this->c->dbMap->users;
         $orderBy = [];
         $where   = ['u.id>1'];
 
-        foreach ($order as $filed => $val) {
-            if (! isset($fileds[$filed])) {
+        foreach ($order as $field => $dir) {
+            if (! isset($fields[$field])) {
                 throw new InvalidArgumentException('No sorting field found');
             }
-            if ('ACS' !== $val && 'DESC' !== $val) {
+            if ('ASC' !== $dir && 'DESC' !== $dir) {
                 throw new InvalidArgumentException('The sorting order is not clear');
             }
-            $orderBy[] = "u.{$filed} {$val}";
+            $orderBy[] = "u.{$field} {$dir}";
         }
         if (empty($orderBy)) {
             $orderBy = 'u.username ASC';
@@ -39,9 +39,29 @@ class Filter extends Action
             $orderBy = \implode(', ', $orderBy);
         }
 
+        $vars = [];
+
+        foreach ($filters as $field => $rule) {
+            if (! isset($fields[$field])) {
+                throw new InvalidArgumentException('No sorting field found');
+            }
+            switch ($rule[0]) {
+                case '=':
+                case '!=':
+                    $where[] = "u.{$field}{$rule[0]}?{$fields[$field]}";
+                    $vars[]  = $rule[1];
+                    break;
+                case 'LIKE':
+                    $where[] = "u.{$field} LIKE ?{$fields[$field]}";
+                    $vars[]  = \str_replace(['*', '_'], ['%', '\\_'], $rule[1]);
+                    break;
+                default:
+                    throw new InvalidArgumentException('The condition is not clear');
+            }
+        }
+
         $where = \implode(' AND ', $where);
 
-        $vars = [];
         $sql = "SELECT u.id
                 FROM ::users AS u
                 WHERE {$where}

+ 6 - 0
app/lang/English/admin_options.po

@@ -279,6 +279,12 @@ msgstr "Posts per page"
 msgid "Posts per page help"
 msgstr "The default number of posts to display per page in a topic. Users can personalize this setting."
 
+msgid "Users per page label"
+msgstr "Users per page"
+
+msgid "Users per page help"
+msgstr "The number of users displayed on one page of the list."
+
 msgid "Indent label"
 msgstr "Indent size"
 

+ 6 - 0
app/lang/Russian/admin_options.po

@@ -279,6 +279,12 @@ msgstr "Сообщений на страницу"
 msgid "Posts per page help"
 msgstr "Значение по умолчанию сколько сообщений выводить на странице с темой. Пользователь может установить своё значение."
 
+msgid "Users per page label"
+msgstr "Пользователей на страницу"
+
+msgid "Users per page help"
+msgstr "Число пользователей отображаемых на одной странице списка."
+
 msgid "Indent label"
 msgstr "Размер отступа"
 

+ 21 - 3
app/templates/userlist.tpl

@@ -55,12 +55,30 @@
       <div class="f-ulist">
         <ol class="f-table">
           <li class="f-row f-thead" value="{{ $p->startNum }}">
-            <span class="f-hcell f-cusername">{!! __('Username') !!}</span>
+            <span class="f-hcell f-cusername">
+              <span class="f-hc-table">
+                <span class="f-hc-tasc">▲</span>
+                <span class="f-hc-tname">{!! __('Username') !!}</span>
+                <span class="f-hc-tdesc">▼</span>
+              </span>
+            </span>
             <span class="f-hcell f-ctitle">{!! __('Title') !!}</span>
     @if ($p->user->showPostCount)
-            <span class="f-hcell f-cnumposts">{!! __('Posts') !!}</span>
+            <span class="f-hcell f-cnumposts">
+              <span class="f-hc-table">
+                <span class="f-hc-tasc">▲</span>
+                <span class="f-hc-tname">{!! __('Posts') !!}</span>
+                <span class="f-hc-tdesc">▼</span>
+              </span>
+            </span>
     @endif
-            <span class="f-hcell f-cdatereg">{!! __('Registered') !!}</span>
+            <span class="f-hcell f-cdatereg">
+              <span class="f-hc-table">
+                <span class="f-hc-tasc">▲</span>
+                <span class="f-hc-tname">{!! __('Registered') !!}</span>
+                <span class="f-hc-tdesc">▼</span>
+              </span>
+            </span>
           </li>
   @foreach ($p->userList as $user)
           <li class="f-row">

+ 21 - 0
public/style/ForkBB/style.css

@@ -1946,6 +1946,18 @@ li + li .f-btn {
     display: none;
   }
 
+  .f-ulist .f-hcell .f-hc-table {
+    display: table;
+    line-height: 1;
+  }
+
+  .f-hc-table .f-hc-tasc,
+  .f-hc-table .f-hc-tdesc {
+    font-size: 0.625rem;
+    display: block;
+    text-align: center;
+  }
+
   .f-ulist .f-cell > b,
   .f-ulist .f-cell > i {
     font-style: normal;
@@ -1970,9 +1982,18 @@ li + li .f-btn {
     text-align: center;
   }
 
+  .f-ulist .f-cnumposts .f-hc-table {
+    margin: 0 auto;
+  }
+
   .f-ulist .f-cdatereg {
     padding: 0.625rem;
     width: 11rem;
     min-width: 11rem;
   }
+
+  .f-ulist .f-hcell {
+    padding-top: 0.3125rem;
+    padding-bottom: 0.3125rem;
+  }
 }