Browse Source

2018-01-22

Visman 7 years ago
parent
commit
0c13908e25

+ 6 - 1
app/Controllers/Routing.php

@@ -66,7 +66,12 @@ class Routing
             }
             }
             // поиск
             // поиск
             if ($user->g_search == '1') {
             if ($user->g_search == '1') {
-                $r->add(['GET','POST'], '/search[/{advanced:advanced}]',           'Search:view',   'Search');
+                $r->add('GET',  '/search[/simple/{keywords}[/{page:[1-9]\d*}]]',  'Search:view',   'Search');
+                $r->add('POST', '/search',                                        'Search:view');
+
+                $r->add('GET',  '/search/advanced[/{keywords}/{author}/{forums}/{serch_in:\d}/{sort_by:\d}/{sort_dir:\d}/{show_as:\d}[/{page:[1-9]\d*}]]', 'Search:viewAdvanced',   'SearchAdvanced');
+                $r->add('POST', '/search/advanced',           'Search:viewAdvanced');
+
                 $r->add('GET',          '/search/{action:last|unanswered}[/{page:[1-9]\d*}]', 'Search:action', 'SearchAction');
                 $r->add('GET',          '/search/{action:last|unanswered}[/{page:[1-9]\d*}]', 'Search:action', 'SearchAction');
             }
             }
             // юзеры
             // юзеры

+ 14 - 3
app/Core/Router.php

@@ -59,6 +59,16 @@ class Router
      */
      */
     protected $length;
     protected $length;
 
 
+    protected $subSearch = [
+        '/',
+        '\\',
+    ];
+
+    protected $subRepl = [
+        '(_slash_)',
+        '(_backslash_)',
+    ];
+
     /**
     /**
      * Конструктор
      * Конструктор
      *
      *
@@ -130,7 +140,7 @@ class Router
             if (isset($args[$name])) {
             if (isset($args[$name])) {
                 // кроме page = 1
                 // кроме page = 1
                 if ($name !== 'page' || $args[$name] !== 1) {
                 if ($name !== 'page' || $args[$name] !== 1) {
-                    $data['{' . $name . '}'] = rawurlencode(preg_replace('%[\s\\\/]+%u', '-', $args[$name]));
+                    $data['{' . $name . '}'] = rawurlencode(str_replace($this->subSearch, $this->subRepl, $args[$name]));
                     continue;
                     continue;
                 }
                 }
             }
             }
@@ -140,7 +150,8 @@ class Router
                 return $result . '/';
                 return $result . '/';
             // значение не обязательно
             // значение не обязательно
             } else {
             } else {
-                $link = preg_replace('%\[[^\[\]{}]*{' . preg_quote($name, '%') . '}[^\[\]{}]*\]%', '', $link);
+//                $link = preg_replace('%\[[^\[\]{}]*{' . preg_quote($name, '%') . '}[^\[\]{}]*\]%', '', $link);
+                $link = preg_replace('%\[[^\[\]]*?{' . preg_quote($name, '%') . '}[^\[\]]*+(\[((?>[^\[\]]*+)|(?1))+\])*?\]%', '', $link);
             }
             }
         }
         }
         $link = str_replace(['[', ']'], '', $link);
         $link = str_replace(['[', ']'], '', $link);
@@ -207,7 +218,7 @@ class Router
                 $args = [];
                 $args = [];
                 foreach ($keys as $key) {
                 foreach ($keys as $key) {
                     if (isset($matches[$key])) {
                     if (isset($matches[$key])) {
-                        $args[$key] = $matches[$key];
+                        $args[$key] = str_replace($this->subRepl, $this->subSearch, $matches[$key]);
                     }
                     }
                 }
                 }
                 return [self::OK, $handler, $args, $marker];
                 return [self::OK, $handler, $args, $marker];

+ 150 - 57
app/Models/Pages/Search.php

@@ -41,35 +41,51 @@ class Search extends Page
         $this->listForOptions = $options;
         $this->listForOptions = $options;
     }
     }
 
 
+    /**
+     * Расширенный поиск
+     *
+     * @param array $args
+     * @param string $method
+     *
+     * @return Page
+     */
+    public function viewAdvanced(array $args, $method)
+    {
+        return $this->view($args, $method, true);
+    }
+
     /**
     /**
      * Поиск
      * Поиск
      *
      *
      * @param array $args
      * @param array $args
      * @param string $method
      * @param string $method
+     * @param bool $advanced
      *
      *
      * @return Page
      * @return Page
      */
      */
-    public function view(array $args, $method)
+    public function view(array $args, $method, $advanced = false)
     {
     {
         $this->c->Lang->load('search');
         $this->c->Lang->load('search');
         $this->calcList();
         $this->calcList();
 
 
+        $marker = $advanced ? 'SearchAdvanced' : 'Search';
+
         $v = null;
         $v = null;
-        if ('POST' === $method) {
+        if ('POST' === $method || isset($args['keywords'])) {
             $v = $this->c->Validator->reset()
             $v = $this->c->Validator->reset()
                 ->addValidators([
                 ->addValidators([
-                    'check_query' => [$this, 'vCheckQuery'],
+                    'check_query'  => [$this, 'vCheckQuery'],
+                    'check_forums' => [$this, 'vCheckForums'],
+                    'check_author' => [$this, 'vCheckAuthor'],
                 ])->addRules([
                 ])->addRules([
-                    'token'    => 'token:Search',
-                    'keywords' => 'required|string:trim|max:100|check_query',
-                    'author'   => 'absent',
-                    'forums'   => 'absent',
-                    'serch_in' => 'absent',
-                    'sort_by'  => 'absent',
-                    'sort_dir' => 'absent',
-                    'show_as'  => 'absent',
+                    'author'   => 'absent:*',
+                    'forums'   => 'absent:*',
+                    'serch_in' => 'absent:0|integer',
+                    'sort_by'  => 'absent:0|integer',
+                    'sort_dir' => 'absent:0|integer',
+                    'show_as'  => 'absent:0|integer',
                 ])->addArguments([
                 ])->addArguments([
-                    'token' => $args,
+//                    'token' => $args,
                 ])->addAliases([
                 ])->addAliases([
                     'keywords' => 'Keyword search',
                     'keywords' => 'Keyword search',
                     'author'   => 'Author search',
                     'author'   => 'Author search',
@@ -80,11 +96,10 @@ class Search extends Page
                     'show_as'  => 'Show as',
                     'show_as'  => 'Show as',
                 ]);
                 ]);
 
 
-            if (isset($args['advanced'])) {
+            if ($advanced) {
                 $v->addRules([
                 $v->addRules([
-                    'author'   => 'string:trim|max:25',
-                    'forums'   => 'array',
-                    'forums.*' => 'integer|in:' . implode(',', $this->listOfIndexes),
+                    'author'   => 'required|string:trim|max:25|check_author',
+                    'forums'   => 'check_forums',
                     'serch_in' => 'required|integer|in:0,1,2',
                     'serch_in' => 'required|integer|in:0,1,2',
                     'sort_by'  => 'required|integer|in:0,1,2,3',
                     'sort_by'  => 'required|integer|in:0,1,2,3',
                     'sort_dir' => 'required|integer|in:0,1',
                     'sort_dir' => 'required|integer|in:0,1',
@@ -92,43 +107,29 @@ class Search extends Page
                 ]);
                 ]);
             }
             }
 
 
-            if ($v->validation($_POST)) {
-                $forums = $v->forums;
-
-                if (empty($forums) && ! $this->c->user->isAdmin) {
-                    $forums = $this->listOfIndexes;
-                }
+            if ('POST' === $method) {
+                $v->addRules([
+                    'token'    => 'token:' . $marker,
+                ]);
+            }
 
 
-                $options = [
-                    'keywords' => $v->keywords,
-                    'author'   => (string) $v->author,
-                    'forums'   => $forums,
-                    'serch_in' => ['all', 'posts', 'topics'][(int) $v->serch_in],
-                    'sort_by'  => ['post', 'author', 'subject', 'forum'][(int) $v->sort_by],
-                    'sort_dir' => ['desc', 'asc'][(int) $v->sort_dir],
-                    'show_as'  => ['posts', 'topics'][(int) $v->show_as],
-                ];
-
-                $result = $this->c->search->execute($options);
-
-                $user = $this->c->user;
-                if ($user->g_search_flood) {
-                    $user->last_search = time();
-                    $this->c->users->update($user); //?????
-                }
+            $v->addRules([
+                'keywords'     => 'required|string:trim|max:100|check_query:' . $method,
+            ]);
 
 
-                if (empty($result)) {
-                    $this->fIswev = ['i', \ForkBB\__('No hits')];
-                }
+            if ('POST' === $method && $v->validation($_POST)) {
+                return $this->c->Redirect->page($marker, $v->getData());
+            } elseif ('GET' === $method && $v->validation($args)) {
+                return $this->action(array_merge($args, $v->getData(), ['action' => 'search']), $method, $advanced);
             }
             }
 
 
             $this->fIswev = $v->getErrors();
             $this->fIswev = $v->getErrors();
         }
         }
 
 
         $form = [
         $form = [
-            'action' => $this->c->Router->link('Search', $args),
+            'action' => $this->c->Router->link($marker),
             'hidden' => [
             'hidden' => [
-                'token' => $this->c->Csrf->create('Search', $args),
+                'token' => $this->c->Csrf->create($marker),
             ],
             ],
             'sets' => [],
             'sets' => [],
             'btns'   => [
             'btns'   => [
@@ -140,7 +141,7 @@ class Search extends Page
             ],
             ],
         ];
         ];
 
 
-        if (isset($args['advanced'])) {
+        if ($advanced) {
             $form['sets'][] = [
             $form['sets'][] = [
                 'fields' => [
                 'fields' => [
                     [
                     [
@@ -162,7 +163,8 @@ class Search extends Page
                         'type'      => 'text',
                         'type'      => 'text',
                         'maxlength' => 25,
                         'maxlength' => 25,
                         'title'     => \ForkBB\__('Author search'),
                         'title'     => \ForkBB\__('Author search'),
-                        'value'     => $v ? $v->author : '',
+                        'value'     => $v ? $v->author : '*',
+                        'required'  => true,
                     ],
                     ],
                     [
                     [
                         'type'      => 'info',
                         'type'      => 'info',
@@ -177,7 +179,7 @@ class Search extends Page
                         'dl'      => 'w3',
                         'dl'      => 'w3',
                         'type'    => 'multiselect',
                         'type'    => 'multiselect',
                         'options' => $this->listForOptions,
                         'options' => $this->listForOptions,
-                        'value'   => $v ? $v->forums : null,
+                        'value'   => $v ? explode('.', $v->forums) : null,
                         'title'   => \ForkBB\__('Forum search'),
                         'title'   => \ForkBB\__('Forum search'),
                         'size'    => min(count($this->listForOptions), 10),
                         'size'    => min(count($this->listForOptions), 10),
                     ],
                     ],
@@ -250,7 +252,7 @@ class Search extends Page
                 'fields' => [
                 'fields' => [
                     [
                     [
                         'type'      => 'info',
                         'type'      => 'info',
-                        'value'     => \ForkBB\__('<a href="%s">Advanced search</a>', $this->c->Router->link('Search', ['advanced' => 'advanced'])),
+                        'value'     => \ForkBB\__('<a href="%s">Advanced search</a>', $this->c->Router->link('SearchAdvanced')),
                         'html'      => true,
                         'html'      => true,
                     ],
                     ],
                     'keywords' => [
                     'keywords' => [
@@ -281,37 +283,109 @@ class Search extends Page
      *
      *
      * @param Validator $v
      * @param Validator $v
      * @param string $query
      * @param string $query
+     * @param string $method
      *
      *
      * @return string
      * @return string
      */
      */
-    public function vCheckQuery(Validator $v, $query)
+    public function vCheckQuery(Validator $v, $query, $method)
     {
     {
-        $user = $this->c->user;
+        if (empty($v->getErrors())) {
+            $user  = $this->c->user;
+            $flood = $user->last_search && time() - $user->last_search < $user->g_search_flood;
 
 
-        if ($user->last_search && time() - $user->last_search < $user->g_search_flood) {
-            $v->addError(\ForkBB\__('Search flood', $user->g_search_flood, $user->g_search_flood - time() + $user->last_search));
-        } else {
-            $search = $this->c->search;
+            if ('POST' !== $method || ! $flood) {
+                $search = $this->c->search;
+
+                if (! $search->prepare($query)) {
+                    $v->addError(\ForkBB\__($search->queryError, $search->queryText));
+                } else {
+
+                    if ($this->c->search->execute($v, $this->listOfIndexes, $flood)) {
+                        $flood = false;
 
 
-            if (! $search->prepare($query)) {
-                $v->addError(\ForkBB\__($search->queryError, $search->queryText));
+                        if (empty($search->queryIds)) {
+                            $v->addError('No hits', 'i');
+                        }
+
+                        if ($search->queryNoCache && $user->g_search_flood) {
+                            $user->last_search = time();
+                            $this->c->users->update($user); //?????
+                        }
+                    }
+                }
+            }
+
+            if ($flood) {
+                $v->addError(\ForkBB\__('Search flood', $user->g_search_flood, $user->g_search_flood - time() + $user->last_search));
             }
             }
         }
         }
 
 
         return $query;
         return $query;
     }
     }
 
 
+    /**
+     * Дополнительная проверка разделов
+     *
+     * @param Validator $v
+     * @param string|array $forums
+     *
+     * @return string
+     */
+    public function vCheckForums(Validator $v, $forums)
+    {
+        if ('*' !== $forums) {
+            if (is_string($forums) && preg_match('%^\d+(?:\.\d+)*$%D', $forums)) {
+                $forums = explode('.', $forums);
+            } elseif (null === $forums) {
+                $forums = '*';
+            } elseif (! is_array($forums)) {
+                $v->addError('The :alias contains an invalid value');
+                $forums = '*';
+            }
+        }
+
+        if ('*' !== $forums) {
+            if (! empty(array_diff($forums, $this->listOfIndexes))) {
+                $v->addError('The :alias contains an invalid value');
+            }
+            sort($forums, SORT_NUMERIC);
+            $forums = implode('.', $forums);
+        }
+
+        return $forums;
+    }
+
+    /**
+     * Дополнительная проверка автора
+     *
+     * @param Validator $v
+     * @param string|array $forums
+     *
+     * @return string
+     */
+    public function vCheckAuthor(Validator $v, $name)
+    {
+        $name = preg_replace('%\*+%', '*', $name);
+
+        if ('*' !== $name && ! preg_match('%[\p{L}\p{N}]%', $name)) {
+            $v->addError('The :alias is not valid format');
+        }
+
+        return $name;
+    }
+
     /**
     /**
      * Типовые действия
      * Типовые действия
      *
      *
      * @param array $args
      * @param array $args
      * @param string $method
      * @param string $method
+     * @param bool $advanced
      *
      *
      * @throws InvalidArgumentException
      * @throws InvalidArgumentException
      *
      *
      * @return Page
      * @return Page
      */
      */
-    public function action(array $args, $method)
+    public function action(array $args, $method, $advanced = false)
     {
     {
         $this->c->Lang->load('search');
         $this->c->Lang->load('search');
 
 
@@ -319,6 +393,25 @@ class Search extends Page
         $model->page = isset($args['page']) ? (int) $args['page'] : 1;
         $model->page = isset($args['page']) ? (int) $args['page'] : 1;
         $action      = $args['action'];
         $action      = $args['action'];
         switch ($action) {
         switch ($action) {
+            case 'search':
+                if (1 === $model->showAs) {
+                    if ('*' === $args['author']) {
+                        $model->name  = \ForkBB\__('By keywords show as topics', $args['keywords']);
+                    } else {
+                        $model->name  = \ForkBB\__('By both show as topics', $args['keywords'], $args['author']);
+                    }
+                    $list = $model->actionT($action);
+                } else {
+                    if ('*' === $args['author']) {
+                        $model->name  = \ForkBB\__('By keywords show as posts', $args['keywords']);
+                    } else {
+                        $model->name  = \ForkBB\__('By both show as posts', $args['keywords'], $args['author']);
+                    }
+                    //?????
+                }
+                $model->linkMarker = $advanced ? 'SearchAdvanced' : 'Search';;
+                $model->linkArgs   = $args;
+                break;
             case 'last':
             case 'last':
             case 'unanswered':
             case 'unanswered':
                 $list = $model->actionT($action);
                 $list = $model->actionT($action);

+ 1 - 1
app/Models/Pages/Topic.php

@@ -136,7 +136,7 @@ class Topic extends Page
             return $this->c->Message->message('Bad request');
             return $this->c->Message->message('Bad request');
         }
         }
 
 
-        if (! $posts = $this->c->posts->view($topic)) {
+        if (! $posts = $topic->pageData()) {
             return $this->go('last', $topic);
             return $this->go('last', $topic);
         }
         }
 
 

+ 14 - 17
app/Models/Post/Model.php

@@ -37,6 +37,8 @@ class Model extends DataModel
     /**
     /**
      * Автор сообщения
      * Автор сообщения
      *
      *
+     * @throws RuntimeException
+     *
      * @return User
      * @return User
      */
      */
     protected function getuser() //????
     protected function getuser() //????
@@ -44,19 +46,14 @@ class Model extends DataModel
         $user = $this->c->users->get($this->poster_id);
         $user = $this->c->users->get($this->poster_id);
 
 
         if (! $user instanceof User) {
         if (! $user instanceof User) {
-            $attrs = $this->a; //????
-            $attrs['id'] = $attrs['poster_id'];
-
-            $user = $this->c->users->create($attrs);
-
-            if ($user->isGuest) {
-                $user->__email = $this->poster_email;
-            } else {
-                $this->c->users->set($user->id, $user);
-            }
+            throw new RuntimeException('No user data');
+        } elseif (1 === $this->poster_id) {
+            $user = clone $user;
+            $user->__email = $this->poster_email;
+            $user->__username = $this->poster;
         }
         }
 
 
-        return $user; 
+        return $user;
     }
     }
 
 
     /**
     /**
@@ -130,11 +127,11 @@ class Model extends DataModel
         }
         }
 
 
         return $this->user->id === $this->c->user->id
         return $this->user->id === $this->c->user->id
-            && (($this->id == $this->parent->first_post_id && $this->c->user->g_delete_topics == '1') 
+            && (($this->id == $this->parent->first_post_id && $this->c->user->g_delete_topics == '1')
                 || ($this->id != $this->parent->first_post_id && $this->c->user->g_delete_posts == '1')
                 || ($this->id != $this->parent->first_post_id && $this->c->user->g_delete_posts == '1')
             )
             )
-            && ($this->c->user->g_deledit_interval == '0' 
-                || $this->edit_post == '1' 
+            && ($this->c->user->g_deledit_interval == '0'
+                || $this->edit_post == '1'
                 || time() - $this->posted < $this->c->user->g_deledit_interval
                 || time() - $this->posted < $this->c->user->g_deledit_interval
             );
             );
     }
     }
@@ -156,8 +153,8 @@ class Model extends DataModel
 
 
         return $this->user->id === $this->c->user->id
         return $this->user->id === $this->c->user->id
             && $this->c->user->g_edit_posts == '1'
             && $this->c->user->g_edit_posts == '1'
-            && ($this->c->user->g_deledit_interval == '0' 
-                || $this->edit_post == '1' 
+            && ($this->c->user->g_deledit_interval == '0'
+                || $this->edit_post == '1'
                 || time() - $this->posted < $this->c->user->g_deledit_interval
                 || time() - $this->posted < $this->c->user->g_deledit_interval
             );
             );
     }
     }
@@ -179,7 +176,7 @@ class Model extends DataModel
 
 
     /**
     /**
      * HTML код сообщения
      * HTML код сообщения
-     * 
+     *
      * @return string
      * @return string
      */
      */
     public function html()
     public function html()

+ 98 - 48
app/Models/Post/View.php

@@ -3,88 +3,138 @@
 namespace ForkBB\Models\Post;
 namespace ForkBB\Models\Post;
 
 
 use ForkBB\Models\Action;
 use ForkBB\Models\Action;
+use ForkBB\Models\Search\Model as Search;
 use ForkBB\Models\Topic\Model as Topic;
 use ForkBB\Models\Topic\Model as Topic;
+use ForkBB\Models\User\Model as User;
 use PDO;
 use PDO;
 use InvalidArgumentException;
 use InvalidArgumentException;
 use RuntimeException;
 use RuntimeException;
 
 
 class View extends Action
 class View extends Action
 {
 {
+    protected $aliases;
+
+    protected function queryFields(array $args)
+    {
+        $result  = [];
+        $fields  = [];
+        $aliases = [];
+        foreach ($args as $alias => $rawFields) {
+            $aliases[$alias] = [];
+            foreach ($rawFields as $originalName => $replName) {
+                if (null === $replName || false === $replName) {
+                    continue;
+                }
+
+                $name = $alias . '.' . $originalName;
+
+                if (true === $replName && isset($fields[$originalName])) {
+                    $replName = "alias_{$alias}_{$originalName}";
+                    $result[] = $name . ' AS '. $replName;
+                    $aliases[$alias][$replName] = $originalName;
+                    $fields[$replName] = $alias;
+                } elseif (true === $replName) {
+                    $result[] = $name;
+                    $aliases[$alias][$originalName] = true;
+                    $fields[$originalName] = $alias;
+                } else {
+                    $result[] = $name . ' AS '. $replName;
+                    $aliases[$alias][$replName] = $replName; //???? $originalName;
+                    $fields[$replName] = $alias;
+                }
+            }
+        }
+        $this->aliases = $aliases;
+
+        return implode(', ', $result);
+    }
+
+    protected function setData(array $args, array $data)
+    {
+        foreach ($args as $aliases => $model) {
+            $attrs = [];
+            foreach (explode('.', $aliases) as $alias) {
+                if (empty($this->aliases[$alias])) {
+                    continue;
+                }
+                foreach ($this->aliases[$alias] as $key => $repl) {
+                    $name = true === $repl ? $key : $repl;
+                    $attrs[$name] = $data[$key];
+                }
+            }
+            $model->setAttrs($attrs);
+        }
+    }
+
     /**
     /**
      * Возвращает список сообщений
      * Возвращает список сообщений
      *
      *
      * @param mixed $arg
      * @param mixed $arg
-     * 
+     *
      * @throws InvalidArgumentException
      * @throws InvalidArgumentException
+     * @throws RuntimeException
      *
      *
      * @return array
      * @return array
      */
      */
     public function view($arg)
     public function view($arg)
     {
     {
-        $stickFP = false;
-
         if ($arg instanceof Topic) {
         if ($arg instanceof Topic) {
-            if (! $arg->hasPage()) {
-                throw new InvalidArgumentException('Bad number of displayed page');
-            }
-
-            $offset = ($arg->page - 1) * $this->c->user->disp_posts;
-            $vars = [
-                ':tid'    => $arg->id,
-                ':offset' => $offset,
-                ':rows'   => $this->c->user->disp_posts,
-            ];
-            $sql = 'SELECT id
-                    FROM ::posts
-                    WHERE topic_id=?i:tid
-                    ORDER BY id LIMIT ?i:offset, ?i:rows';
-            $list = $this->c->DB->query($sql, $vars)->fetchAll(PDO::FETCH_COLUMN);
-
-            if (empty($list)) {
-                return [];
-            }
-
-            // приклейка первого сообщения темы
-            if (($arg->stick_fp || $arg->poll_type) && ! in_array($arg->first_post_id, $list)) {
-                array_unshift($list, $arg->first_post_id);
-                $stickFP = true;
-            }
-
-        } elseif (is_array($arg)) {
-            $list = $arg; //????
+            $expanded = false;
+        } elseif ($arg instanceof Search) {
+            $expanded = true;
         } else {
         } else {
-            throw new InvalidArgumentException('Expected Topic or array');
+            throw new InvalidArgumentException('Expected Topic or Search');
+        }
+
+        if (empty($arg->idsList) || ! is_array($arg->idsList)) {
+            throw new RuntimeException('Model does not contain of posts list for display');
         }
         }
 
 
         $vars = [
         $vars = [
-            ':ids' => $list,
+            ':ids' => $arg->idsList,
         ];
         ];
         $sql = 'SELECT id, message, poster, posted
         $sql = 'SELECT id, message, poster, posted
                 FROM ::warnings
                 FROM ::warnings
                 WHERE id IN (?ai:ids)';
                 WHERE id IN (?ai:ids)';
         $warnings = $this->c->DB->query($sql, $vars)->fetchAll(PDO::FETCH_GROUP);
         $warnings = $this->c->DB->query($sql, $vars)->fetchAll(PDO::FETCH_GROUP);
 
 
-        //????
-        $sql = 'SELECT u.warning_all, u.gender, u.email, u.title, u.url, u.location, u.signature,
-                       u.email_setting, u.num_posts, u.registered, u.admin_note, u.messages_enable,
-                       u.group_id,
-                       p.id, p.poster as username, p.poster_id, p.poster_ip, p.poster_email, p.message,
-                       p.hide_smilies, p.posted, p.edited, p.edited_by, p.edit_post, p.user_agent, p.topic_id,
-                       g.g_user_title, g.g_promote_next_group, g.g_pm
-                FROM ::posts AS p
-                INNER JOIN ::users AS u ON u.id=p.poster_id
-                INNER JOIN ::groups AS g ON g.g_id=u.group_id
-                WHERE p.id IN (?ai:ids)';
+        if (! $expanded) {
+            $vars = [
+                ':ids' => $arg->idsList,
+                ':fields' => $this->queryFields([
+                    'p' => array_map(function($val) {return true;}, $this->c->dbMap->posts), // все поля в true
+                    'u' => array_map(function($val) {return true;}, $this->c->dbMap->users), // все поля в true
+                    'g' => array_map(function($val) {return true;}, $this->c->dbMap->groups), // все поля в true
+                ]),
+            ];
+
+            $sql = 'SELECT ?p:fields
+                    FROM ::posts AS p
+                    INNER JOIN ::users AS u ON u.id=p.poster_id
+                    INNER JOIN ::groups AS g ON g.g_id=u.group_id
+                    WHERE p.id IN (?ai:ids)';
+        } else {
+
+        }
+
         $stmt = $this->c->DB->query($sql, $vars);
         $stmt = $this->c->DB->query($sql, $vars);
 
 
-        $result = array_flip($list);
+        $result = array_flip($arg->idsList);
+
         while ($row = $stmt->fetch()) {
         while ($row = $stmt->fetch()) {
+            $post = $this->manager->create();
+            $user = $this->c->users->create();
+            $this->setData(['p' => $post, 'u.g' => $user], $row);
             if (isset($warnings[$row['id']])) {
             if (isset($warnings[$row['id']])) {
-                $row['warnings'] = $warnings[$row['id']];
+                $post->__warnings = $warnings[$row['id']];
+            }
+            $result[$post->id] = $post;
+            if (! $this->c->users->get($user->id) instanceof User) {
+                $this->c->users->set($user->id, $user);
             }
             }
-            $result[$row['id']] = $this->manager->create($row);
         }
         }
 
 
+        $offset    = ($arg->page - 1) * $this->c->user->disp_posts;
         $postCount = 0;
         $postCount = 0;
         $timeMax   = 0;
         $timeMax   = 0;
 
 
@@ -93,7 +143,7 @@ class View extends Action
                 if ($post->posted > $timeMax) {
                 if ($post->posted > $timeMax) {
                     $timeMax = $post->posted;
                     $timeMax = $post->posted;
                 }
                 }
-                if ($stickFP && $post->id === $arg->first_post_id) {
+                if ($post->id === $arg->first_post_id && $offset > 0) {
                     $post->postNumber = 1;
                     $post->postNumber = 1;
                 } else {
                 } else {
                     ++$postCount;
                     ++$postCount;

+ 12 - 5
app/Models/Search/ActionT.php

@@ -11,7 +11,7 @@ use RuntimeException;
 class ActionT extends Method
 class ActionT extends Method
 {
 {
     /**
     /**
-     * Действия с темами
+     * Поисковые действия по темам
      *
      *
      * @param string $action
      * @param string $action
      *
      *
@@ -26,7 +26,11 @@ class ActionT extends Method
             return []; //????
             return []; //????
         }
         }
 
 
+        $sql = null;
         switch ($action) {
         switch ($action) {
+            case 'search':
+                $list = $this->model->queryIds;
+                break;
             case 'last':
             case 'last':
                 $sql = 'SELECT t.id
                 $sql = 'SELECT t.id
                         FROM ::topics AS t
                         FROM ::topics AS t
@@ -42,10 +46,13 @@ class ActionT extends Method
             default:
             default:
                 throw new InvalidArgumentException('Unknown action: ' . $action);
                 throw new InvalidArgumentException('Unknown action: ' . $action);
         }
         }
-        $vars = [
-            ':forums' => array_keys($root->descendants),
-        ];
-        $list = $this->c->DB->query($sql, $vars)->fetchAll(PDO::FETCH_COLUMN);
+
+        if (null !== $sql) {
+            $vars = [
+                ':forums' => array_keys($root->descendants),
+            ];
+            $list = $this->c->DB->query($sql, $vars)->fetchAll(PDO::FETCH_COLUMN);
+        }
 
 
         $this->model->numPages = (int) ceil((count($list) ?: 1) / $this->c->user->disp_topics);
         $this->model->numPages = (int) ceil((count($list) ?: 1) / $this->c->user->disp_topics);
 
 

+ 147 - 111
app/Models/Search/Execute.php

@@ -2,6 +2,7 @@
 
 
 namespace ForkBB\Models\Search;
 namespace ForkBB\Models\Search;
 
 
+use ForkBB\Core\Validator;
 use ForkBB\Models\Method;
 use ForkBB\Models\Method;
 use ForkBB\Models\Forum\Model as Forum;
 use ForkBB\Models\Forum\Model as Forum;
 use ForkBB\Models\Post\Model as Post;
 use ForkBB\Models\Post\Model as Post;
@@ -19,40 +20,83 @@ class Execute extends Method
     protected $stmtCJK;
     protected $stmtCJK;
 
 
     /**
     /**
-     * @param array $options
+     * Поиск тем/сообщений в соответствии с поисковым запросом
+     * Получение данных из таблицы кеша
+     * Сохранение результатов в таблицу кеша
+     *
+     * @param Validator $v
+     * @param array $forumIdxs
+     * @param bool $flood
      *
      *
      * @throws RuntimeException
      * @throws RuntimeException
      *
      *
-     * @return array
+     * @return bool
      */
      */
-    public function execute(array $options)
+    public function execute(Validator $v, array $forumIdxs, $flood)
     {
     {
         if (! is_array($this->model->queryWords) || ! is_string($this->model->queryText)) {
         if (! is_array($this->model->queryWords) || ! is_string($this->model->queryText)) {
-            throw new InvalidArgumentException('No query data');
+            throw new RuntimeException('No query data');
         }
         }
 
 
-echo '<pre>';
-var_dump($this->model->queryText);
-
         $this->words   = [];
         $this->words   = [];
         $this->stmtIdx = null;
         $this->stmtIdx = null;
         $this->stmtCJK = null;
         $this->stmtCJK = null;
-        $vars          = $this->buildSelect($options);
-
-var_dump($this->queryIdx, $this->queryCJK);
+        $queryVars     = $this->buildSelect($v, $forumIdxs);
+
+        $key = $this->c->user->group_id . '-' .
+               $v->serch_in .
+               $v->sort_by .
+               $v->sort_dir .
+               $this->model->showAs . '-' .
+               $this->model->queryText . '-' . // $v->keywords
+               $v->author . '-' .
+               $v->forums;
+
+        $vars = [
+            ':key' => $key,
+        ];
+        $sql = 'SELECT search_time, search_data
+                FROM ::search_cache
+                WHERE search_key=?s:key
+                ORDER BY search_time DESC
+                LIMIT 1';
+        $row = $this->c->DB->query($sql, $vars)->fetch();
+
+        if (! empty($row['search_time']) && time() - $row['search_time'] < 60 * 5) { //????
+            $result                    = explode("\n", $row['search_data']);
+            $this->model->queryIds     = explode(',', $result[0]);
+            $this->model->queryNoCache = false;
+            return true;
+        } elseif ($flood) {
+            return false;
+        }
 
 
-        $ids = $this->exec($this->model->queryWords, $vars);
+        $ids = $this->exec($this->model->queryWords, $queryVars);
 
 
-        if ('asc' === $options['sort_dir']) {
+        if (1 === $v->sort_dir) {
             asort($ids, $this->sortType);
             asort($ids, $this->sortType);
         } else {
         } else {
             arsort($ids, $this->sortType);
             arsort($ids, $this->sortType);
         }
         }
 
 
-var_dump($ids);
-echo '</pre>';
+        $ids = array_keys($ids);
 
 
-        return $ids;
+        $data = [
+            implode(',', $ids),
+        ];
+        $vars = [
+            ':data' => implode("\n", $data),
+            ':key'  => $key,
+            ':time' => time(),
+        ];
+        $sql = 'INSERT INTO ::search_cache (search_key, search_time, search_data)
+                VALUES (?s:key, ?i:time, ?s:data)';
+        $this->c->DB->exec($sql, $vars);
+
+        $this->model->queryIds     = $ids;
+        $this->model->queryNoCache = true;
+
+        return true;
     }
     }
 
 
     /**
     /**
@@ -70,9 +114,6 @@ echo '</pre>';
         $ids   = [];
         $ids   = [];
 
 
         foreach ($words as $word) {
         foreach ($words as $word) {
-
-var_dump($word);
-
             // служебное слово
             // служебное слово
             if ('AND' === $word || 'OR' === $word || 'NOT' === $word) {
             if ('AND' === $word || 'OR' === $word || 'NOT' === $word) {
                 $type = $word;
                 $type = $word;
@@ -119,7 +160,6 @@ var_dump($word);
                     }
                     }
                 }
                 }
 
 
-var_dump($list);
                 if (! $count) {
                 if (! $count) {
                     $ids = $list;
                     $ids = $list;
                 } elseif ('AND' === $type) {
                 } elseif ('AND' === $type) {
@@ -138,143 +178,139 @@ var_dump($list);
     }
     }
 
 
     /**
     /**
-     * @param array $options
+     * Создание sql запросов к поисковому индексу и к сообщениям/темам
+     *
+     * @param Validator $v
+     * @param array $forumIdxs
      *
      *
      * @return array
      * @return array
      */
      */
-    protected function buildSelect(array $options)
+    protected function buildSelect(Validator $v, array $forumIdxs)
     {
     {
-        # ["keywords"]=> string(5) "fnghj"
-        # ["author"]  => string(0) ""
-        # ["forums"]  => NULL
-        # ["serch_in"]=> string(3) "all"
-        # ["sort_by"] => string(4) "post"
-        # ["sort_dir"]=> string(4) "desc"
-        # ["show_as"] => string(5) "posts"
-        $vars  = [];
+        $vars     = [];
         $whereIdx = [];
         $whereIdx = [];
         $whereCJK = [];
         $whereCJK = [];
-        $joinTIdx = false;
-        $joinPIdx = false;
-        $useT     = false;
-        $useP     = false;
-
-        if (! empty($options['forums'])) {
-            $joinTIdx               = true;
-            $whereIdx[]             = 't.forum_id IN (?ai:forums)';
-            $whereCJK[]             = 't.forum_id IN (?ai:forums)';
-            $useT                   = true;
-            $vars[':forums']        = (array) $options['forums'];
+        $useTIdx  = false;
+        $usePIdx  = false;
+        $useTCJK  = false;
+        $usePCJK  = false;
+
+        if ('*' !== $v->forums || ! $this->c->user->isAdmin) {
+            $useTIdx                 = true;
+            $whereIdx[]              = 't.forum_id IN (?ai:forums)';
+            $whereCJK[]              = 't.forum_id IN (?ai:forums)';
+            $useTCJK                 = true;
+            $vars[':forums']         = '*' === $v->forums ? $forumIdxs : explode('.', $v->forums);
         }
         }
 
 
-        //???? нужен индекс по авторам сообщений/тем
+        //???? нужен индекс по авторам сообщений/тем?
         //???? что делать с подчеркиванием в именах?
         //???? что делать с подчеркиванием в именах?
-        if ('' != $options['author']) {
-            $joinPIdx               = true;
-            $vars[':author']        = str_replace(['*', '?'], ['%', '_'], $options['author']);
-            $whereIdx[]             = 'p.poster LIKE ?s:author';
+        if ('*' !== $v->author) {
+            $usePIdx                 = true;
+            $vars[':author']         = str_replace(['*', '?'], ['%', '_'], $v->author);
+            $whereIdx[]              = 'p.poster LIKE ?s:author';
         }
         }
 
 
-        switch ($options['serch_in']) {
-            case 'posts':
-                $whereIdx[]         = 'm.subject_match=0';
-                $whereCJK[]         = 'p.message LIKE ?s:word';
-                $useP               = true;
+        $this->model->showAs         = $v->show_as;
+
+        switch ($v->serch_in) {
+            case 1:
+                $whereIdx[]          = 'm.subject_match=0';
+                $whereCJK[]          = 'p.message LIKE ?s:word';
+                $usePCJK             = true;
                 if (isset($vars[':author'])) {
                 if (isset($vars[':author'])) {
-                    $whereCJK[]     = 'p.poster LIKE ?s:author';
+                    $whereCJK[]      = 'p.poster LIKE ?s:author';
                 }
                 }
                 break;
                 break;
-            case 'topics':
-                $whereIdx[]         = 'm.subject_match=1';
-                $whereCJK[]         = 't.subject LIKE ?s:word';
-                $useT               = true;
+            case 2:
+                $whereIdx[]          = 'm.subject_match=1';
+                $whereCJK[]          = 't.subject LIKE ?s:word';
+                $useTCJK             = true;
                 if (isset($vars[':author'])) {
                 if (isset($vars[':author'])) {
-                    $whereCJK[]     = 't.poster LIKE ?s:author';
+                    $whereCJK[]      = 't.poster LIKE ?s:author';
                 }
                 }
                 // при поиске в заголовках результат только в виде списка тем
                 // при поиске в заголовках результат только в виде списка тем
-                $options['show_as'] = 'topics';
+                $this->model->showAs = 1;
                 break;
                 break;
             default:
             default:
                 if (isset($vars[':author'])) {
                 if (isset($vars[':author'])) {
-                    $whereCJK[]     = '((p.message LIKE ?s:word AND p.poster LIKE ?s:author) OR (t.subject LIKE ?s:word AND t.poster LIKE ?s:author))';
+                    $whereCJK[]      = '((p.message LIKE ?s:word AND p.poster LIKE ?s:author) OR (t.subject LIKE ?s:word AND t.poster LIKE ?s:author))';
                 } else {
                 } else {
-                    $whereCJK[]     = '(p.message LIKE ?s:word OR t.subject LIKE ?s:word)';
+                    $whereCJK[]      = '(p.message LIKE ?s:word OR t.subject LIKE ?s:word)';
                 }
                 }
-                $useP               = true;
-                $useT               = true;
+                $usePCJK             = true;
+                $useTCJK             = true;
                 break;
                 break;
         }
         }
 
 
-        if ('topics' === $options['show_as']) {
-            $showTopics             = true;
-            $joinPIdx               = true;
-            $selectFIdx             = 'p.topic_id';
-            $selectFCJK             = 't.id';
-            $useT                   = true;
+        if (1 === $this->model->showAs) {
+            $usePIdx                 = true;
+            $selectFIdx              = 'p.topic_id';
+            $selectFCJK              = 't.id';
+            $useTCJK                 = true;
         } else {
         } else {
-            $showTopics             = false;
-            $selectFIdx             = 'm.post_id';
-            $selectFCJK             = 'p.id';
-            $useP                   = true;
+            $selectFIdx              = 'm.post_id';
+            $selectFCJK              = 'p.id';
+            $usePCJK                 = true;
         }
         }
 
 
-        switch ($options['sort_by']) {
-            case 'author':
-                if ($showTopics) {
-                    $sortIdx        = 't.poster';
-                    $sortCJK        = 't.poster';
-                    $joinTIdx       = true;
-                    $useT           = true;
+        switch ($v->sort_by) {
+            case 1:
+                if (1 === $this->model->showAs) {
+                    $sortIdx         = 't.poster';
+                    $sortCJK         = 't.poster';
+                    $useTIdx         = true;
+                    $useTCJK         = true;
                 } else {
                 } else {
-                    $sortIdx        = 'p.poster';
-                    $sortCJK        = 'p.poster';
-                    $joinPIdx       = true;
-                    $useP           = true;
+                    $sortIdx         = 'p.poster';
+                    $sortCJK         = 'p.poster';
+                    $usePIdx         = true;
+                    $usePCJK         = true;
                 }
                 }
-                $this->sortType     = SORT_STRING;
+                $this->sortType      = SORT_STRING;
                 break;
                 break;
-            case 'subject':
-                $sortIdx            = 't.subject';
-                $sortCJK            = 't.subject';
-                $joinTIdx           = true;
-                $useT               = true;
-                $this->sortType     = SORT_STRING;
+            case 2:
+                $sortIdx             = 't.subject';
+                $sortCJK             = 't.subject';
+                $useTIdx             = true;
+                $useTCJK             = true;
+                $this->sortType      = SORT_STRING;
                 break;
                 break;
-            case 'forum':
-                $sortIdx            = 't.forum_id';
-                $sortCJK            = 't.forum_id';
-                $joinTIdx           = true;
-                $useT               = true;
-                $this->sortType     = SORT_NUMERIC;
+            case 3:
+                $sortIdx             = 't.forum_id';
+                $sortCJK             = 't.forum_id';
+                $useTIdx             = true;
+                $useTCJK             = true;
+                $this->sortType      = SORT_NUMERIC;
                 break;
                 break;
             default:
             default:
-                if ($showTopics) {
-                    $sortIdx        = 't.last_post';
-                    $sortCJK        = 't.last_post';
-                    $joinTIdx       = true;
-                    $useT           = true;
+                if (1 === $this->model->showAs) {
+                    $sortIdx         = 't.last_post';
+                    $sortCJK         = 't.last_post';
+                    $useTIdx         = true;
+                    $useTCJK         = true;
                 } else {
                 } else {
-                    $sortIdx        = 'm.post_id';
-                    $sortCJK        = 'p.id';
-                    $useP           = true;
+                    $sortIdx         = 'm.post_id';
+                    $sortCJK         = 'p.id';
+                    $usePCJK         = true;
                 }
                 }
-                $this->sortType     = SORT_NUMERIC;
+                $this->sortType      = SORT_NUMERIC;
                 break;
                 break;
         }
         }
 
 
-        $joinPIdx = $joinPIdx || $joinTIdx ? 'INNER JOIN ::posts AS p ON p.id=m.post_id '   : '';
-        $joinTIdx = $joinTIdx           ? 'INNER JOIN ::topics AS t ON t.id=p.topic_id ' : '';
-        $whereIdx = empty($whereIdx)    ? '' : ' AND ' . implode(' AND ', $whereIdx);
+        $usePIdx  = $usePIdx || $useTIdx ? 'INNER JOIN ::posts AS p ON p.id=m.post_id '   : '';
+        $useTIdx  = $useTIdx             ? 'INNER JOIN ::topics AS t ON t.id=p.topic_id ' : '';
+        $whereIdx = empty($whereIdx)     ? '' : ' AND ' . implode(' AND ', $whereIdx);
 
 
         $this->queryIdx = "SELECT {$selectFIdx}, {$sortIdx} FROM ::search_words AS w " .
         $this->queryIdx = "SELECT {$selectFIdx}, {$sortIdx} FROM ::search_words AS w " .
                           'INNER JOIN ::search_matches AS m ON m.word_id=w.id ' .
                           'INNER JOIN ::search_matches AS m ON m.word_id=w.id ' .
-                          $joinPIdx .
-                          $joinTIdx .
+                          $usePIdx .
+                          $useTIdx .
                           'WHERE w.word LIKE ?s:word' . $whereIdx;
                           'WHERE w.word LIKE ?s:word' . $whereIdx;
 
 
-        if ($useP) {
+        if ($usePCJK) {
             $this->queryCJK = "SELECT {$selectFCJK}, {$sortCJK} FROM ::posts AS p " .
             $this->queryCJK = "SELECT {$selectFCJK}, {$sortCJK} FROM ::posts AS p " .
-                              ($useT ? 'INNER JOIN ::topics AS t ON t.id=p.topic_id ' : '') .
+                              ($useTCJK ? 'INNER JOIN ::topics AS t ON t.id=p.topic_id ' : '') .
                               'WHERE ' . implode(' AND ', $whereCJK);
                               'WHERE ' . implode(' AND ', $whereCJK);
         } else {
         } else {
             $this->queryCJK = "SELECT {$selectFCJK}, {$sortCJK} FROM ::topics AS t " .
             $this->queryCJK = "SELECT {$selectFCJK}, {$sortCJK} FROM ::topics AS t " .

+ 2 - 2
app/Models/Search/Prepare.php

@@ -158,9 +158,9 @@ class Prepare extends Method
         }
         }
 
 
         if (! $count) {
         if (! $count) {
-            $error = 'There is no word for search: "%s"';
+            $error = 'There is no word for search: \'%s\'';
         } else if ($keyword) {
         } else if ($keyword) {
-            $error = 'Syntactic word at the end of the search query: "%s"';
+            $error = 'Syntactic word at the end of the search query: \'%s\'';
         } elseif (! empty($stack)) {
         } elseif (! empty($stack)) {
             $error = 'The order of brackets is broken: \'%s\'';
             $error = 'The order of brackets is broken: \'%s\'';
         }
         }

+ 35 - 0
app/Models/Topic/Model.php

@@ -3,6 +3,7 @@
 namespace ForkBB\Models\Topic;
 namespace ForkBB\Models\Topic;
 
 
 use ForkBB\Models\DataModel;
 use ForkBB\Models\DataModel;
+use PDO;
 use RuntimeException;
 use RuntimeException;
 
 
 class Model extends DataModel
 class Model extends DataModel
@@ -224,6 +225,40 @@ class Model extends DataModel
         return $this->page > 0 && $this->page <= $this->numPages;
         return $this->page > 0 && $this->page <= $this->numPages;
     }
     }
 
 
+    /**
+     * Возвращает массив сообщений с установленной страницы
+     *
+     * @throws InvalidArgumentException
+     *
+     * @return array
+     */
+    public function pageData()
+    {
+        if (! $this->hasPage()) {
+            throw new InvalidArgumentException('Bad number of displayed page');
+        }
+
+        $vars = [
+            ':tid'    => $this->id,
+            ':offset' => ($this->page - 1) * $this->c->user->disp_posts,
+            ':rows'   => $this->c->user->disp_posts,
+        ];
+        $sql = 'SELECT id
+                FROM ::posts
+                WHERE topic_id=?i:tid
+                ORDER BY id
+                LIMIT ?i:offset, ?i:rows';
+        $list = $this->c->DB->query($sql, $vars)->fetchAll(PDO::FETCH_COLUMN);
+
+        if (! empty($list) && ($this->stick_fp || $this->poll_type) && ! in_array($this->first_post_id, $list)) {
+            array_unshift($list, $this->first_post_id);
+        }
+
+        $this->idsList = $list;
+
+        return empty($this->idsList) ? [] : $this->c->posts->view($this);
+    }
+
     /**
     /**
      * Вычисляет страницу темы на которой находится данное сообщение
      * Вычисляет страницу темы на которой находится данное сообщение
      *
      *

+ 7 - 6
app/Models/Topic/View.php

@@ -18,7 +18,8 @@ class View extends Action
      * @param mixed $arg
      * @param mixed $arg
      *
      *
      * @throws InvalidArgumentException
      * @throws InvalidArgumentException
-     * 
+     * @throws RuntimeException
+     *
      * @return array
      * @return array
      */
      */
     public function view($arg)
     public function view($arg)
@@ -32,7 +33,7 @@ class View extends Action
         }
         }
 
 
         if (empty($arg->idsList) || ! is_array($arg->idsList)) {
         if (empty($arg->idsList) || ! is_array($arg->idsList)) {
-            throw new RuntimeException('Model does not contain a list of topics to display');
+            throw new RuntimeException('Model does not contain of topics list for display');
         }
         }
 
 
         $vars = [
         $vars = [
@@ -41,9 +42,9 @@ class View extends Action
         ];
         ];
 
 
         if (! $this->c->user->isGuest && '1' == $this->c->config->o_show_dot) {
         if (! $this->c->user->isGuest && '1' == $this->c->config->o_show_dot) {
-            $sql = 'SELECT topic_id 
-                    FROM ::posts 
-                    WHERE poster_id=?i:uid AND topic_id IN (?ai:ids) 
+            $sql = 'SELECT topic_id
+                    FROM ::posts
+                    WHERE poster_id=?i:uid AND topic_id IN (?ai:ids)
                     GROUP BY topic_id';
                     GROUP BY topic_id';
             $dots = $this->c->DB->query($sql, $vars)->fetchAll(PDO::FETCH_COLUMN);
             $dots = $this->c->DB->query($sql, $vars)->fetchAll(PDO::FETCH_COLUMN);
             $dots = array_flip($dots);
             $dots = array_flip($dots);
@@ -73,7 +74,7 @@ class View extends Action
         while ($row = $stmt->fetch()) {
         while ($row = $stmt->fetch()) {
             $row['dot'] = isset($dots[$row['id']]);
             $row['dot'] = isset($dots[$row['id']]);
             $result[$row['id']] = $this->manager->create($row);
             $result[$row['id']] = $this->manager->create($row);
-            if ($expanded) {
+            if ($expanded && ! $this->c->user->isGuest) {
                 $result[$row['id']]->parent->__mf_mark_all_read = $row['mf_mark_all_read'];
                 $result[$row['id']]->parent->__mf_mark_all_read = $row['mf_mark_all_read'];
             }
             }
         }
         }

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

@@ -1196,6 +1196,7 @@ select {
     padding-right: 0.625rem;
     padding-right: 0.625rem;
     vertical-align: middle;
     vertical-align: middle;
     font-size: 0.875rem;
     font-size: 0.875rem;
+    text-align: right;
   }
   }
 
 
   .f-ftlist .f-cltopic,
   .f-ftlist .f-cltopic,