Przeglądaj źródła

Add followed forums for "Latest active topics" and "Unanswered topics"

Visman 2 lat temu
rodzic
commit
dcde27781d

+ 25 - 5
app/Controllers/Routing.php

@@ -82,7 +82,6 @@ class Routing
                 'Auth:logout',
                 'Logout'
             );
-
             // обработка "кривых" перенаправлений с логина и регистрации
             $r->add(
                 $r::GET,
@@ -96,6 +95,7 @@ class Routing
                 'Redirect:toIndex'
             );
         }
+
         // OAuth
         if (
             $user->isAdmin
@@ -108,6 +108,7 @@ class Routing
                 'RegLogCallback'
             );
         }
+
         if (1 === $config->b_oauth_allow) {
             $r->add(
                 $r::PST,
@@ -116,6 +117,7 @@ class Routing
                 'RegLogRedirect'
             );
         }
+
         // просмотр разрешен
         if (1 === $user->g_read_board) {
             // главная
@@ -135,6 +137,7 @@ class Routing
                 '/index.html',
                 'Redirect:toIndex'
             );
+
             // правила
             if (
                 1 === $config->b_rules
@@ -150,6 +153,7 @@ class Routing
                     'Rules'
                 );
             }
+
             // поиск
             if (1 === $user->g_search) {
                 $r->add(
@@ -163,7 +167,6 @@ class Routing
                     '/search',
                     'Search:view'
                 );
-
                 $r->add(
                     $r::GET,
                     '/search/advanced[/{keywords}/{author}/{forums}/{serch_in:\d}/{sort_by:\d}/{sort_dir:\d}/{show_as:\d}[/{page|i:[1-9]\d*}]]',
@@ -175,7 +178,6 @@ class Routing
                     '/search/advanced',
                     'Search:viewAdvanced'
                 );
-
                 $r->add(
                     $r::GET,
                     '/search[/user/{uid|i:[1-9]\d*}]/{action:(?!search)[a-z_]+}[/in_forum/{forum|i:[1-9]\d*}][/{page|i:[1-9]\d*}]',
@@ -189,6 +191,7 @@ class Routing
                     'OpenSearch'
                 );
             }
+
             // юзеры
             if ($userRules->viewUsers) {
                 // список пользователей
@@ -276,8 +279,16 @@ class Routing
                     'EditUserPass'
                 );
             }
-            // удаление своего профиля
+
             if (! $user->isGuest) {
+                // настройка поиска
+                $r->add(
+                    $r::DUO,
+                    '/user/{id|i:' . $user->id . '}/edit/config/search',
+                    'ProfileSearch:config',
+                    'EditUserSearch'
+                );
+                // удаление своего профиля
                 $r->add(
                     $r::DUO,
                     '/user/{id|i:' . $user->id . '}/delete/profile',
@@ -285,6 +296,7 @@ class Routing
                     'DeleteUserProfile'
                 );
             }
+
             // управление аккаунтами OAuth
             if (
                 ! $user->isGuest
@@ -303,6 +315,7 @@ class Routing
                     'EditUserOAuthAction'
                 );
             }
+
             // смена своего email
             if (! $user->isGuest) {
                 $r->add(
@@ -398,6 +411,7 @@ class Routing
                 'Delete:delete',
                 'DeletePost'
             );
+
             // сигналы (репорты)
             if (
                 ! $user->isAdmin
@@ -410,6 +424,7 @@ class Routing
                     'ReportPost'
                 );
             }
+
             // отправка email
             if (
                 ! $user->isGuest
@@ -422,6 +437,7 @@ class Routing
                     'SendEmail'
                 );
             }
+
             // feed
             $r->add(
                 $r::GET,
@@ -429,6 +445,7 @@ class Routing
                 'Feed:view',
                 'Feed'
             );
+
             // подписки
             if (
                 ! $user->isGuest
@@ -447,6 +464,7 @@ class Routing
                     'TopicSubscription'
                 );
             }
+
             // личные сообщения
             if ($user->usePM) {
                 $r->add(
@@ -463,6 +481,7 @@ class Routing
                 );
             }
         }
+
         // опросы
         if ($userRules->usePoll) {
             $r->add(
@@ -472,6 +491,7 @@ class Routing
                 'Poll'
             );
         }
+
         // админ и модератор
         if ($user->isAdmMod) {
             $r->add(
@@ -520,7 +540,6 @@ class Routing
                 'AdminUsersAction:view',
                 'AdminUsersAction'
             );
-
             $r->add(
                 $r::GET,
                 '/admin/users/promote/{uid|i:[1-9]\d*}/{pid|i:[1-9]\d*}/{token}',
@@ -603,6 +622,7 @@ class Routing
             );
 
         }
+
         // только админ
         if ($user->isAdmin) {
             $r->add(

+ 5 - 3
app/Models/Page.php

@@ -561,7 +561,7 @@ abstract class Page extends Model
                     }
 
                     if ($crumb->linkCrumbExt) {
-                        $ext = [$crumb->linkCrumbExt, '#'];
+                        $ext = [$crumb->linkCrumbExt, $crumb->textCrumbExt ?? '#'];
                     } else {
                         $ext = null;
                     }
@@ -573,17 +573,19 @@ abstract class Page extends Model
                 );
             // ссылка (передана массивом)
             } elseif (\is_array($crumb)) {
-                $result[]     = [$crumb[0], $crumb[1], $active, $ext];
+                $result[]     = [$crumb[0], $crumb[1], $active, $crumb[2] ?? $ext];
                 $this->titles = $crumb[1];
+                $ext          = null;
             // строка
             } else {
                 $result[]     = [null, (string) $crumb, $active, $ext];
                 $this->titles = (string) $crumb;
+                $ext          = null;
             }
 
             $active = null;
-            $ext    = null;
         }
+
         // главная страница
         $result[] = [$this->c->Router->link('Index'), 'Index', $active, $ext];
 

+ 1 - 0
app/Models/Pages/Admin/Install.php

@@ -1186,6 +1186,7 @@ class Install extends Admin
                 'ip_check_type'    => ['TINYINT UNSIGNED', false, 0],
                 'login_ip_cache'   => ['VARCHAR(255)', false, ''],
                 'u_up_size_mb'     => ['INT(10) UNSIGNED', false, 0],
+                'unfollowed_f'     => ['VARCHAR(255)', false, ''],
             ],
             'PRIMARY KEY' => ['id'],
             'UNIQUE KEYS' => [

+ 21 - 1
app/Models/Pages/Admin/Update.php

@@ -25,7 +25,7 @@ class Update extends Admin
 {
     const PHP_MIN                    = '8.0.0';
     const REV_MIN_FOR_UPDATE         = 53;
-    const LATEST_REV_WITH_DB_CHANGES = 63;
+    const LATEST_REV_WITH_DB_CHANGES = 67;
     const LOCK_NAME                  = 'lock_update';
     const LOCK_TTL                   = 1800;
     const CONFIG_FILE                = 'main.php';
@@ -851,4 +851,24 @@ class Update extends Admin
 
         return null;
     }
+
+    /**
+     * rev.66 to rev.67
+     */
+    protected function stageNumber66(array $args): ?int
+    {
+        $coreConfig = new CoreConfig($this->configFile);
+
+        $coreConfig->add(
+            'multiple=>ProfileSearch',
+            '\\ForkBB\\Models\\Pages\\Profile\\Search::class',
+            'ProfileDelete'
+        );
+
+        $coreConfig->save();
+
+        $this->c->DB->addField('::users', 'unfollowed_f', 'VARCHAR(255)', false, '');
+
+        return null;
+    }
 }

+ 16 - 1
app/Models/Pages/Profile/Config.php

@@ -321,7 +321,22 @@ class Config extends Profile
                     ],
                 ],
             ];
-            }
+        }
+
+        if ($this->rules->configureSearch) {
+            $form['sets']['search'] = [
+                'legend' => 'Search options',
+                'class'  => ['data-edit'],
+                'fields' => [
+                    'search_config' => [
+                        'type'  => 'link',
+                        'value' => __('Set up search'),
+                        'title' => __('Set up search'),
+                        'href'  => $this->c->Router->link('EditUserSearch', $args),
+                    ],
+                ],
+            ];
+        }
 
         return $form;
     }

+ 201 - 0
app/Models/Pages/Profile/Search.php

@@ -0,0 +1,201 @@
+<?php
+/**
+ * This file is part of the ForkBB <https://github.com/forkbb>.
+ *
+ * @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
+ * @license   The MIT License (MIT)
+ */
+
+declare(strict_types=1);
+
+namespace ForkBB\Models\Pages\Profile;
+
+use ForkBB\Models\Page;
+use ForkBB\Models\Pages\Profile;
+use ForkBB\Models\Forum\Forum;
+use function \ForkBB\__;
+
+class Search extends Profile
+{
+    /**
+     * Подготавливает данные для шаблона конфигурации поиска
+     */
+    public function config(array $args, string $method): Page
+    {
+        if (
+            false === $this->initProfile($args['id'])
+            || ! $this->rules->configureSearch
+        ) {
+            return $this->c->Message->message('Bad request');
+        }
+
+        $this->c->Lang->load('validator');
+
+        if ($this->rules->my) {
+            $this->forumManager = $this->c->forums;
+        } else {
+            $this->forumManager = $this->c->ForumManager->init($this->c->groups->get($this->curUser->group_id));
+        }
+
+        if ('POST' === $method) {
+            $v = $this->c->Validator->reset()
+                ->addValidators([
+                ])->addRules([
+                    'token'    => 'token:EditUserSearch',
+                    'follow.*' => 'integer|in:' . \implode(',', \array_keys($this->curForums)),
+                    'save'     => 'required|string',
+                ])->addAliases([
+                ])->addArguments([
+                    'token'    => $args,
+                ])->addMessages([
+                ]);
+
+            if ($v->validation($_POST)) {
+                if (! empty($v->follow)) {
+                    $unfollow = \array_diff(\array_keys($this->curForums), $v->follow);
+
+                    \sort($unfollow, \SORT_NUMERIC);
+
+                    $unfollow = \implode(',', $unfollow);
+
+                    while (
+                        \strlen($unfollow) > 255
+                        && false !== ($pos = \strrpos($unfollow, ','))
+                    ) {
+                        $unfollow = \substr($unfollow, 0, $pos);
+                    }
+
+                    $this->curUser->unfollowed_f = $unfollow;
+
+                    $this->c->users->update($this->curUser);
+                }
+
+                return $this->c->Redirect->page('EditUserSearch', $args)->message('Update search config redirect', FORK_MESS_SUCC);
+            }
+
+            $this->fIswev = $v->getErrors();
+        }
+
+        $this->crumbs          = $this->crumbs(
+            [
+                $this->c->Router->link('EditUserSearch', $args),
+                'Search config',
+            ],
+            [
+                $this->c->Router->link('EditUserBoardConfig', $args),
+                'Board configuration',
+            ]
+        );
+        $this->form            = $this->form($args);
+        $this->actionBtns      = $this->btns('config');
+        $this->profileIdSuffix = '-search';
+
+        return $this;
+    }
+
+    /**
+     * Возвращает список доступных разделов для пользователя текущего профиля
+     */
+    protected function getcurForums(): array
+    {
+        $root = $this->forumManager->get(0);
+
+        return $root instanceof Forum ? $root->descendants : [];
+    }
+
+    /**
+     * Возвращает id неотслеживаемых форумов в виде массива
+     */
+    protected function getcurUnfollowed(): array
+    {
+        $raw = $this->curUser->unfollowed_f;
+
+        if (empty($raw)) {
+            return [];
+        }
+
+        $result = [];
+
+        foreach (\explode(',', $raw) as $id) {
+            $id = (int) $id;
+
+            if (
+                $id > 0
+                && isset($this->curForums[$id])
+            ) {
+                $result[$id] = $id;
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Создает массив данных для формы
+     */
+    protected function form(array $args): array
+    {
+        $form = [
+            'action' => $this->c->Router->link('EditUserSearch', $args),
+            'hidden' => [
+                'token' => $this->c->Csrf->create('EditUserSearch', $args),
+            ],
+            'sets'   => [],
+            'btns'   => [
+                'save' => [
+                    'type'  => 'submit',
+                    'value' => __('Save'),
+                ],
+            ],
+        ];
+
+        $root = $this->forumManager->get(0);
+
+        if ($root instanceof Forum) {
+            $list = $this->forumManager->depthList($root, 0);
+            $cid  = null;
+
+            $form['sets']['start']['inform'] = [
+                [
+                    'message' => ['Select followed forums for %s and %s:', __('Latest active topics'), __('Unanswered topics')],
+                ],
+            ];
+
+            foreach ($list as $forum) {
+                if ($cid !== $forum->cat_id) {
+                    $form['sets']["category{$forum->cat_id}-info"] = [
+                        'inform' => [
+                            [
+                                'message' => $forum->cat_name,
+                            ],
+                        ],
+                    ];
+                    $cid = $forum->cat_id;
+                }
+
+                $fields = [];
+                $fields["name{$forum->id}"] = [
+                    'class'   => ['modforum', 'name', 'depth' . $forum->depth],
+                    'type'    => 'str',
+                    'value'   => $forum->forum_name,
+                    'caption' => 'Forum label',
+                ];
+                $fields["follow[{$forum->id}]"] = [
+                    'class'    => ['modforum', 'moderator'],
+                    'type'     => 'checkbox',
+                    'value'    => $forum->id,
+                    'checked'  => ! isset($this->curUnfollowed[$forum->id]),
+                    'disabled' => '' != $this->curForums[$forum->id]->redirect_url,
+                    'caption'  => 'Follow label',
+                ];
+                $form['sets']["forum{$forum->id}"] = [
+                    'class'  => ['modforum'],
+                    'legend' => $forum->cat_name . ' / ' . $forum->forum_name,
+                    'fields' => $fields,
+                ];
+            }
+        }
+
+        return $form;
+    }
+}

+ 26 - 2
app/Models/Pages/Search.php

@@ -428,12 +428,13 @@ class Search extends Page
         $asTopicsList = true;
         $list         = false;
         $uid          = $args['uid'] ?? null;
-        $subIndex = [
+        $subIndex     = [
             'topics_with_your_posts' => 'with-your-posts',
             'latest_active_topics'   => 'latest',
             'unanswered_topics'      => 'unanswered',
             'new'                    => 'new',
         ];
+        $extLink      = true;
 
         switch ($action) {
             case 'search':
@@ -459,6 +460,8 @@ class Search extends Page
                 if ($this->user->isGuest) {
                     break;
                 }
+
+                $extLink = false;
             case 'latest_active_topics':
             case 'unanswered_topics':
                 if (isset($uid)) {
@@ -470,6 +473,19 @@ class Search extends Page
                 $model->name       = __('Quick search ' . $action);
                 $model->linkMarker = 'SearchAction';
 
+                if (
+                    $extLink
+                    && ! $this->user->isGuest
+                ) {
+                    $model->linkCrumbExt = $this->c->Router->link('EditUserSearch', ['id' => $this->user->id]);
+
+                    if (empty($this->user->unfollowed_f)) {
+                        $model->textCrumbExt = __('Set up');
+                    } else {
+                        $model->textCrumbExt = '-' . (\substr_count($this->user->unfollowed_f, ',') + 1);
+                    }
+                }
+
                 if ($forum->id) {
                     $model->linkArgs = ['action' => $action, 'forum' => $forum->id];
                 } else {
@@ -523,6 +539,7 @@ class Search extends Page
             return $this->c->Message->message('Bad request');
         } elseif (empty($list)) {
             $this->fIswev = [FORK_MESS_INFO, 'No hits'];
+            $this->noHits = true;
 
             return $this->view([], 'GET', true);
         }
@@ -563,7 +580,14 @@ class Search extends Page
      */
     protected function crumbs(mixed ...$crumbs): array
     {
-        $crumbs[] = [$this->c->Router->link('Search'), 'Search'];
+        // перехват пустого результата для 'latest_active_topics' и 'unanswered_topics'
+        if (isset($this->noHits, $this->c->search->linkCrumbExt, $this->c->search->textCrumbExt)) {
+            $ext = [$this->c->search->linkCrumbExt, $this->c->search->textCrumbExt];
+        } else {
+            $ext = null;
+        }
+
+        $crumbs[] = [$this->c->Router->link('Search'), 'Search', $ext];
 
         return parent::crumbs(...$crumbs);
     }

+ 5 - 0
app/Models/Rules/Profile.php

@@ -168,4 +168,9 @@ class Profile extends Rules
     {
         return $this->my && 1 === $this->c->config->b_oauth_allow;
     }
+
+    protected function getconfigureSearch(): bool
+    {
+        return $this->my && 1 === $this->curUser->g_search;
+    }
 }

+ 14 - 0
app/Models/Search/ActionT.php

@@ -17,6 +17,18 @@ use InvalidArgumentException;
 
 class ActionT extends Method
 {
+    /**
+     * Удаляет из списка неотслеживаемые пользователем разделы
+     */
+    protected function unfollow(array $forums): array
+    {
+        if (empty($this->c->user->unfollowed_f)) {
+            return $forums;
+        } else {
+            return \array_diff($forums, \array_map('\\intval', \explode(',', $this->c->user->unfollowed_f)));
+        }
+    }
+
     /**
      * Поисковые действия по темам
      */
@@ -44,6 +56,7 @@ class ActionT extends Method
                     WHERE t.forum_id IN (?ai:forums) AND t.moved_to=0
                     ORDER BY t.last_post DESC
                     LIMIT 1000';
+                $forums = $this->unfollow($forums);
 
                 break;
             case 'unanswered_topics':
@@ -51,6 +64,7 @@ class ActionT extends Method
                     FROM ::topics AS t
                     WHERE t.forum_id IN (?ai:forums) AND t.moved_to=0 AND t.num_replies=0
                     ORDER BY t.last_post DESC';
+                $forums = $this->unfollow($forums);
 
                 break;
             case 'topics_with_your_posts':

+ 1 - 0
app/config/main.dist.php

@@ -381,6 +381,7 @@ return [
         'ProfileMod'         => \ForkBB\Models\Pages\Profile\Mod::class,
         'ProfileOAuth'       => \ForkBB\Models\Pages\Profile\OAuth::class,
         'ProfileDelete'      => \ForkBB\Models\Pages\Profile\Delete::class,
+        'ProfileSearch'      => \ForkBB\Models\Pages\Profile\Search::class,
         'AdminIndex'         => \ForkBB\Models\Pages\Admin\Index::class,
         'AdminStatistics'    => \ForkBB\Models\Pages\Admin\Statistics::class,
         'AdminOptions'       => \ForkBB\Models\Pages\Admin\Options::class,

+ 18 - 0
app/lang/en/profile.po

@@ -356,3 +356,21 @@ msgstr "<b>This is an irreversible operation!</b>"
 
 msgid "Your deleted redirect"
 msgstr "Your profile has been deleted."
+
+msgid "Search config"
+msgstr "Search config"
+
+msgid "Search options"
+msgstr "Search options"
+
+msgid "Set up search"
+msgstr "Set up search"
+
+msgid "Follow label"
+msgstr "Follow"
+
+msgid "Update search config redirect"
+msgstr "Search settings updated."
+
+msgid "Select followed forums for %s and %s:"
+msgstr "Select followed forums for <b><i>%s</i></b> and <b><i>%s</i></b>:"

+ 3 - 0
app/lang/en/search.po

@@ -176,3 +176,6 @@ msgstr "Logical operator at the beginning of the search (sub)query: '%s'"
 
 msgid "Logical operators follow one after another: '%s'"
 msgstr "Logical operators follow one after another: '%s'"
+
+msgid "Set up"
+msgstr "Set up"

+ 18 - 0
app/lang/ru/profile.po

@@ -356,3 +356,21 @@ msgstr "<b>Это необратимая операция!</b>"
 
 msgid "Your deleted redirect"
 msgstr "Ваш профиль удален."
+
+msgid "Search config"
+msgstr "Конфигурация поиска"
+
+msgid "Search options"
+msgstr "Опции поиска"
+
+msgid "Set up search"
+msgstr "Настроить поиск"
+
+msgid "Follow label"
+msgstr "Отслеживать"
+
+msgid "Update search config redirect"
+msgstr "Настройки поиска сохранены."
+
+msgid "Select followed forums for %s and %s:"
+msgstr "Выберите отслеживаемые разделы для <b><i>%s</i></b> и <b><i>%s</i></b>:"

+ 3 - 0
app/lang/ru/search.po

@@ -176,3 +176,6 @@ msgstr "Логический оператор в начале (под)запро
 
 msgid "Logical operators follow one after another: '%s'"
 msgstr "Логические операторы следуют друг за другом: '%s'"
+
+msgid "Set up"
+msgstr "Настр."

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

@@ -2632,6 +2632,10 @@ body,
   width: calc(100% - 3rem);
 }
 
+#fork-profile-search .f-finform:first-child {
+  margin-bottom: 1rem;
+}
+
 /****************************/
 /* Профиль - редактирование */
 /****************************/
@@ -2641,11 +2645,11 @@ body,
   border-bottom: 0.0625rem dotted var(--br-fprimary);
 }
 
-#fork .f-fs-header-edit,
+/*#fork .f-fs-header-edit,
 #fork .f-fs-data-edit {
   padding: 0.3125rem 0;
   margin-bottom: 0.3125rem;
-}
+}*/
 
 #fork .f-fs-header-edit .f-avatar-img {
   max-width: 11.75rem;