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

This commit is contained in:
Visman 2023-08-06 20:49:53 +07:00
parent 7313224e57
commit dcde27781d
15 changed files with 363 additions and 14 deletions

View file

@ -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(

View file

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

View file

@ -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' => [

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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':

View file

@ -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,

View file

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

View file

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

View file

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

View file

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

View file

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