diff --git a/com.woltlab.wcf/templates/searchResultList.tpl b/com.woltlab.wcf/templates/searchResultList.tpl
index 32dc0b216c4..681e9940341 100644
--- a/com.woltlab.wcf/templates/searchResultList.tpl
+++ b/com.woltlab.wcf/templates/searchResultList.tpl
@@ -1,43 +1,41 @@
-
+ {lang}wcf.search.object.{$message->getObjectTypeName()}{/lang}
+
+ {unsafe:$message->getFormattedMessage()}
+
+ {/foreach}
+
diff --git a/ts/WoltLabSuite/Core/Ui/Search/Extended.ts b/ts/WoltLabSuite/Core/Ui/Search/Extended.ts
index c4155d82e7e..dc1aa408185 100644
--- a/ts/WoltLabSuite/Core/Ui/Search/Extended.ts
+++ b/ts/WoltLabSuite/Core/Ui/Search/Extended.ts
@@ -10,22 +10,15 @@
import { dboAction } from "../../Ajax";
import DatePicker from "../../Date/Picker";
import * as DomUtil from "../../Dom/Util";
-import { ucfirst } from "../../StringUtil";
import UiSearchInput from "./Input";
-import * as UiScroll from "../Scroll";
import { setValues as setItemListValues } from "../ItemList";
type ResponseSearch = {
count: number;
title: string;
- pages?: number;
pageNo?: number;
- searchID?: number;
template?: string;
-};
-
-type ResponseSearchResults = {
- template: string;
+ containerCssClassName?: string;
};
type SearchParameters = string[][];
@@ -42,11 +35,8 @@ export class UiSearchExtended {
private readonly typeInput: HTMLSelectElement;
private readonly delimiter: HTMLDivElement;
private readonly filtersContainer: HTMLDetailsElement;
- private searchID: number | undefined = undefined;
- private pages = 0;
private activePage = 1;
private lastSearchRequest: AbortController | undefined = undefined;
- private lastSearchResultRequest: AbortController | undefined = undefined;
private searchParameters: SearchParameters = [];
constructor() {
@@ -117,17 +107,15 @@ export class UiSearchExtended {
const request = dboAction("search", "wcf\\data\\search\\SearchAction").payload(this.getFormData());
this.lastSearchRequest = request.getAbortController();
- const { count, searchID, title, pages, pageNo, template } = (await request.dispatch()) as ResponseSearch;
+ const { count, title, pageNo, template, containerCssClassName } = (await request.dispatch()) as ResponseSearch;
document.querySelector(".contentTitle")!.textContent = title;
- this.searchID = searchID;
this.removeSearchResults();
if (count > 0) {
- this.pages = pages!;
this.activePage = pageNo!;
- this.showSearchResults(template!);
+ this.showSearchResults(template!, containerCssClassName!);
} else if (Object.keys(this.getFormData()).length > 4) {
// Show the advanced filters when there are no results
// but advanced filters are applied.
@@ -237,81 +225,23 @@ export class UiSearchExtended {
void this.search(SearchAction.Init);
}
- private initPagination(position: "top" | "bottom"): void {
- const wrapperDiv = document.createElement("div");
- wrapperDiv.classList.add("pagination" + ucfirst(position));
- this.form.parentElement!.insertBefore(wrapperDiv, this.delimiter);
-
- const pagination = document.createElement("woltlab-core-pagination");
- pagination.page = this.activePage;
- pagination.count = this.pages;
- pagination.behavior = "button";
- pagination.url = this.getPaginationUrl();
- pagination.addEventListener("switchPage", (event: CustomEvent) => {
- void this.changePage(event.detail).then(() => {
- if (position === "bottom") {
- UiScroll.element(this.form.nextElementSibling as HTMLElement, undefined, "auto");
- }
- });
- });
-
- wrapperDiv.append(pagination);
- }
-
- private getPaginationUrl(): string {
- const url = new URL(this.form.action);
- url.search += url.search !== "" ? "&" : "?";
-
- const searchParameters: SearchParameters = [];
- new FormData(this.form).forEach((value, key) => {
- // eslint-disable-next-line @typescript-eslint/no-base-to-string
- const trimmed = value.toString().trim();
- if (trimmed) {
- searchParameters.push([key, trimmed]);
- }
- });
- const parameters = searchParameters.slice();
-
- if (this.activePage > 1) {
- parameters.push(["pageNo", this.activePage.toString()]);
- }
- url.search += new URLSearchParams(parameters).toString();
-
- return url.toString();
- }
-
- private async changePage(pageNo: number): Promise {
- this.lastSearchResultRequest?.abort();
-
- const request = dboAction("getSearchResults", "wcf\\data\\search\\SearchAction").payload({
- searchID: this.searchID,
- pageNo,
- });
- this.lastSearchResultRequest = request.getAbortController();
- const { template } = (await request.dispatch()) as ResponseSearchResults;
- this.activePage = pageNo;
- this.removeSearchResults();
- this.showSearchResults(template);
- this.updateQueryString(SearchAction.Navigation);
- }
-
private removeSearchResults(): void {
while (this.form.nextSibling !== null && this.form.nextSibling !== this.delimiter) {
this.form.parentElement!.removeChild(this.form.nextSibling);
}
}
- private showSearchResults(template: string): void {
- if (this.pages > 1) {
- this.initPagination("top");
- }
-
- const fragment = DomUtil.createFragmentFromHtml(template);
- this.form.parentElement!.insertBefore(fragment, this.delimiter);
+ private showSearchResults(template: string, containerCssClassName: string): void {
+ const container = document.createElement("div");
+ container.classList.add("section", ...containerCssClassName.split(" ").filter((className) => className !== ""));
+ this.form.parentElement!.insertBefore(container, this.delimiter);
+ DomUtil.setInnerHtml(container, template);
- if (this.pages > 1) {
- this.initPagination("bottom");
- }
+ // The list view handles the pagination, but the page number is part of the search url.
+ container.querySelector("woltlab-core-pagination")?.addEventListener("switchPage", (event: CustomEvent) => {
+ this.activePage = event.detail;
+ this.updateQueryString(SearchAction.Navigation);
+ });
}
}
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Search/Extended.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Search/Extended.js
index d10249b0686..37eb749d447 100644
--- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Search/Extended.js
+++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Search/Extended.js
@@ -6,25 +6,21 @@
* @license GNU Lesser General Public License
* @woltlabExcludeBundle all
*/
-define(["require", "exports", "tslib", "../../Ajax", "../../Date/Picker", "../../Dom/Util", "../../StringUtil", "./Input", "../Scroll", "../ItemList"], function (require, exports, tslib_1, Ajax_1, Picker_1, DomUtil, StringUtil_1, Input_1, UiScroll, ItemList_1) {
+define(["require", "exports", "tslib", "../../Ajax", "../../Date/Picker", "../../Dom/Util", "./Input", "../ItemList"], function (require, exports, tslib_1, Ajax_1, Picker_1, DomUtil, Input_1, ItemList_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UiSearchExtended = void 0;
Picker_1 = tslib_1.__importDefault(Picker_1);
DomUtil = tslib_1.__importStar(DomUtil);
Input_1 = tslib_1.__importDefault(Input_1);
- UiScroll = tslib_1.__importStar(UiScroll);
class UiSearchExtended {
form;
queryInput;
typeInput;
delimiter;
filtersContainer;
- searchID = undefined;
- pages = 0;
activePage = 1;
lastSearchRequest = undefined;
- lastSearchResultRequest = undefined;
searchParameters = [];
constructor() {
this.form = document.getElementById("extendedSearchForm");
@@ -85,14 +81,12 @@ define(["require", "exports", "tslib", "../../Ajax", "../../Date/Picker", "../..
this.lastSearchRequest?.abort();
const request = (0, Ajax_1.dboAction)("search", "wcf\\data\\search\\SearchAction").payload(this.getFormData());
this.lastSearchRequest = request.getAbortController();
- const { count, searchID, title, pages, pageNo, template } = (await request.dispatch());
+ const { count, title, pageNo, template, containerCssClassName } = (await request.dispatch());
document.querySelector(".contentTitle").textContent = title;
- this.searchID = searchID;
this.removeSearchResults();
if (count > 0) {
- this.pages = pages;
this.activePage = pageNo;
- this.showSearchResults(template);
+ this.showSearchResults(template, containerCssClassName);
}
else if (Object.keys(this.getFormData()).length > 4) {
// Show the advanced filters when there are no results
@@ -191,69 +185,21 @@ define(["require", "exports", "tslib", "../../Ajax", "../../Date/Picker", "../..
this.typeInput.dispatchEvent(new Event("change"));
void this.search(2 /* SearchAction.Init */);
}
- initPagination(position) {
- const wrapperDiv = document.createElement("div");
- wrapperDiv.classList.add("pagination" + (0, StringUtil_1.ucfirst)(position));
- this.form.parentElement.insertBefore(wrapperDiv, this.delimiter);
- const pagination = document.createElement("woltlab-core-pagination");
- pagination.page = this.activePage;
- pagination.count = this.pages;
- pagination.behavior = "button";
- pagination.url = this.getPaginationUrl();
- pagination.addEventListener("switchPage", (event) => {
- void this.changePage(event.detail).then(() => {
- if (position === "bottom") {
- UiScroll.element(this.form.nextElementSibling, undefined, "auto");
- }
- });
- });
- wrapperDiv.append(pagination);
- }
- getPaginationUrl() {
- const url = new URL(this.form.action);
- url.search += url.search !== "" ? "&" : "?";
- const searchParameters = [];
- new FormData(this.form).forEach((value, key) => {
- // eslint-disable-next-line @typescript-eslint/no-base-to-string
- const trimmed = value.toString().trim();
- if (trimmed) {
- searchParameters.push([key, trimmed]);
- }
- });
- const parameters = searchParameters.slice();
- if (this.activePage > 1) {
- parameters.push(["pageNo", this.activePage.toString()]);
- }
- url.search += new URLSearchParams(parameters).toString();
- return url.toString();
- }
- async changePage(pageNo) {
- this.lastSearchResultRequest?.abort();
- const request = (0, Ajax_1.dboAction)("getSearchResults", "wcf\\data\\search\\SearchAction").payload({
- searchID: this.searchID,
- pageNo,
- });
- this.lastSearchResultRequest = request.getAbortController();
- const { template } = (await request.dispatch());
- this.activePage = pageNo;
- this.removeSearchResults();
- this.showSearchResults(template);
- this.updateQueryString(1 /* SearchAction.Navigation */);
- }
removeSearchResults() {
while (this.form.nextSibling !== null && this.form.nextSibling !== this.delimiter) {
this.form.parentElement.removeChild(this.form.nextSibling);
}
}
- showSearchResults(template) {
- if (this.pages > 1) {
- this.initPagination("top");
- }
- const fragment = DomUtil.createFragmentFromHtml(template);
- this.form.parentElement.insertBefore(fragment, this.delimiter);
- if (this.pages > 1) {
- this.initPagination("bottom");
- }
+ showSearchResults(template, containerCssClassName) {
+ const container = document.createElement("div");
+ container.classList.add("section", ...containerCssClassName.split(" ").filter((className) => className !== ""));
+ this.form.parentElement.insertBefore(container, this.delimiter);
+ DomUtil.setInnerHtml(container, template);
+ // The list view handles the pagination, but the page number is part of the search url.
+ container.querySelector("woltlab-core-pagination")?.addEventListener("switchPage", (event) => {
+ this.activePage = event.detail;
+ this.updateQueryString(1 /* SearchAction.Navigation */);
+ });
}
}
exports.UiSearchExtended = UiSearchExtended;
diff --git a/wcfsetup/install/files/lib/data/search/Search.class.php b/wcfsetup/install/files/lib/data/search/Search.class.php
index 45bcaf8fc4f..e69c2ab8663 100644
--- a/wcfsetup/install/files/lib/data/search/Search.class.php
+++ b/wcfsetup/install/files/lib/data/search/Search.class.php
@@ -3,6 +3,7 @@
namespace wcf\data\search;
use wcf\data\DatabaseObject;
+use wcf\system\WCF;
/**
* Represents a search.
@@ -17,5 +18,49 @@
* @property-read int $searchTime timestamp of the search
* @property-read string $searchType type of search, like `messages` or `users`
* @property-read string $searchHash hash identifying the search for the user to reuse the result within the first 30 minutes
+ *
+ * @phpstan-type SearchData array{
+ * results: list,
+ * query: string,
+ * additionalData: array,
+ * sortField: string,
+ * sortOrder: string,
+ * objectTypeNames: string[],
+ * }
*/
-class Search extends DatabaseObject {}
+class Search extends DatabaseObject
+{
+ /**
+ * @var SearchData
+ */
+ private array $unserializedSearchData;
+
+ /**
+ * Returns true if the active user can access the results of this message search.
+ *
+ * @since 6.3
+ */
+ public function canAccess(): bool
+ {
+ if ($this->isNil() || $this->searchType !== 'messages') {
+ return false;
+ }
+
+ return $this->userID === null || $this->userID === WCF::getUser()->userID;
+ }
+
+ /**
+ * Returns the unserialized data and parameters of this search.
+ *
+ * @return SearchData
+ * @since 6.3
+ */
+ public function getSearchData(): array
+ {
+ if (!isset($this->unserializedSearchData)) {
+ $this->unserializedSearchData = \unserialize($this->searchData);
+ }
+
+ return $this->unserializedSearchData;
+ }
+}
diff --git a/wcfsetup/install/files/lib/data/search/SearchAction.class.php b/wcfsetup/install/files/lib/data/search/SearchAction.class.php
index 62e030dd009..60082df0021 100644
--- a/wcfsetup/install/files/lib/data/search/SearchAction.class.php
+++ b/wcfsetup/install/files/lib/data/search/SearchAction.class.php
@@ -95,10 +95,9 @@ public function validateSearch(): void
* @return array{
* count: int,
* title: string,
- * pages: int,
* pageNo: int,
- * searchID: int,
* template: string,
+ * containerCssClassName: string,
* }|array{count: int, title: string}
* @since 5.5
*/
@@ -117,34 +116,25 @@ public function search(): array
];
}
- $startIndex = 0;
- if ($this->parameters['pageNo'] > 1) {
- $startIndex = \SEARCH_RESULTS_PER_PAGE * ($this->parameters['pageNo'] - 1);
- }
- $resultHandler = new SearchResultHandler($search, $startIndex);
- $resultHandler->loadSearchResults();
- $templateName = $resultHandler->getTemplateName();
- SearchResultTextParser::getInstance()->setSearchQuery($resultHandler->getQuery());
+ $resultHandler = new SearchResultHandler($search);
+ $listView = $resultHandler->getResultListView();
+ $listView->setPageNo(\max(1, $this->parameters['pageNo']));
return [
- 'count' => $resultHandler->countSearchResults(),
+ 'count' => $listView->countItems(),
'title' => WCF::getLanguage()->getDynamicVariable('wcf.search.results.title', [
- 'count' => $resultHandler->countSearchResults(),
+ 'count' => $listView->countItems(),
'query' => $resultHandler->getQuery(),
]),
- 'pages' => (int)\ceil($resultHandler->countSearchResults() / \SEARCH_RESULTS_PER_PAGE),
- 'pageNo' => $this->parameters['pageNo'] ?: 1,
- 'searchID' => $search->searchID,
- 'template' => WCF::getTPL()->render($templateName['application'], $templateName['templateName'], [
- 'objects' => $resultHandler->getSearchResults(),
- 'query' => $resultHandler->getQuery(),
- 'customIcons' => $resultHandler->getCustomIcons(),
- ]),
+ 'pageNo' => $listView->getPageNo(),
+ 'template' => $listView->render(),
+ 'containerCssClassName' => $listView->getContainerCssClassName(),
];
}
/**
* @since 5.5
+ * @deprecated 6.3 The search results are paginated by `SearchResultListView`.
*/
public function validateGetSearchResults(): void
{
@@ -152,10 +142,7 @@ public function validateGetSearchResults(): void
$this->readInteger('pageNo');
$search = new Search($this->parameters['searchID']);
- if ($search->isNil() || $search->searchType !== 'messages') {
- throw new IllegalLinkException();
- }
- if ($search->userID !== null && $search->userID !== WCF::getUser()->userID) {
+ if (!$search->canAccess()) {
throw new IllegalLinkException();
}
}
@@ -163,6 +150,7 @@ public function validateGetSearchResults(): void
/**
* @return array{template: string}
* @since 5.5
+ * @deprecated 6.3 The search results are paginated by `SearchResultListView`.
*/
public function getSearchResults(): array
{
diff --git a/wcfsetup/install/files/lib/event/listView/user/SearchResultListViewInitialized.class.php b/wcfsetup/install/files/lib/event/listView/user/SearchResultListViewInitialized.class.php
new file mode 100644
index 00000000000..edc46ca2276
--- /dev/null
+++ b/wcfsetup/install/files/lib/event/listView/user/SearchResultListViewInitialized.class.php
@@ -0,0 +1,19 @@
+
+ * @since 6.3
+ */
+final class SearchResultListViewInitialized implements IPsr14Event
+{
+ public function __construct(public readonly SearchResultListView $listView) {}
+}
diff --git a/wcfsetup/install/files/lib/system/listView/user/SearchResultListView.class.php b/wcfsetup/install/files/lib/system/listView/user/SearchResultListView.class.php
new file mode 100644
index 00000000000..9954f980b16
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/listView/user/SearchResultListView.class.php
@@ -0,0 +1,154 @@
+
+ * @since 6.3
+ *
+ * @extends AbstractListView<(DatabaseObject&ISearchResultObject)|(DatabaseObjectDecorator&ISearchResultObject), DatabaseObjectList>
+ */
+final class SearchResultListView extends AbstractListView
+{
+ private readonly Search $search;
+ private SearchResultHandler $resultHandler;
+
+ public function __construct(
+ public readonly int $searchID
+ ) {
+ $this->search = new Search($this->searchID);
+
+ $this->setItemsPerPage(\SEARCH_RESULTS_PER_PAGE);
+ $this->setAllowFiltering(false);
+ $this->setAllowSorting(false);
+ $this->setAllowInteractions(false);
+ $this->setAllowBulkInteractions(false);
+ }
+
+ #[\Override]
+ public function isAccessible(): bool
+ {
+ return $this->search->canAccess();
+ }
+
+ #[\Override]
+ protected function initObjectList(): void
+ {
+ $this->objectList = $this->createObjectList();
+ $this->fireInitializedEvent();
+
+ $this->objectCount = new SearchResultHandler($this->search)->countSearchResults();
+ if ($this->getPageNo() > 1 && $this->getPageNo() > $this->countPages()) {
+ $this->setPageNo($this->countPages() ?: 1);
+ }
+
+ $this->resultHandler = new SearchResultHandler(
+ $this->search,
+ ($this->getPageNo() - 1) * $this->getItemsPerPage(),
+ $this->getItemsPerPage()
+ );
+ $this->resultHandler->loadSearchResults();
+ }
+
+ #[\Override]
+ protected function applyFilters(): void
+ {
+ // Filters are applied by the search engine when the search is executed.
+ }
+
+ /**
+ * @return list<(DatabaseObject&ISearchResultObject)|(DatabaseObjectDecorator&ISearchResultObject)>
+ */
+ #[\Override]
+ public function getItems(): array
+ {
+ $items = [];
+ foreach ($this->getResultHandler()->getSearchResults() as $item) {
+ if (!($item instanceof DatabaseObject) && !($item instanceof DatabaseObjectDecorator)) {
+ throw new ImplementationException(\get_class($item), DatabaseObjectDecorator::class);
+ }
+
+ $items[] = $item;
+ }
+
+ return $items;
+ }
+
+ #[\Override]
+ public function countItems(): int
+ {
+ $this->getObjectList();
+
+ return $this->objectCount;
+ }
+
+ public function getQuery(): string
+ {
+ return $this->getResultHandler()->getQuery();
+ }
+
+ #[\Override]
+ public function renderItems(): string
+ {
+ $resultHandler = $this->getResultHandler();
+ $templateName = $resultHandler->getTemplateName();
+ SearchResultTextParser::getInstance()->setSearchQuery($resultHandler->getQuery());
+
+ return WCF::getTPL()->render($templateName['application'], $templateName['templateName'], [
+ 'objects' => $resultHandler->getSearchResults(),
+ 'query' => $resultHandler->getQuery(),
+ 'customIcons' => $resultHandler->getCustomIcons(),
+ 'view' => $this,
+ ]);
+ }
+
+ private function getResultHandler(): SearchResultHandler
+ {
+ $this->getObjectList();
+
+ return $this->resultHandler;
+ }
+
+ #[\Override]
+ protected function getInitializedEvent(): SearchResultListViewInitialized
+ {
+ return new SearchResultListViewInitialized($this);
+ }
+
+ /**
+ * @return DatabaseObjectList
+ */
+ #[\Override]
+ protected function createObjectList(): DatabaseObjectList
+ {
+ return new class extends DatabaseObjectList {};
+ }
+
+ #[\Override]
+ public function getParameters(): array
+ {
+ return [
+ 'searchID' => $this->searchID,
+ ];
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/search/AbstractSearchProvider.class.php b/wcfsetup/install/files/lib/system/search/AbstractSearchProvider.class.php
index 97eeed3fbac..54205a1674a 100644
--- a/wcfsetup/install/files/lib/system/search/AbstractSearchProvider.class.php
+++ b/wcfsetup/install/files/lib/system/search/AbstractSearchProvider.class.php
@@ -3,7 +3,9 @@
namespace wcf\system\search;
use wcf\data\object\type\AbstractObjectTypeProcessor;
+use wcf\data\search\Search;
use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\listView\AbstractListView;
/**
* This class provides default implementations for the ISearchProvider interface.
@@ -100,6 +102,12 @@ public function getCustomIconName(): ?string
return null;
}
+ #[\Override]
+ public function getResultListView(Search $search): ?AbstractListView
+ {
+ return null;
+ }
+
/**
* @deprecated 5.5
*/
diff --git a/wcfsetup/install/files/lib/system/search/ISearchProvider.class.php b/wcfsetup/install/files/lib/system/search/ISearchProvider.class.php
index e13ff972215..9ad6edbc507 100644
--- a/wcfsetup/install/files/lib/system/search/ISearchProvider.class.php
+++ b/wcfsetup/install/files/lib/system/search/ISearchProvider.class.php
@@ -3,7 +3,9 @@
namespace wcf\system\search;
use wcf\data\search\ISearchResultObject;
+use wcf\data\search\Search;
use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\listView\AbstractListView;
/**
* Interface for full-text search providers.
@@ -116,4 +118,14 @@ public function getFetchObjectsQuery(?PreparedStatementConditionBuilder $additio
* @since 6.0
*/
public function getCustomIconName(): ?string;
+
+ /**
+ * Can optionally return a list view that renders the results of the given search.
+ * Returns `null` to use the default `SearchResultListView`.
+ * This method is only called, if this provider is the only provider that is searched.
+ *
+ * @return ?AbstractListView<*, *>
+ * @since 6.3
+ */
+ public function getResultListView(Search $search): ?AbstractListView;
}
diff --git a/wcfsetup/install/files/lib/system/search/SearchResultHandler.class.php b/wcfsetup/install/files/lib/system/search/SearchResultHandler.class.php
index cb127442547..6fb482beddf 100644
--- a/wcfsetup/install/files/lib/system/search/SearchResultHandler.class.php
+++ b/wcfsetup/install/files/lib/system/search/SearchResultHandler.class.php
@@ -7,6 +7,8 @@
use wcf\page\SearchResultPage;
use wcf\system\exception\ImplementationException;
use wcf\system\exception\SystemException;
+use wcf\system\listView\AbstractListView;
+use wcf\system\listView\user\SearchResultListView;
/**
* Provides the results of a full-text search.
@@ -15,18 +17,15 @@
* @copyright 2001-2021 WoltLab GmbH
* @license GNU Lesser General Public License
* @since 5.5
+ *
+ * @phpstan-import-type SearchData from Search
*/
final class SearchResultHandler
{
private readonly Search $search;
/**
- * @var array{
- * query: string,
- * objectTypeNames: string[],
- * results: list,
- * additionalData: array
- * }
+ * @var SearchData
*/
private array $searchData;
@@ -46,7 +45,7 @@ public function __construct(Search $search, int $startIndex = 0, int $limit = \S
$this->search = $search;
$this->startIndex = $startIndex;
$this->limit = $limit;
- $this->searchData = \unserialize($this->search->searchData);
+ $this->searchData = $this->search->getSearchData();
}
public function countSearchResults(): int
@@ -139,6 +138,27 @@ public function getTemplateName(): array
return $this->getLegacyTemplateName();
}
+ /**
+ * Returns the list view that renders the search results.
+ *
+ * @return AbstractListView<*, *>
+ * @since 6.3
+ */
+ public function getResultListView(): AbstractListView
+ {
+ if (\count($this->searchData['objectTypeNames']) === 1) {
+ $objectType = SearchEngine::getInstance()->getObjectType($this->searchData['objectTypeNames'][0]);
+ if ($objectType instanceof ISearchProvider) {
+ $listView = $objectType->getResultListView($this->search);
+ if ($listView !== null) {
+ return $listView;
+ }
+ }
+ }
+
+ return new SearchResultListView($this->search->searchID);
+ }
+
/**
* @return array
*/