Visman %!s(int64=7) %!d(string=hai) anos
pai
achega
9ff60e147e

+ 28 - 7
app/Models/Forum/Manager.php

@@ -17,9 +17,9 @@ class Manager extends ManagerModel
 
     /**
      * Создает новую модель раздела
-     * 
+     *
      * @param array $attrs
-     * 
+     *
      * @return Forum
      */
     public function create(array $attrs = [])
@@ -29,7 +29,7 @@ class Manager extends ManagerModel
 
     /**
      * Инициализация списка разделов
-     * 
+     *
      * @param Group $group
      *
      * @return Manager
@@ -61,9 +61,9 @@ class Manager extends ManagerModel
 
     /**
      * Получение модели раздела
-     * 
+     *
      * @param int $id
-     * 
+     *
      * @return null|Forum
      */
     public function get($id)
@@ -85,7 +85,7 @@ class Manager extends ManagerModel
      * Обновляет раздел в БД
      *
      * @param Forum $forum
-     * 
+     *
      * @return Forum
      */
     public function update(Forum $forum)
@@ -97,7 +97,7 @@ class Manager extends ManagerModel
      * Добавляет новый раздел в БД
      *
      * @param Forum $forum
-     * 
+     *
      * @return int
      */
     public function insert(Forum $forum)
@@ -106,4 +106,25 @@ class Manager extends ManagerModel
         $this->set($id, $forum);
         return $id;
     }
+
+    /**
+     * Получение списка разделов и подразделов с указанием глубины вложения
+     *
+     * @param Forum $forum
+     * @param int $depth
+     * @param array $list
+     *
+     * @return array
+     */
+    public function depthList(Forum $forum, $depth, array $list = [])
+    {
+        ++$depth;
+        foreach ($forum->subforums as $sub) {
+            $sub->__depth = $depth;
+            $list[] = $sub;
+
+            $list = $this->depthList($sub, $depth, $list);
+        }
+        return $list;
+    }
 }

+ 22 - 4
app/Models/Page.php

@@ -172,13 +172,13 @@ class Page extends Model
 
     /**
      * Добавляет стиль на страницу
-     * 
+     *
      * @param string $name
      * @param string $val
-     * 
+     *
      * @return Page
      */
-    public function addStyle($name, $val) 
+    public function addStyle($name, $val)
     {
         $this->a['pageHeaders']['style'][$name] = $val;
         return $this;
@@ -230,7 +230,7 @@ class Page extends Model
 
     /**
      * Дописывает в массив титула страницы новый элемент
-     * $this->titles
+     * $this->titles = ...
      *
      * @param string $val
      */
@@ -242,4 +242,22 @@ class Page extends Model
             $this->a['titles'][] = $val;
         }
     }
+
+    /**
+     * Добавление новой ошибки
+     * $this->fIswev = ...
+     *
+     * @param array $val
+     */
+    public function setfIswev(array $val)
+    {
+        if (empty($this->a['fIswev'])) {
+            $this->a['fIswev'] = [];
+        }
+        if (isset($val[0]) && isset($val[1]) && is_string($val[0]) && is_string($val[1])) {
+            $this->a['fIswev'][$val[0]][] = $val[1];
+        } else {
+            $this->a['fIswev'] = array_merge_recursive((array) $this->a['fIswev'], $val);
+        }
+    }
 }

+ 12 - 33
app/Models/Pages/Admin/Forums.php

@@ -8,30 +8,9 @@ use ForkBB\Models\Pages\Admin;
 
 class Forums extends Admin
 {
-    /**
-     * Получение списка разделов и подразделов
-     * 
-     * @param Forum $forum
-     * @param int $depth
-     * @param array $list
-     * 
-     * @return array
-     */
-    protected function forumsList(Forum $forum, $depth, array $list = [])
-    {
-        ++$depth;
-        foreach ($forum->subforums as $sub) {
-            $sub->__depth = $depth;
-            $list[] = $sub;
-    
-            $list = $this->forumsList($sub, $depth, $list);
-        }
-        return $list;
-    }
-
     /**
      * Составление списка категорий/разделов для выбора родителя
-     * 
+     *
      * @param Forum $forum
      */
     protected function calcList(Forum $forum)
@@ -44,16 +23,16 @@ class Forums extends Admin
         $idxs       = [];
         $root = $this->c->forums->get(0);
         if ($root instanceof Forum) {
-            foreach ($this->forumsList($root, 0) as $f) {
+            foreach ($this->c->forums->depthList($root, 0) as $f) {
                 if ($cid !== $f->cat_id) {
                     $cid       = $f->cat_id;
                     $options[] = [-$cid, \ForkBB\__('Category prefix') . $f->cat_name];
                     $idxs[]    = -$cid;
                     unset($categories[$cid]);
                 }
-                
+
                 $indent = str_repeat(\ForkBB\__('Forum indent'), $f->depth);
-    
+
                 if ($f->id === $forum->id || isset($forum->descendants[$f->id]) || $f->redirect_url) {
                     $options[] = [$f->id, $indent . \ForkBB\__('Forum prefix') . $f->forum_name, true];
                 } else {
@@ -72,9 +51,9 @@ class Forums extends Admin
 
     /**
      * Вычисление позиции для (нового) раздела
-     * 
+     *
      * @param Forum $forum
-     * 
+     *
      * @return int
      */
     protected function forumPos(Forum $forum)
@@ -163,7 +142,7 @@ class Forums extends Admin
         $root = $this->c->forums->get(0);
 
         if ($root instanceof Forum) {
-            $list = $this->forumsList($root, -1);
+            $list = $this->c->forums->depthList($root, -1);
 
             $fieldset = [];
             $cid = null;
@@ -175,7 +154,7 @@ class Forums extends Admin
                         ];
                         $fieldset = [];
                     }
-    
+
                     $form['sets'][] = [
                         'info' => [
                             'info1' => [
@@ -186,7 +165,7 @@ class Forums extends Admin
                     ];
                     $cid = $forum->cat_id;
                 }
-    
+
                 $fieldset[] = [
                     'dl'        => ['name', 'inline', 'depth' . $forum->depth],
                     'type'      => 'btn',
@@ -212,7 +191,7 @@ class Forums extends Admin
                     'disabled' => $disabled,
                 ];
             }
-    
+
             $form['sets'][] = [
                 'fields' => $fieldset,
             ];
@@ -326,7 +305,7 @@ class Forums extends Admin
 
     /**
      * Редактирование раздела
-     * Создание нового раздела 
+     * Создание нового раздела
      *
      * @param array $args
      * @param string $method
@@ -405,7 +384,7 @@ class Forums extends Admin
                         $message = 'Forum updated redirect';
                         $this->c->forums->update($forum);
                     }
-    
+
                     $this->c->groups->Perm->update($forum, $v->perms);
                 }
 

+ 20 - 20
app/Models/Pages/Auth.php

@@ -17,9 +17,9 @@ class Auth extends Page
 
     /**
      * Выход пользователя
-     * 
+     *
      * @param array $args
-     * 
+     *
      * @return Page
      */
     public function logout($args)
@@ -38,10 +38,10 @@ class Auth extends Page
 
     /**
      * Вход на форум
-     * 
+     *
      * @param array $args
      * @param string $method
-     * 
+     *
      * @return Page
      */
     public function login(array $args, $method)
@@ -63,16 +63,16 @@ class Auth extends Page
                     'username' => 'Username',
                     'password' => 'Passphrase',
                 ]);
-    
+
             if ($v->validation($_POST)) {
                 return $this->c->Redirect->url($v->redirect)->message('Login redirect');
             }
-            
+
             $this->fIswev = $v->getErrors();
         }
 
         $ref = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
-        
+
         $this->fIndex     = 'login';
         $this->nameTpl    = 'login';
         $this->onlinePos  = 'login';
@@ -91,10 +91,10 @@ class Auth extends Page
 
     /**
      * Проверка по базе и вход
-     * 
+     *
      * @param Validator $v
      * @param string $password
-     * 
+     *
      * @return string
      */
     public function vLoginProcess(Validator $v, $password)
@@ -144,10 +144,10 @@ class Auth extends Page
 
     /**
      * Запрос на смену кодовой фразы
-     * 
+     *
      * @param array $args
      * @param string $method
-     * 
+     *
      * @return Page
      */
     public function forget(array $args, $method)
@@ -177,7 +177,7 @@ class Auth extends Page
                     'username' => $this->tmpUser->username,
                     'link' => $link,
                 ];
-        
+
                 try {
                     $isSent = $this->c->Mail
                         ->reset()
@@ -217,10 +217,10 @@ class Auth extends Page
 
     /**
      * Дополнительная проверка email
-     * 
+     *
      * @param Validator $v
      * @param string $email
-     * 
+     *
      * @return string
      */
     public function vCheckEmail(Validator $v, $email)
@@ -243,10 +243,10 @@ class Auth extends Page
 
     /**
      * Смена кодовой фразы
-     * 
+     *
      * @param array $args
      * @param string $method
-     * 
+     *
      * @return Page
      */
     public function changePass(array $args, $method)
@@ -278,14 +278,14 @@ class Auth extends Page
                     'password.password'  => 'Pass format',
                     'password2.same'     => 'Pass not match',
                 ]);
-    
+
             if ($v->validation($_POST)) {
                 $user->password        = password_hash($v->password, PASSWORD_DEFAULT);
                 $user->email_confirmed = 1;
                 $user->activate_string = null;
                 $this->c->users->update($user);
-        
-                $this->a['fIswev']['s'][] = \ForkBB\__('Pass updated');
+
+                $this->fIswev = ['s', \ForkBB\__('Pass updated')];
                 return $this->login([], 'GET');
             }
 
@@ -297,7 +297,7 @@ class Auth extends Page
             $user->email_confirmed = 1;
             $this->c->users->update($user);
             $this->c->Cache->delete('stats');
-            $this->a['fIswev']['i'][] = \ForkBB\__('Account activated');
+            $this->fIswev = ['i', \ForkBB\__('Account activated')];
         }
 
         $this->fIndex     = 'login';

+ 3 - 3
app/Models/Pages/Forum.php

@@ -10,9 +10,9 @@ class Forum extends Page
 
     /**
      * Подготовка данных для шаблона
-     * 
+     *
      * @param array $args
-     * 
+     *
      * @return Page
      */
     public function view(array $args)
@@ -44,7 +44,7 @@ class Forum extends Page
         $this->crumbs     = $this->crumbs($forum);
 
         if (empty($this->topics)) {
-            $this->a['fIswev']['i'][] = \ForkBB\__('Empty forum');
+            $this->fIswev = ['i', \ForkBB\__('Empty forum')];
         }
 
         return $this;

+ 3 - 3
app/Models/Pages/Index.php

@@ -8,7 +8,7 @@ class Index extends Page
 {
     /**
      * Подготовка данных для шаблона
-     * 
+     *
      * @return Page
      */
     public function view()
@@ -17,7 +17,7 @@ class Index extends Page
         $this->c->Lang->load('subforums');
 
         // крайний пользователь // ???? может в stats переместить?
-        $this->c->stats->userLast = $this->c->user->g_view_users == '1' 
+        $this->c->stats->userLast = $this->c->user->g_view_users == '1'
             ? [ $this->c->Router->link('User', [
                     'id'   => $this->c->stats->userLast['id'],
                     'name' => $this->c->stats->userLast['username'],
@@ -30,7 +30,7 @@ class Index extends Page
         $forums = empty($root) ? [] : $root->subforums;
         $ctgs   = [];
         if (empty($forums)) {
-            $this->a['fIswev']['i'][] = \ForkBB\__('Empty board');
+            $this->fIswev = ['i', \ForkBB\__('Empty board')];
         } else {
             foreach($forums as $forum) {
                 $ctgs[$forum->cat_id][] = $forum;

+ 23 - 23
app/Models/Pages/Install.php

@@ -27,7 +27,7 @@ class Install extends Page
 
     /**
      * Возращает доступные типы БД
-     * 
+     *
      * @return array
      */
     protected function DBTypes()
@@ -57,10 +57,10 @@ class Install extends Page
 
     /**
      * Подготовка данных для страницы установки форума
-     * 
+     *
      * @param array $args
      * @param string $method
-     * 
+     *
      * @return Page
      */
     public function install(array $args, $method)
@@ -85,13 +85,13 @@ class Install extends Page
 
         // версия PHP
         if (version_compare(PHP_VERSION, self::PHP_MIN, '<')) {
-            $this->a['fIswev']['e'][] = \ForkBB\__('You are running error', 'PHP', PHP_VERSION, $this->c->FORK_REVISION, self::PHP_MIN);
+            $this->fIswev = ['e', \ForkBB\__('You are running error', 'PHP', PHP_VERSION, $this->c->FORK_REVISION, self::PHP_MIN)];
         }
 
         // типы БД
         $this->dbTypes = $this->DBTypes();
         if (empty($this->dbTypes)) {
-            $this->a['fIswev']['e'][] = \ForkBB\__('No DB extensions');
+            $this->fIswev = ['e', \ForkBB\__('No DB extensions')];
         }
 
         // доступность папок на запись
@@ -103,30 +103,30 @@ class Install extends Page
         foreach ($folders as $folder) {
             if (! is_writable($folder)) {
                 $folder = str_replace(dirname($this->c->DIR_APP), '', $folder);
-                $this->a['fIswev']['e'][] = \ForkBB\__('Alert folder', $folder);
+                $this->fIswev = ['e', \ForkBB\__('Alert folder', $folder)];
             }
         }
 
         // доступность шаблона конфигурации
         $config = @file_get_contents($this->c->DIR_CONFIG . '/main.dist.php');
         if (false === $config) {
-            $this->a['fIswev']['e'][] = \ForkBB\__('No access to main.dist.php');
+            $this->fIswev = ['e', \ForkBB\__('No access to main.dist.php')];
         }
         unset($config);
 
         // языки
         $langs = $this->c->Func->getLangs();
         if (empty($langs)) {
-            $this->a['fIswev']['e'][] = \ForkBB\__('No language packs');
+            $this->fIswev = ['e', \ForkBB\__('No language packs')];
         }
 
         // стили
         $styles = $this->c->Func->getStyles();
         if (empty($styles)) {
-            $this->a['fIswev']['e'][] = \ForkBB\__('No styles');
+            $this->fIswev = ['e', \ForkBB\__('No styles')];
         }
 
-        if ('POST' === $method && ! $changeLang && empty($this->a['fIswev']['e'])) {
+        if ('POST' === $method && ! $changeLang && empty($this->a['fIswev']['e'])) { //????
             $v = $this->c->Validator->reset()
                 ->addValidators([
                     'check_prefix' => [$this, 'vCheckPrefix'],
@@ -164,7 +164,7 @@ class Install extends Page
                 ])->addMessages([
                     'email'        => 'Wrong email',
                 ]);
-    
+
             if ($v->validation($_POST)) {
                 return $this->installEnd($v);
             } else {
@@ -193,8 +193,8 @@ class Install extends Page
                 ],
                 'btns'   => [
                     'changelang'  => [
-                        'type'      => 'submit', 
-                        'value'     => \ForkBB\__('Change language'), 
+                        'type'      => 'submit',
+                        'value'     => \ForkBB\__('Change language'),
                     ],
                 ],
             ];
@@ -363,8 +363,8 @@ class Install extends Page
             ],
             'btns'   => [
                 'submit'  => [
-                    'type'      => 'submit', 
-                    'value'     => \ForkBB\__('Start install'), 
+                    'type'      => 'submit',
+                    'value'     => \ForkBB\__('Start install'),
                 ],
             ],
         ];
@@ -379,10 +379,10 @@ class Install extends Page
 
     /**
      * Обработка base URL
-     * 
+     *
      * @param Validator $v
      * @param string $url
-     * 
+     *
      * @return string
      */
     public function vRtrimURL(Validator $v, $url)
@@ -392,10 +392,10 @@ class Install extends Page
 
     /**
      * Дополнительная проверка префикса
-     * 
+     *
      * @param Validator $v
      * @param string $prefix
-     * 
+     *
      * @return string
      */
     public function vCheckPrefix(Validator $v, $prefix)
@@ -412,10 +412,10 @@ class Install extends Page
 
     /**
      * Полная проверка подключения к БД
-     * 
+     *
      * @param Validator $v
      * @param string $dbhost
-     * 
+     *
      * @return string
      */
     public function vCheckHost(Validator $v, $dbhost)
@@ -482,9 +482,9 @@ class Install extends Page
 
     /**
      * Завершение установки форума
-     * 
+     *
      * @param Validator $v
-     * 
+     *
      * @return Page
      */
     protected function installEnd(Validator $v)

+ 10 - 10
app/Models/Pages/PostFormTrait.php

@@ -4,18 +4,18 @@ namespace ForkBB\Models\Pages;
 
 use ForkBB\Models\Model;
 
-trait PostFormTrait 
+trait PostFormTrait
 {
     /**
      * Возвращает данные для построения формы создания темы/сообщения
-     * 
+     *
      * @param Model $model
      * @param string $marker
      * @param array $args
      * @param bool $editPost
      * @param bool $editSubject
      * @param bool $quickReply
-     * 
+     *
      * @return array
      */
     protected function messageForm(Model $model, $marker, array $args, $editPost = false, $editSubject = false, $quickReply = false)
@@ -32,13 +32,13 @@ trait PostFormTrait
             'sets'   => [],
             'btns'   => [
                 'submit'  => [
-                    'type'      => 'submit', 
-                    'value'     => \ForkBB\__('Submit'), 
+                    'type'      => 'submit',
+                    'value'     => \ForkBB\__('Submit'),
                     'accesskey' => 's',
                 ],
                 'preview' => [
-                    'type'      => 'submit', 
-                    'value'     => \ForkBB\__('Preview'), 
+                    'type'      => 'submit',
+                    'value'     => \ForkBB\__('Preview'),
                     'accesskey' => 'p',
                     'class'     => 'f-minor',
                 ],
@@ -48,7 +48,7 @@ trait PostFormTrait
         $fieldset = [];
         if ($this->c->user->isGuest) {
             $fieldset['username'] = [
-                'dl'        => 't1',
+                'dl'        => 'w1',
                 'type'      => 'text',
                 'maxlength' => 25,
                 'title'     => \ForkBB\__('Username'),
@@ -58,7 +58,7 @@ trait PostFormTrait
                 'autofocus' => $autofocus,
             ];
             $fieldset['email'] = [
-                'dl'        => 't2',
+                'dl'        => 'w2',
                 'type'      => 'text',
                 'maxlength' => 80,
                 'title'     => \ForkBB\__('Email'),
@@ -98,7 +98,7 @@ trait PostFormTrait
             'fields' => $fieldset,
         ];
         $autofocus = null;
-        
+
         $fieldset = [];
         if ($this->c->user->isAdmin || $this->c->user->isModerator($model)) {
             if ($editSubject) {

+ 94 - 31
app/Models/Pages/Search.php

@@ -4,12 +4,43 @@ namespace ForkBB\Models\Pages;
 
 use ForkBB\Models\Page;
 use ForkBB\Core\Validator;
+use ForkBB\Models\Forum\Model as Forum;
 use InvalidArgumentException;
 
 class Search extends Page
 {
     use CrumbTrait;
 
+    /**
+     * Составление списка категорий/разделов для выбора
+     */
+    protected function calcList()
+    {
+        $cid     = null;
+        $options = [];
+        $idxs    = [];
+        $root = $this->c->forums->get(0);
+        if ($root instanceof Forum) {
+            foreach ($this->c->forums->depthList($root, -1) as $f) {
+                if ($cid !== $f->cat_id) {
+                    $cid       = $f->cat_id;
+                    $options[] = [\ForkBB\__('Category prefix') . $f->cat_name];
+                }
+
+                $indent = str_repeat(\ForkBB\__('Forum indent'), $f->depth);
+
+                if ($f->redirect_url) {
+                    $options[] = [$f->id, $indent . \ForkBB\__('Forum prefix') . $f->forum_name, true];
+                } else {
+                    $options[] = [$f->id, $indent . \ForkBB\__('Forum prefix') . $f->forum_name];
+                    $idxs[]    = $f->id;
+                }
+            }
+        }
+        $this->listOfIndexes  = $idxs;
+        $this->listForOptions = $options;
+    }
+
     /**
      * Поиск
      *
@@ -21,6 +52,7 @@ class Search extends Page
     public function view(array $args, $method)
     {
         $this->c->Lang->load('search');
+        $this->calcList();
 
         $v = null;
         if ('POST' === $method) {
@@ -51,7 +83,8 @@ class Search extends Page
             if (isset($args['advanced'])) {
                 $v->addRules([
                     'author'   => 'string:trim|max:25',
-                    'forums'   => 'integer',
+                    'forums'   => 'array',
+                    'forums.*' => 'integer|in:' . implode(',', $this->listOfIndexes),
                     'serch_in' => 'required|integer|in:0,1,2',
                     'sort_by'  => 'required|integer|in:0,1,2,3',
                     'sort_dir' => 'required|integer|in:0,1',
@@ -60,15 +93,33 @@ class Search extends Page
             }
 
             if ($v->validation($_POST)) {
-                $this->c->search->execute([
+                $forums = $v->forums;
+
+                if (empty($forums) && ! $this->c->user->isAdmin) {
+                    $forums = $this->listOfIndexes;
+                }
+
+                $options = [
                     'keywords' => $v->keywords,
                     'author'   => (string) $v->author,
-                    'forums'   => $v->forums,
+                    '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); //?????
+                }
+
+                if (empty($result)) {
+                    $this->fIswev = ['i', \ForkBB\__('No hits')];
+                }
             }
 
             $this->fIswev = $v->getErrors();
@@ -98,7 +149,7 @@ class Search extends Page
                         'html'      => true,
                     ],
                     'keywords' => [
-                        'dl'        => 't2',
+                        'dl'        => 'w2',
                         'type'      => 'text',
                         'maxlength' => 100,
                         'title'     => \ForkBB\__('Keyword search'),
@@ -107,7 +158,7 @@ class Search extends Page
                         'autofocus' => true,
                     ],
                     'author' => [
-                        'dl'        => 't1',
+                        'dl'        => 'w1',
                         'type'      => 'text',
                         'maxlength' => 25,
                         'title'     => \ForkBB\__('Author search'),
@@ -123,28 +174,31 @@ class Search extends Page
                 'legend' => \ForkBB\__('Search in legend'),
                 'fields' => [
                     'forums' => [
+                        'dl'      => 'w3',
                         'type'    => 'multiselect',
-                        'options' => [],
+                        'options' => $this->listForOptions,
                         'value'   => $v ? $v->forums : null,
                         'title'   => \ForkBB\__('Forum search'),
+                        'size'    => min(count($this->listForOptions), 10),
                     ],
                     'serch_in' => [
-                        'type'   => 'select',
+                        'dl'      => 'w3',
+                        'type'    => 'select',
                         'options' => [
                             0 => \ForkBB\__('Message and subject'),
                             1 => \ForkBB\__('Message only'),
                             2 => \ForkBB\__('Topic only'),
                         ],
-                        'value'  => $v ? $v->serch_in : 0,
-                        'title'  => \ForkBB\__('Search in'),
+                        'value'   => $v ? $v->serch_in : 0,
+                        'title'   => \ForkBB\__('Search in'),
                     ],
                     [
-                        'type'  => 'info',
-                        'value' => \ForkBB\__('Search in info'),
+                        'type'    => 'info',
+                        'value'   => \ForkBB\__('Search in info'),
                     ],
                     [
-                        'type'  => 'info',
-                        'value' => \ForkBB\__('Search multiple forums info'),
+                        'type'    => 'info',
+                        'value'   => \ForkBB\__('Search multiple forums info'),
                     ],
 
                 ],
@@ -153,37 +207,40 @@ class Search extends Page
                 'legend' => \ForkBB\__('Search results legend'),
                 'fields' => [
                     'sort_by' => [
-                        'type'   => 'select',
+                        'dl'      => 'w4',
+                        'type'    => 'select',
                         'options' => [
                             0 => \ForkBB\__('Sort by post time'),
                             1 => \ForkBB\__('Sort by author'),
                             2 => \ForkBB\__('Sort by subject'),
                             3 => \ForkBB\__('Sort by forum'),
                         ],
-                        'value'  => $v ? $v->sort_by : 0,
-                        'title'  => \ForkBB\__('Sort by'),
+                        'value'   => $v ? $v->sort_by : 0,
+                        'title'   => \ForkBB\__('Sort by'),
                     ],
                     'sort_dir' => [
-                        'type'   => 'radio',
-                        'values' => [
+                        'dl'      => 'w4',
+                        'type'    => 'radio',
+                        'values'  => [
                             0 => \ForkBB\__('Descending'),
                             1 => \ForkBB\__('Ascending'),
                         ],
-                        'value'  => $v ? $v->sort_dir : 0,
-                        'title'  => \ForkBB\__('Sort order'),
+                        'value'   => $v ? $v->sort_dir : 0,
+                        'title'   => \ForkBB\__('Sort order'),
                     ],
                     'show_as' => [
-                        'type'   => 'radio',
-                        'values' => [
+                        'dl'      => 'w4',
+                        'type'    => 'radio',
+                        'values'  => [
                             0 => \ForkBB\__('Show as posts'),
                             1 => \ForkBB\__('Show as topics'),
                         ],
-                        'value'  => $v ? $v->show_as : 0,
-                        'title'  => \ForkBB\__('Show as'),
+                        'value'   => $v ? $v->show_as : 0,
+                        'title'   => \ForkBB\__('Show as'),
                     ],
                     [
-                        'type'  => 'info',
-                        'value' => \ForkBB\__('Search results info'),
+                        'type'    => 'info',
+                        'value'   => \ForkBB\__('Search results info'),
                     ],
                 ],
 
@@ -229,10 +286,16 @@ class Search extends Page
      */
     public function vCheckQuery(Validator $v, $query)
     {
-        $search = $this->c->search;
+        $user = $this->c->user;
+
+        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 (! $search->prepare($query)) {
-            $v->addError(\ForkBB\__($search->queryError, $search->queryText));
+            if (! $search->prepare($query)) {
+                $v->addError(\ForkBB\__($search->queryError, $search->queryText));
+            }
         }
 
         return $query;
@@ -270,7 +333,7 @@ class Search extends Page
         if (false === $list) {
             return $this->c->Message->message('Bad request');
         } elseif (empty($list)) {
-            $this->a['fIswev']['i'][] = \ForkBB\__('No hits');
+            $this->fIswev = ['i', \ForkBB\__('No hits')];
             return $this->view(['advanced' => 'advanced'], 'GET');
         }
 

+ 121 - 57
app/Models/Search/Execute.php

@@ -11,11 +11,12 @@ use RuntimeException;
 
 class Execute extends Method
 {
-    protected $selectForIndex;
-    protected $selectForPosts;
+    protected $queryIdx;
+    protected $queryCJK;
     protected $sortType;
     protected $words;
-    protected $stmt;
+    protected $stmtIdx;
+    protected $stmtCJK;
 
     /**
      * @param array $options
@@ -33,9 +34,12 @@ class Execute extends Method
 echo '<pre>';
 var_dump($this->model->queryText);
 
-        $this->words = [];
-        $this->stmt  = null;
-        $vars        = $this->buildSelect($options);
+        $this->words   = [];
+        $this->stmtIdx = null;
+        $this->stmtCJK = null;
+        $vars          = $this->buildSelect($options);
+
+var_dump($this->queryIdx, $this->queryCJK);
 
         $ids = $this->exec($this->model->queryWords, $vars);
 
@@ -47,7 +51,8 @@ var_dump($this->model->queryText);
 
 var_dump($ids);
 echo '</pre>';
-        exit();
+
+        return $ids;
     }
 
     /**
@@ -55,14 +60,14 @@ echo '</pre>';
      *
      * @param array $words
      * @param array $vars
-     * @param array $ids
      *
      * @return array
      */
-    protected function exec(array $words, array $vars, array $ids = [])
+    protected function exec(array $words, array $vars)
     {
         $type  = 'AND';
         $count = 0;
+        $ids   = [];
 
         foreach ($words as $word) {
 
@@ -80,12 +85,12 @@ var_dump($word);
             }
 
             if (is_array($word) && (! isset($word['type']) || 'CJK' !== $word['type'])) {
-                $ids = $this->exec($word, $vars, $ids);
+                $ids = $this->exec($word, $vars);
             } else {
                 $CJK = false;
                 if (isset($word['type']) && 'CJK' === $word['type']) {
                     $CJK  = true;
-                    $word = $word['word']; //???? добавить *
+                    $word = '*' . trim($word['word'], '*') . '*';
                 }
 
                 $word = str_replace(['*', '?'], ['%', '_'], $word);
@@ -95,13 +100,23 @@ var_dump($word);
                 } else {
                     $vars[':word'] = $word;
 
-                    if (null === $this->stmt) {
-                        $this->stmt = $this->c->DB->prepare($this->selectForIndex, $vars);
-                        $this->stmt->execute();
+                    if ($CJK) {
+                        if (null === $this->stmtCJK) {
+                            $this->stmtCJK = $this->c->DB->prepare($this->queryCJK, $vars);
+                            $this->stmtCJK->execute();
+                        } else {
+                            $this->stmtCJK->execute($vars);
+                        }
+                        $this->words[$word] = $list = $this->stmtCJK->fetchAll(PDO::FETCH_KEY_PAIR);
                     } else {
-                        $this->stmt->execute($vars);
+                        if (null === $this->stmtIdx) {
+                            $this->stmtIdx = $this->c->DB->prepare($this->queryIdx, $vars);
+                            $this->stmtIdx->execute();
+                        } else {
+                            $this->stmtIdx->execute($vars);
+                        }
+                        $this->words[$word] = $list = $this->stmtIdx->fetchAll(PDO::FETCH_KEY_PAIR);
                     }
-                    $this->words[$word] = $list = $this->stmt->fetchAll(PDO::FETCH_KEY_PAIR);
                 }
 
 var_dump($list);
@@ -137,85 +152,134 @@ var_dump($list);
         # ["sort_dir"]=> string(4) "desc"
         # ["show_as"] => string(5) "posts"
         $vars  = [];
-        $where = [];
-        $joinT = false;
-        $joinP = false;
+        $whereIdx = [];
+        $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'];
+        }
+
+        //???? нужен индекс по авторам сообщений/тем
+        //???? что делать с подчеркиванием в именах?
+        if ('' != $options['author']) {
+            $joinPIdx               = true;
+            $vars[':author']        = str_replace(['*', '?'], ['%', '_'], $options['author']);
+            $whereIdx[]             = 'p.poster LIKE ?s:author';
+        }
 
         switch ($options['serch_in']) {
             case 'posts':
-                $where[]            = 'm.subject_match=0';
+                $whereIdx[]         = 'm.subject_match=0';
+                $whereCJK[]         = 'p.message LIKE ?s:word';
+                $useP               = true;
+                if (isset($vars[':author'])) {
+                    $whereCJK[]     = 'p.poster LIKE ?s:author';
+                }
                 break;
             case 'topics':
-                $where[]            = 'm.subject_match=1';
+                $whereIdx[]         = 'm.subject_match=1';
+                $whereCJK[]         = 't.subject LIKE ?s:word';
+                $useT               = true;
+                if (isset($vars[':author'])) {
+                    $whereCJK[]     = 't.poster LIKE ?s:author';
+                }
                 // при поиске в заголовках результат только в виде списка тем
                 $options['show_as'] = 'topics';
                 break;
-        }
-
-        if (! empty($options['forums'])) {
-            $joinT                  = true;
-            $where[]                = 't.forum_id IN (?ai:forums)';
-            $vars[':forums']        = (array) $options['forums'];
+            default:
+                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))';
+                } else {
+                    $whereCJK[]     = '(p.message LIKE ?s:word OR t.subject LIKE ?s:word)';
+                }
+                $useP               = true;
+                $useT               = true;
+                break;
         }
 
         if ('topics' === $options['show_as']) {
             $showTopics             = true;
-            $joinP                  = true;
-            $selectF                = 'p.topic_id';
+            $joinPIdx               = true;
+            $selectFIdx             = 'p.topic_id';
+            $selectFCJK             = 't.id';
+            $useT                   = true;
         } else {
             $showTopics             = false;
-            $selectF                = 'm.post_id';
-        }
-
-        //???? нужен индекс по авторам сообщений
-        //???? что делать с подчеркиванием в именах?
-        if ('' != $options['author']) {
-            $joinP                  = true;
-            $vars[':author']        = str_replace(['*', '?'], ['%', '_'], $options['author']);
-            $where[]                = 'p.poster LIKE ?s:author';
+            $selectFIdx             = 'm.post_id';
+            $selectFCJK             = 'p.id';
+            $useP                   = true;
         }
 
         switch ($options['sort_by']) {
             case 'author':
                 if ($showTopics) {
-                    $sortBy         = 't.poster';
-                    $joinT          = true;
+                    $sortIdx        = 't.poster';
+                    $sortCJK        = 't.poster';
+                    $joinTIdx       = true;
+                    $useT           = true;
                 } else {
-                    $sortBy         = 'p.poster';
-                    $joinP          = true;
+                    $sortIdx        = 'p.poster';
+                    $sortCJK        = 'p.poster';
+                    $joinPIdx       = true;
+                    $useP           = true;
                 }
                 $this->sortType     = SORT_STRING;
                 break;
             case 'subject':
-                $sortBy             = 't.subject';
-                $joinT              = true;
+                $sortIdx            = 't.subject';
+                $sortCJK            = 't.subject';
+                $joinTIdx           = true;
+                $useT               = true;
                 $this->sortType     = SORT_STRING;
                 break;
             case 'forum':
-                $sortBy             = 't.forum_id';
-                $joinT              = true;
+                $sortIdx            = 't.forum_id';
+                $sortCJK            = 't.forum_id';
+                $joinTIdx           = true;
+                $useT               = true;
                 $this->sortType     = SORT_NUMERIC;
                 break;
             default:
                 if ($showTopics) {
-                    $sortBy         = 't.last_post';
-                    $joinT          = true;
+                    $sortIdx        = 't.last_post';
+                    $sortCJK        = 't.last_post';
+                    $joinTIdx       = true;
+                    $useT           = true;
                 } else {
-                    $sortBy         = 'm.post_id';
+                    $sortIdx        = 'm.post_id';
+                    $sortCJK        = 'p.id';
+                    $useP           = true;
                 }
                 $this->sortType     = SORT_NUMERIC;
                 break;
         }
 
-        $joinP = $joinP || $joinT ? 'INNER JOIN ::posts AS p ON p.id=m.post_id '   : '';
-        $joinT = $joinT           ? 'INNER JOIN ::topics AS t ON t.id=p.topic_id ' : '';
-        $where = empty($where)    ? '' : ' AND ' . implode(' AND ', $where);
+        $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);
 
-        $this->selectForIndex = "SELECT {$selectF}, {$sortBy} FROM ::search_words AS w " .
-                                'INNER JOIN ::search_matches AS m ON m.word_id=w.id ' .
-                                $joinP .
-                                $joinT .
-                                'WHERE w.word LIKE ?s:word' . $where;
+        $this->queryIdx = "SELECT {$selectFIdx}, {$sortIdx} FROM ::search_words AS w " .
+                          'INNER JOIN ::search_matches AS m ON m.word_id=w.id ' .
+                          $joinPIdx .
+                          $joinTIdx .
+                          'WHERE w.word LIKE ?s:word' . $whereIdx;
+
+        if ($useP) {
+            $this->queryCJK = "SELECT {$selectFCJK}, {$sortCJK} FROM ::posts AS p " .
+                              ($useT ? 'INNER JOIN ::topics AS t ON t.id=p.topic_id ' : '') .
+                              'WHERE ' . implode(' AND ', $whereCJK);
+        } else {
+            $this->queryCJK = "SELECT {$selectFCJK}, {$sortCJK} FROM ::topics AS t " .
+                              'WHERE ' . implode(' AND ', $whereCJK);
+        }
 
         return $vars;
     }

+ 2 - 2
app/Models/User/Current.php

@@ -74,7 +74,7 @@ class Current extends Action
      * @param int $id
      *
      * @throws RuntimeException
-     * 
+     *
      * @return User;
      */
     protected function load($id)
@@ -109,7 +109,7 @@ class Current extends Action
 
     /**
      * Возврат юзер агента браузера пользователя
-     * 
+     *
      * @return string
      */
     protected function getUserAgent()

+ 9 - 0
app/lang/English/search.po

@@ -188,3 +188,12 @@ msgstr "<a href=\"%s\">Advanced search</a>"
 
 msgid "<a href=\"%s\">Simple search</a>"
 msgstr "<a href=\"%s\">Simple search</a>"
+
+msgid "Category prefix"
+msgstr ""
+
+msgid "Forum prefix"
+msgstr ""
+
+msgid "Forum indent"
+msgstr "◦ ◦ "

+ 9 - 0
app/lang/Russian/search.po

@@ -188,3 +188,12 @@ msgstr "<a href=\"%s\">Расширенный поиск</a>"
 
 msgid "<a href=\"%s\">Simple search</a>"
 msgstr "<a href=\"%s\">Простой поиск</a>"
+
+msgid "Category prefix"
+msgstr ""
+
+msgid "Forum prefix"
+msgstr ""
+
+msgid "Forum indent"
+msgstr "◦ ◦ "

+ 33 - 1
app/templates/layouts/form.tpl

@@ -34,13 +34,45 @@
           @endif
         @elseif ('select' === $cur['type'])
                 <select @if (! empty($cur['required'])) required @endif @if (! empty($cur['disabled'])) disabled @endif @if (isset($cur['autofocus'])) autofocus @endif class="f-ctrl" id="id-{{ $key }}" name="{{ $key }}">
+          @if (null === ($count = null) && is_array(reset($cur['options'])) && 1 === count(reset($cur['options'])) && $count = 0) @endif
           @foreach ($cur['options'] as $v => $option)
             @if (is_array($option))
+              @if (null !== $count && 1 === count($option))
+                @if (++$count > 1)
+                </optgroup>
+                @endif
+                <optgroup label="{{ $option[0] }}">
+              @else
                   <option value="{{ $option[0] }}" @if ($option[0] == $cur['value']) selected @endif @if (isset($option[2])) disabled @endif>{{ $option[1] }}</option>
+              @endif
             @else
                   <option value="{{ $v }}" @if ($v == $cur['value']) selected @endif>{{ $option }}</option>
             @endif
           @endforeach
+          @if (null !== $count)
+                </optgroup>
+          @endif
+                </select>
+        @elseif ('multiselect' === $cur['type'])
+                <select @if (! empty($cur['required'])) required @endif @if (! empty($cur['disabled'])) disabled @endif @if (isset($cur['autofocus'])) autofocus @endif @if (! empty($cur['size'])) size="{{ $cur['size'] }}" @endif multiple class="f-ctrl" id="id-{{ $key }}" name="{{ $key }}[]">
+          @if (null === ($count = null) && is_array(reset($cur['options'])) && 1 === count(reset($cur['options'])) && $count = 0) @endif
+          @foreach ($cur['options'] as $v => $option)
+            @if (is_array($option))
+              @if (null !== $count && 1 === count($option))
+                @if (++$count > 1)
+                </optgroup>
+                @endif
+                <optgroup label="{{ $option[0] }}">
+              @else
+                  <option value="{{ $option[0] }}" @if ((is_array($cur['value']) && in_array($option[0], $cur['value'])) || $option[0] == $cur['value']) selected @endif @if (isset($option[2])) disabled @endif>{{ $option[1] }}</option>
+              @endif
+            @else
+                  <option value="{{ $v }}" @if ((is_array($cur['value']) && in_array($v, $cur['value'])) || $v == $cur['value']) selected @endif>{{ $option }}</option>
+            @endif
+          @endforeach
+          @if (null !== $count)
+                </optgroup>
+          @endif
                 </select>
         @elseif ('number' === $cur['type'])
                 <input @if (! empty($cur['required'])) required @endif @if (! empty($cur['disabled'])) disabled @endif @if (isset($cur['autofocus'])) autofocus @endif class="f-ctrl" id="id-{{ $key }}" name="{{ $key }}" type="number" min="{{ $cur['min'] }}" max="{{ $cur['max'] }}" @if (isset($cur['value'])) value="{{ $cur['value'] }}" @endif>
@@ -69,7 +101,7 @@
 @foreach ($form['btns'] as $key => $cur)
   @if ('submit' === $cur['type'])
             <input class="f-btn @if(isset($cur['class'])) {{ $cur['class'] }} @endif" type="{{ $cur['type'] }}" name="{{ $key }}" value="{{ $cur['value'] }}" @if (isset($cur['accesskey'])) accesskey="{{ $cur['accesskey'] }}" @endif>
-  @elseif ('btn'=== $cur['type'])          
+  @elseif ('btn'=== $cur['type'])
             <a class="f-btn @if(isset($cur['class'])) {{ $cur['class'] }} @endif" data-name="{{ $key }}" href="{!! $cur['link'] !!}" @if (isset($cur['accesskey'])) accesskey="{{ $cur['accesskey'] }}" @endif>{{ $cur['value'] }}</a>
   @endif
 @endforeach

+ 34 - 6
public/style/ForkBB/style.css

@@ -479,6 +479,7 @@ select {
 .f-fdiv .f-child6 {
   display: block;
   width: 100%;
+  clear: both;
 }
 
 .f-fdiv .f-child1 {
@@ -1042,6 +1043,12 @@ select {
   border-bottom: 0.0625rem solid #AA7939;
 }
 
+/*
+.f-ftlist .f-row:hover {
+  background-color: #F8F4E3;
+}
+*/
+
 .f-ftlist .f-thead {
   border-bottom: 0.125rem solid #AA7939;
 }
@@ -1574,20 +1581,20 @@ li + li .f-btn {
 }
 
 @media screen and (min-width: 50rem) {
-  .f-fdiv .f-field-t1 {
+  .f-fdiv .f-field-w1 {
     width: 30%;
     float: left;
     padding-top: 0.625rem;
   }
 
-  .f-fdiv .f-field-t2 {
+  .f-fdiv .f-field-w2 {
     width: 70%;
     float: left;
     padding-top: 0.625rem;
   }
 
-  .f-field-t1 + .f-field-t2,
-  .f-field-t2 + .f-field-t1 {
+  .f-field-w1 + .f-field-w2,
+  .f-field-w2 + .f-field-w1 {
     padding-left: 0.625rem;
   }
 }
@@ -1663,6 +1670,10 @@ li + li .f-btn {
   padding-left: 2.5rem;
 }
 
+.f-field-depth5 > dd {
+  padding-left: 3.125rem;
+}
+
 .f-editcategories-form .f-field-inline:nth-child(-n+3) {
   padding-top: 0.625rem;
 }
@@ -1793,8 +1804,25 @@ li + li .f-btn {
 }
 
 @media screen and (min-width: 50rem) {
-  .f-search-form .f-fdiv .f-field-t1,
-  .f-search-form .f-fdiv .f-field-t2 {
+  .f-search-form .f-fdiv .f-field-w1,
+  .f-search-form .f-fdiv .f-field-w2 {
     padding-top: 0;
   }
+
+  .f-search-form .f-fdiv .f-field-w3 {
+    width: 50%;
+    float: left;
+/*    padding-top: 0; */
+  }
+
+  .f-search-form .f-fdiv .f-field-w4 {
+    width: 33.33%;
+    float: left;
+/*    padding-top: 0; */
+  }
+
+  .f-field-w3 + .f-field-w3,
+  .f-field-w4 + .f-field-w4 {
+    padding-left: 0.625rem;
+  }
 }