diff --git a/Sources/Actions/Search.php b/Sources/Actions/Search.php index 7d5a9f39f9b..30cf73a6865 100644 --- a/Sources/Actions/Search.php +++ b/Sources/Actions/Search.php @@ -18,7 +18,7 @@ use SMF\ActionInterface; use SMF\ActionRouter; use SMF\ActionTrait; -use SMF\Board; +use SMF\Category; use SMF\Config; use SMF\Db\DatabaseApi as Db; use SMF\ErrorHandler; @@ -70,7 +70,6 @@ public function execute(): void // Don't load this in XML mode. if (!isset($_REQUEST['xml'])) { Theme::loadTemplate('Search'); - Theme::loadTemplate('GenericControls'); Theme::loadJavaScriptFile('suggest.js', ['defer' => false, 'minimize' => true], 'smf_suggest'); } @@ -175,19 +174,51 @@ public function execute(): void } } - // If user selected some particular boards, is this one of them? - if (!empty(Utils::$context['search_params']['brd'])) { - $boards = Utils::$context['search_params']['brd']; - } - // User didn't select any boards, so select all except ignored and recycle boards. - elseif (!empty(Config::$modSettings['recycle_enable']) && !empty(Config::$modSettings['recycle_board'])) { - $boards = array_merge(User::$me->ignoreboards, [(int) Config::$modSettings['recycle_board']]); - } else { - $boards = User::$me->ignoreboards; - } + // Find all the boards this user is allowed to see. + Category::getTree(); + + Utils::$context['num_boards'] = 0; + Utils::$context['boards_check_all'] = true; + Utils::$context['categories'] = []; + + foreach (Category::$loaded as $category) { + // Clone it so that we can edit it without touching the real data. + $cat = clone $category; + + // Remove all redirect boards from the its children. + $cat->children = array_filter( + $cat->children, + fn($board) => empty($board->redirect), + ); + + // Skip empty categories. + if (empty($cat->children)) { + continue; + } - Utils::$context['categories'] = Board::getUserVisibleBoards($boards); + // Add the category to the list. + Utils::$context['categories'][$cat->id] = $cat; + + // Figure out which boards to mark as selected. + foreach ($cat->children as $key => $board) { + Utils::$context['num_boards']++; + + // If user selected some particular boards, is this one of them? + if (!empty(Utils::$context['search_params']['brd'])) { + $board->selected = \in_array($board->id, Utils::$context['search_params']['brd']); + } + // User didn't select any boards, so select all except ignored and recycle boards. + else { + $board->selected = !$board->recycle && !\in_array($board->id, User::$me->ignoreboards); + } + + if (!$board->selected && !$board->recycle) { + Utils::$context['boards_check_all'] = false; + } + } + } + // Searching in a topic? if (!empty($_REQUEST['topic'])) { Utils::$context['search_params']['topic'] = (int) $_REQUEST['topic']; Utils::$context['search_params']['show_complete'] = true; diff --git a/Sources/Board.php b/Sources/Board.php index d20ef689be8..9bece3d2943 100644 --- a/Sources/Board.php +++ b/Sources/Board.php @@ -231,8 +231,23 @@ class Board implements \ArrayAccess, Routable * @var bool * * Whether this board is the recycle bin board. - */ - public bool $recycle = false; + * + * For the sake of compatibility with \ArrayAccess it is possible to write + * to this property, but doing so is pointless because the value will be + * overwritten the next time the property is read. + */ + public bool $recycle { + // @todo Once \ArrayAccess compatibility is no longer required, change this hook to + // `get => !empty(Config::$modSettings['recycle_enable']) && $this->id == (Config::$modSettings['recycle_board'] ?? NAN);` + &get { + $this->recycle = ( + !empty(Config::$modSettings['recycle_enable']) + && $this->id == (Config::$modSettings['recycle_board'] ?? NAN) + ); + + return $this->recycle; + } + } /** * @var bool @@ -326,6 +341,15 @@ class Board implements \ArrayAccess, Routable */ public string $error; + /** + * @var bool + * + * Whether this board is selected in the search form. + * + * Only used during search. + */ + public bool $selected = false; + /************************** * Public static properties **************************/ @@ -1108,59 +1132,6 @@ public static function getMsgMemberID(int $messageID): int return (int) $memberID; } - /** - * Fetches the list of boards the user is allowed to see and organizes them by categories. - * - * This function queries the database for boards visible to the current user, grouped by categories. - * It returns a structured array of categories and their associated boards. - * - * @param array $boards An array of board IDs to mark as selected. - * @return array The structured array of categories and boards. - */ - public static function getUserVisibleBoards(array $boards): array - { - // Query to fetch boards visible to the user. - $request = Db::$db->query( - 'SELECT id_board, b.name, child_level, c.name AS cat_name, id_cat - FROM {db_prefix}boards AS b - JOIN {db_prefix}categories AS c USING (id_cat) - WHERE {query_see_board} - AND redirect = {string:empty_string} - ORDER BY board_order', - [ - 'empty_string' => '', - ], - identifier: 'order_by_board_order', - ); - - $categories = []; - $categoryTracker = []; - $currentCategoryIndex = -1; - - // Process the results and group boards by categories. - while ($row = Db::$db->fetch_assoc($request)) { - if (!isset($categoryTracker[$row['id_cat']])) { - $categories[++$currentCategoryIndex] = [ - 'id' => (int) $row['id_cat'], - 'name' => $row['cat_name'], - 'boards' => [], - ]; - $categoryTracker[$row['id_cat']] = true; - } - - $categories[$currentCategoryIndex]['boards'][] = [ - 'id' => (int) $row['id_board'], - 'name' => $row['name'], - 'child_level' => (int) $row['child_level'], - 'selected' => \in_array($row['id_board'], $boards), - ]; - } - - Db::$db->free_result($request); - - return $categories; - } - /** * Modify the settings and position of a board. * Used by ManageBoards.php to change the settings of a board. @@ -2313,7 +2284,6 @@ protected function loadBoardInfo(): void case 'id_board': $props['id'] = (int) $value; - $props['recycle'] = !empty(Config::$modSettings['recycle_enable']) && !empty(Config::$modSettings['recycle_board']) && Config::$modSettings['recycle_board'] == $value; break; case 'id_cat': diff --git a/Sources/Category.php b/Sources/Category.php index b34a9db6fd0..09905c751f7 100644 --- a/Sources/Category.php +++ b/Sources/Category.php @@ -136,6 +136,24 @@ class Category implements \ArrayAccess */ public bool $show_unread; + /** + * @var array + * + * IDs of all boards in this category. + * + * For the sake of compatibility with \ArrayAccess it is possible to write + * to this property, but doing so is pointless because the value will be + * overwritten the next time the property is read. + */ + public array $child_ids { + &get { + $this->child_ids = []; + self::recursiveBoards($this->child_ids, $this); + + return $this->child_ids; + } + } + /************************** * Public static properties **************************/ @@ -785,9 +803,9 @@ public static function getTree(): void * Used by self::getTree(). * * @param array &$list The board list - * @param \SMF\Category|\SMF\Board &$tree The board tree + * @param self|Board &$tree The board tree */ - public static function recursiveBoards(array &$list, \SMF\Category|\SMF\Board &$tree): void + public static function recursiveBoards(array &$list, self|Board &$tree): void { if (empty($tree->children)) { return; diff --git a/Themes/default/Search.template.php b/Themes/default/Search.template.php index d8fd66ab09a..5ec56448274 100644 --- a/Themes/default/Search.template.php +++ b/Themes/default/Search.template.php @@ -172,7 +172,7 @@ function template_main() ', $category['name'], '