From: Marcel Werk Date: Mon, 4 Nov 2024 14:56:15 +0000 (+0100) Subject: Migrate exception log to grid view X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=d5535c94c43f8b3c3dd46451bd2573e0a139c23a;p=GitHub%2FWoltLab%2FWCF.git Migrate exception log to grid view --- diff --git a/com.woltlab.wcf/templates/shared_gridView.tpl b/com.woltlab.wcf/templates/shared_gridView.tpl index b8ea8b45ae..7838d6b570 100644 --- a/com.woltlab.wcf/templates/shared_gridView.tpl +++ b/com.woltlab.wcf/templates/shared_gridView.tpl @@ -48,7 +48,12 @@ {$view->getPageNo()}, '{unsafe:$view->getBaseUrl()|encodeJS}', '{unsafe:$view->getSortField()|encodeJS}', - '{unsafe:$view->getSortOrder()|encodeJS}' + '{unsafe:$view->getSortOrder()|encodeJS}', + new Map([ + {foreach from=$view->getParameters() key='name' item='value'} + ['{unsafe:$name|encodeJs}', '{unsafe:$value|encodeJs}'], + {/foreach} + ]) ); }); diff --git a/com.woltlab.wcf/templates/shared_gridViewRows.tpl b/com.woltlab.wcf/templates/shared_gridViewRows.tpl index f494ad2251..3974a70912 100644 --- a/com.woltlab.wcf/templates/shared_gridViewRows.tpl +++ b/com.woltlab.wcf/templates/shared_gridViewRows.tpl @@ -1,6 +1,6 @@ {foreach from=$view->getRows() item='row'} - + {foreach from=$view->getColumns() item='column'} {unsafe:$view->renderColumn($column, $row)} diff --git a/ts/WoltLabSuite/Core/Acp/Controller/ExceptionLog/View.ts b/ts/WoltLabSuite/Core/Acp/Controller/ExceptionLog/View.ts new file mode 100644 index 0000000000..0edc37cf86 --- /dev/null +++ b/ts/WoltLabSuite/Core/Acp/Controller/ExceptionLog/View.ts @@ -0,0 +1,38 @@ +/** + * Shows the dialog that shows exception details. + * + * @author Marcel Werk + * @copyright 2001-2024 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.1 + */ + +import { renderException } from "WoltLabSuite/Core/Api/Exceptions/RenderException"; +import { copyTextToClipboard } from "WoltLabSuite/Core/Clipboard"; +import { dialogFactory } from "WoltLabSuite/Core/Component/Dialog"; +import { promiseMutex } from "WoltLabSuite/Core/Helper/PromiseMutex"; +import { wheneverFirstSeen } from "WoltLabSuite/Core/Helper/Selector"; +import { getPhrase } from "WoltLabSuite/Core/Language"; + +async function showDialog(button: HTMLElement): Promise { + const response = await renderException(button.closest("tr")!.dataset.objectId!); + if (!response.ok) { + return; + } + + const dialog = dialogFactory().fromHtml(response.value.template).withoutControls(); + dialog.content.querySelector(".jsCopyButton")?.addEventListener("click", () => { + void copyTextToClipboard(dialog.content.querySelector(".jsCopyException")!.value); + }); + + dialog.show(getPhrase("wcf.acp.exceptionLog.exception.message")); +} + +export function setup(): void { + wheneverFirstSeen(".jsExceptionLogEntry", (button) => { + button.addEventListener( + "click", + promiseMutex(() => showDialog(button)), + ); + }); +} diff --git a/ts/WoltLabSuite/Core/Api/GridViews/GetRows.ts b/ts/WoltLabSuite/Core/Api/GridViews/GetRows.ts index 6dc07920dc..e271f09e25 100644 --- a/ts/WoltLabSuite/Core/Api/GridViews/GetRows.ts +++ b/ts/WoltLabSuite/Core/Api/GridViews/GetRows.ts @@ -13,6 +13,7 @@ export async function getRows( sortField: string = "", sortOrder: string = "ASC", filters?: Map, + gridViewParameters?: Map, ): Promise> { const url = new URL(`${window.WSC_RPC_API_URL}core/gridViews/rows`); url.searchParams.set("gridView", gridViewClass); @@ -24,6 +25,11 @@ export async function getRows( url.searchParams.set(`filters[${key}]`, value); }); } + if (gridViewParameters) { + gridViewParameters.forEach((value, key) => { + url.searchParams.set(`gridViewParameters[${key}]`, value); + }); + } let response: Response; try { diff --git a/ts/WoltLabSuite/Core/Component/GridView.ts b/ts/WoltLabSuite/Core/Component/GridView.ts index 6730ea4cb5..644331c155 100644 --- a/ts/WoltLabSuite/Core/Component/GridView.ts +++ b/ts/WoltLabSuite/Core/Component/GridView.ts @@ -18,6 +18,7 @@ export class GridView { #defaultSortField: string; #defaultSortOrder: string; #filters: Map; + #gridViewParameters?: Map; constructor( gridId: string, @@ -26,6 +27,7 @@ export class GridView { baseUrl: string = "", sortField = "", sortOrder = "ASC", + gridViewParameters?: Map, ) { this.#gridClassName = gridClassName; this.#table = document.getElementById(`${gridId}_table`) as HTMLTableElement; @@ -39,6 +41,7 @@ export class GridView { this.#defaultSortField = sortField; this.#sortOrder = sortOrder; this.#defaultSortOrder = sortOrder; + this.#gridViewParameters = gridViewParameters; this.#initPagination(); this.#initSorting(); @@ -107,7 +110,14 @@ export class GridView { async #loadRows(updateQueryString: boolean = true): Promise { const response = ( - await getRows(this.#gridClassName, this.#pageNo, this.#sortField, this.#sortOrder, this.#filters) + await getRows( + this.#gridClassName, + this.#pageNo, + this.#sortField, + this.#sortOrder, + this.#filters, + this.#gridViewParameters, + ) ).unwrap(); DomUtil.setInnerHtml(this.#table.querySelector("tbody")!, response.template); @@ -137,9 +147,11 @@ export class GridView { parameters.push(["sortField", this.#sortField]); parameters.push(["sortOrder", this.#sortOrder]); } - this.#filters.forEach((value, key) => { - parameters.push([`filters[${key}]`, value]); - }); + if (this.#filters) { + this.#filters.forEach((value, key) => { + parameters.push([`filters[${key}]`, value]); + }); + } if (parameters.length > 0) { url.search += url.search !== "" ? "&" : "?"; @@ -212,6 +224,9 @@ export class GridView { } #renderFilters(labels: ArrayLike): void { + if (!this.#filterPills) { + return; + } this.#filterPills.innerHTML = ""; if (!this.#filters) { return; diff --git a/wcfsetup/install/files/acp/templates/exceptionLogView.tpl b/wcfsetup/install/files/acp/templates/exceptionLogView.tpl index f33ad72f1a..88c1702b72 100644 --- a/wcfsetup/install/files/acp/templates/exceptionLogView.tpl +++ b/wcfsetup/install/files/acp/templates/exceptionLogView.tpl @@ -1,22 +1,4 @@ {include file='header' pageTitle='wcf.acp.exceptionLog'} -
@@ -65,94 +47,13 @@ {/if} -{hascontent} -
- {content}{pages print=true assign=pagesLinks controller="ExceptionLogView" link="pageNo=%d&logFile=$logFile"}{/content} -
-{/hascontent} +{unsafe:$gridView->render()} -{if !$logFiles|empty} - {if $logFile} - {foreach from=$exceptions item='exception' key='exceptionKey'} -
- - {$exception[message]} - - -
-
-
{lang}wcf.acp.exceptionLog.exception.date{/lang}
-
{$exception[date]|plainTime}
-
- -
-
{lang}wcf.acp.exceptionLog.exception.requestURI{/lang}
-
{$exception[requestURI]}
-
-
-
{lang}wcf.acp.exceptionLog.exception.referrer{/lang}
-
{$exception[referrer]}
-
-
-
{lang}wcf.acp.exceptionLog.exception.userAgent{/lang}
-
{$exception[userAgent]}
-
-
-
{lang}wcf.acp.exceptionLog.exception.memory{/lang}
-
{$exception[peakMemory]|filesizeBinary} / {if $exception[maxMemory] == -1}∞{else}{$exception[maxMemory]|filesizeBinary}{/if}
-
- {foreach from=$exception[chain] item=chain} -
-
{lang}wcf.acp.exceptionLog.exception.message{/lang}
-
{$chain[message]}
-
-
-
{lang}wcf.acp.exceptionLog.exception.class{/lang}
-
{$chain[class]}
-
-
-
{lang}wcf.acp.exceptionLog.exception.file{/lang}
-
{$chain[file]} ({$chain[line]})
-
- {if !$chain[information]|empty} - {foreach from=$chain[information] item=extraInformation} -
-
{$extraInformation[0]}
-
{$extraInformation[1]}
-
- {/foreach} - {/if} -
-
{lang}wcf.acp.exceptionLog.exception.stacktrace{/lang}
-
-
    - {foreach from=$chain[stack] item=stack} -
  1. {$stack[file]} ({$stack[line]}): {$stack[class]}{$stack[type]}{$stack[function]}(…)
  2. - {/foreach} -
-
-
- {/foreach} -
-
-
-
-
-
- {/foreach} - -
- {hascontent} -
- {content}{@$pagesLinks}{/content} -
- {/hascontent} -
- {elseif $exceptionID} - {lang}wcf.acp.exceptionLog.exceptionNotFound{/lang} - {/if} -{else} - {lang}wcf.global.noItems{/lang} -{/if} + {include file='footer'} diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Controller/ExceptionLog/View.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Controller/ExceptionLog/View.js new file mode 100644 index 0000000000..f8632d89ca --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Controller/ExceptionLog/View.js @@ -0,0 +1,29 @@ +/** + * Shows the dialog that shows exception details. + * + * @author Marcel Werk + * @copyright 2001-2024 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.1 + */ +define(["require", "exports", "WoltLabSuite/Core/Api/Exceptions/RenderException", "WoltLabSuite/Core/Clipboard", "WoltLabSuite/Core/Component/Dialog", "WoltLabSuite/Core/Helper/PromiseMutex", "WoltLabSuite/Core/Helper/Selector", "WoltLabSuite/Core/Language"], function (require, exports, RenderException_1, Clipboard_1, Dialog_1, PromiseMutex_1, Selector_1, Language_1) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.setup = setup; + async function showDialog(button) { + const response = await (0, RenderException_1.renderException)(button.closest("tr").dataset.objectId); + if (!response.ok) { + return; + } + const dialog = (0, Dialog_1.dialogFactory)().fromHtml(response.value.template).withoutControls(); + dialog.content.querySelector(".jsCopyButton")?.addEventListener("click", () => { + void (0, Clipboard_1.copyTextToClipboard)(dialog.content.querySelector(".jsCopyException").value); + }); + dialog.show((0, Language_1.getPhrase)("wcf.acp.exceptionLog.exception.message")); + } + function setup() { + (0, Selector_1.wheneverFirstSeen)(".jsExceptionLogEntry", (button) => { + button.addEventListener("click", (0, PromiseMutex_1.promiseMutex)(() => showDialog(button))); + }); + } +}); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Api/GridViews/GetRows.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Api/GridViews/GetRows.js index 7a31864e72..3299f1af96 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Api/GridViews/GetRows.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Api/GridViews/GetRows.js @@ -2,7 +2,7 @@ define(["require", "exports", "WoltLabSuite/Core/Ajax/Backend", "../Result"], fu "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getRows = getRows; - async function getRows(gridViewClass, pageNo, sortField = "", sortOrder = "ASC", filters) { + async function getRows(gridViewClass, pageNo, sortField = "", sortOrder = "ASC", filters, gridViewParameters) { const url = new URL(`${window.WSC_RPC_API_URL}core/gridViews/rows`); url.searchParams.set("gridView", gridViewClass); url.searchParams.set("pageNo", pageNo.toString()); @@ -13,6 +13,11 @@ define(["require", "exports", "WoltLabSuite/Core/Ajax/Backend", "../Result"], fu url.searchParams.set(`filters[${key}]`, value); }); } + if (gridViewParameters) { + gridViewParameters.forEach((value, key) => { + url.searchParams.set(`gridViewParameters[${key}]`, value); + }); + } let response; try { response = (await (0, Backend_1.prepareRequest)(url).get().allowCaching().disableLoadingIndicator().fetchAsJson()); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView.js index 416ef22d0c..b2159fbc40 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView.js @@ -18,7 +18,8 @@ define(["require", "exports", "tslib", "../Api/GridViews/GetRows", "../Dom/Util" #defaultSortField; #defaultSortOrder; #filters; - constructor(gridId, gridClassName, pageNo, baseUrl = "", sortField = "", sortOrder = "ASC") { + #gridViewParameters; + constructor(gridId, gridClassName, pageNo, baseUrl = "", sortField = "", sortOrder = "ASC", gridViewParameters) { this.#gridClassName = gridClassName; this.#table = document.getElementById(`${gridId}_table`); this.#topPagination = document.getElementById(`${gridId}_topPagination`); @@ -31,6 +32,7 @@ define(["require", "exports", "tslib", "../Api/GridViews/GetRows", "../Dom/Util" this.#defaultSortField = sortField; this.#sortOrder = sortOrder; this.#defaultSortOrder = sortOrder; + this.#gridViewParameters = gridViewParameters; this.#initPagination(); this.#initSorting(); this.#initActions(); @@ -86,7 +88,7 @@ define(["require", "exports", "tslib", "../Api/GridViews/GetRows", "../Dom/Util" void this.#loadRows(updateQueryString); } async #loadRows(updateQueryString = true) { - const response = (await (0, GetRows_1.getRows)(this.#gridClassName, this.#pageNo, this.#sortField, this.#sortOrder, this.#filters)).unwrap(); + const response = (await (0, GetRows_1.getRows)(this.#gridClassName, this.#pageNo, this.#sortField, this.#sortOrder, this.#filters, this.#gridViewParameters)).unwrap(); Util_1.default.setInnerHtml(this.#table.querySelector("tbody"), response.template); this.#topPagination.count = response.pages; this.#bottomPagination.count = response.pages; @@ -109,9 +111,11 @@ define(["require", "exports", "tslib", "../Api/GridViews/GetRows", "../Dom/Util" parameters.push(["sortField", this.#sortField]); parameters.push(["sortOrder", this.#sortOrder]); } - this.#filters.forEach((value, key) => { - parameters.push([`filters[${key}]`, value]); - }); + if (this.#filters) { + this.#filters.forEach((value, key) => { + parameters.push([`filters[${key}]`, value]); + }); + } if (parameters.length > 0) { url.search += url.search !== "" ? "&" : "?"; url.search += new URLSearchParams(parameters).toString(); @@ -167,6 +171,9 @@ define(["require", "exports", "tslib", "../Api/GridViews/GetRows", "../Dom/Util" } } #renderFilters(labels) { + if (!this.#filterPills) { + return; + } this.#filterPills.innerHTML = ""; if (!this.#filters) { return; diff --git a/wcfsetup/install/files/lib/acp/page/ExceptionLogViewPage.class.php b/wcfsetup/install/files/lib/acp/page/ExceptionLogViewPage.class.php index 5495a24425..36005b821a 100644 --- a/wcfsetup/install/files/lib/acp/page/ExceptionLogViewPage.class.php +++ b/wcfsetup/install/files/lib/acp/page/ExceptionLogViewPage.class.php @@ -2,6 +2,7 @@ namespace wcf\acp\page; +use wcf\page\AbstractGridViewPage; use wcf\page\AbstractPage; use wcf\page\MultipleLinkPage; use wcf\system\event\EventHandler; @@ -9,6 +10,8 @@ use wcf\system\exception\IllegalLinkException; use wcf\system\Regex; use wcf\system\registry\RegistryHandler; use wcf\system\request\LinkHandler; +use wcf\system\view\grid\AbstractGridView; +use wcf\system\view\grid\ExceptionLogGridView; use wcf\system\WCF; use wcf\util\DirectoryUtil; use wcf\util\ExceptionLogUtil; @@ -21,7 +24,7 @@ use wcf\util\StringUtil; * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License */ -class ExceptionLogViewPage extends MultipleLinkPage +class ExceptionLogViewPage extends AbstractGridViewPage { /** * @inheritDoc @@ -33,43 +36,21 @@ class ExceptionLogViewPage extends MultipleLinkPage */ public $neededPermissions = ['admin.management.canViewLog']; - /** - * @inheritDoc - */ - public $itemsPerPage = 10; - - /** - * given exceptionID - * @var string - */ - public $exceptionID = ''; - /** * @inheritDoc */ public $forceCanonicalURL = true; - /** - * active logfile - * @var string - */ - public $logFile = ''; + public string $exceptionID = ''; + public string $logFile = ''; /** * available logfiles * @var string[] */ - public $logFiles = []; + public array $logFiles = []; - /** - * exceptions shown - * @var array - */ - public $exceptions = []; - - /** - * @inheritDoc - */ + #[\Override] public function readParameters() { parent::readParameters(); @@ -91,22 +72,91 @@ class ExceptionLogViewPage extends MultipleLinkPage $this->canonicalURL = LinkHandler::getInstance()->getControllerLink(self::class, $parameters); } - /** - * @inheritDoc - */ + #[\Override] public function readData() { - AbstractPage::readData(); + $this->markNotificationsAsRead(); + $this->readLogFiles(); + $this->validateParameters(); + + parent::readData(); + } - // mark notifications as read + private function markNotificationsAsRead(): void + { RegistryHandler::getInstance()->set('com.woltlab.wcf', 'exceptionMailerTimestamp', TIME_NOW); + } + private function readLogFiles(): void + { $fileNameRegex = new Regex('(?:^|/)\d{4}-\d{2}-\d{2}\.txt$'); $logFiles = DirectoryUtil::getInstance(WCF_DIR . 'log/', false)->getFiles(\SORT_DESC, $fileNameRegex); foreach ($logFiles as $logFile) { $pathname = WCF_DIR . 'log/' . $logFile; $this->logFiles[$pathname] = $pathname; } + } + + private function validateParameters(): void + { + $fileNameRegex = new Regex('(?:^|/)\d{4}-\d{2}-\d{2}\.txt$'); + if ($this->exceptionID) { + // search the appropriate file + foreach ($this->logFiles as $logFile) { + $contents = \file_get_contents($logFile); + + if (\str_contains($contents, '<<<<<<<<' . $this->exceptionID . '<<<<')) { + $fileNameRegex->match($logFile); + $matches = $fileNameRegex->getMatches(); + $this->logFile = $matches[0]; + break; + } + + unset($contents); + } + + if (!isset($contents)) { + $this->logFile = ''; + + return; + } + } elseif ($this->logFile) { + if (!$fileNameRegex->match(\basename($this->logFile))) { + throw new IllegalLinkException(); + } + if (!\file_exists(WCF_DIR . 'log/' . $this->logFile)) { + throw new IllegalLinkException(); + } + } + } + + #[\Override] + protected function createGridViewController(): AbstractGridView + { + return new ExceptionLogGridView($this->logFile, $this->exceptionID); + } + + #[\Override] + protected function initGridView(): void + { + parent::initGridView(); + + $parameters = []; + if ($this->exceptionID !== '') { + $parameters['exceptionID'] = $this->exceptionID; + } elseif ($this->logFile !== '') { + $parameters['logFile'] = $this->logFile; + } + + $this->gridView->setBaseUrl(LinkHandler::getInstance()->getControllerLink(static::class, $parameters)); + } + + /** + * @inheritDoc + */ + /*public function readData() + { + AbstractPage::readData(); if ($this->exceptionID) { // search the appropriate file @@ -176,25 +226,25 @@ class ExceptionLogViewPage extends MultipleLinkPage unset($this->exceptions[$key]); } } - } + }*/ /** * @inheritDoc */ - public function countItems() + /*public function countItems() { // call countItems event EventHandler::getInstance()->fireAction($this, 'countItems'); return \count($this->exceptions); - } + }*/ /** * Switches to the page containing the exception with the given ID. * * @param string $exceptionID */ - public function searchPage($exceptionID) + /*public function searchPage($exceptionID) { $i = 1; @@ -206,11 +256,9 @@ class ExceptionLogViewPage extends MultipleLinkPage } $this->pageNo = \ceil($i / $this->itemsPerPage); - } + }*/ - /** - * @inheritDoc - */ + #[\Override] public function assignVariables() { parent::assignVariables(); @@ -219,7 +267,6 @@ class ExceptionLogViewPage extends MultipleLinkPage 'exceptionID' => $this->exceptionID, 'logFiles' => \array_flip(\array_map('basename', $this->logFiles)), 'logFile' => $this->logFile, - 'exceptions' => $this->exceptions, ]); } } diff --git a/wcfsetup/install/files/lib/page/AbstractGridViewPage.class.php b/wcfsetup/install/files/lib/page/AbstractGridViewPage.class.php index 0164e432cb..fcc7029e47 100644 --- a/wcfsetup/install/files/lib/page/AbstractGridViewPage.class.php +++ b/wcfsetup/install/files/lib/page/AbstractGridViewPage.class.php @@ -64,7 +64,9 @@ abstract class AbstractGridViewPage extends AbstractPage if ($this->filters !== []) { $this->gridView->setActiveFilters($this->filters); } - $this->gridView->setPageNo($this->pageNo); + if ($this->pageNo != 1) { + $this->gridView->setPageNo($this->pageNo); + } $this->gridView->setBaseUrl(LinkHandler::getInstance()->getControllerLink(static::class)); } diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/gridViews/GetRows.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/gridViews/GetRows.class.php index 0aba1ecac1..f11b75f330 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/gridViews/GetRows.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/gridViews/GetRows.class.php @@ -24,7 +24,7 @@ final class GetRows implements IController throw new UserInputException('gridView', 'invalid'); } - $view = new $parameters->gridView(); + $view = new $parameters->gridView(...$parameters->gridViewParameters); \assert($view instanceof AbstractGridView); if (!$view->isAccessible()) { @@ -66,6 +66,8 @@ final class GetRowsParameters public readonly string $sortField, public readonly string $sortOrder, /** @var string[] */ - public readonly array $filters + public readonly array $filters, + /** @var string[] */ + public readonly array $gridViewParameters, ) {} } diff --git a/wcfsetup/install/files/lib/system/view/grid/ArrayGridView.class.php b/wcfsetup/install/files/lib/system/view/grid/ArrayGridView.class.php new file mode 100644 index 0000000000..58efe7400b --- /dev/null +++ b/wcfsetup/install/files/lib/system/view/grid/ArrayGridView.class.php @@ -0,0 +1,45 @@ +dataArray = $this->getDataArray(); + } + + public function getRows(): array + { + $this->sortRows(); + + return $this->getRowsForPage(); + } + + protected function sortRows(): void + { + \uasort($this->dataArray, function (array $a, array $b) { + if ($this->getSortOrder() === 'ASC') { + return \strcmp($a[$this->getSortField()], $b[$this->getSortField()]); + } else { + return \strcmp($b[$this->getSortField()], $a[$this->getSortField()]); + } + }); + } + + protected function getRowsForPage(): array + { + return \array_slice($this->dataArray, ($this->getPageNo() - 1) * $this->getRowsPerPage(), $this->getRowsPerPage()); + } + + public function countRows(): int + { + return \count($this->dataArray); + } + + protected abstract function getDataArray(): array; +} diff --git a/wcfsetup/install/files/lib/system/view/grid/DatabaseObjectListGridView.class.php b/wcfsetup/install/files/lib/system/view/grid/DatabaseObjectListGridView.class.php index d1ae67b3d9..486477c18d 100644 --- a/wcfsetup/install/files/lib/system/view/grid/DatabaseObjectListGridView.class.php +++ b/wcfsetup/install/files/lib/system/view/grid/DatabaseObjectListGridView.class.php @@ -71,5 +71,13 @@ abstract class DatabaseObjectListGridView extends AbstractGridView } } + #[\Override] + public function getObjectID(mixed $row): mixed + { + \assert($row instanceof DatabaseObject); + + return $row->getObjectID(); + } + protected abstract function createObjectList(): DatabaseObjectList; } diff --git a/wcfsetup/install/files/lib/system/view/grid/ExceptionLogGridView.class.php b/wcfsetup/install/files/lib/system/view/grid/ExceptionLogGridView.class.php new file mode 100644 index 0000000000..f2860ac5bb --- /dev/null +++ b/wcfsetup/install/files/lib/system/view/grid/ExceptionLogGridView.class.php @@ -0,0 +1,99 @@ +exceptionID) { + $this->sortRows(); + $this->jumpToException(); + } + } + + #[\Override] + protected function init(): void + { + $this->addColumns([ + GridViewColumn::for('message') + ->label('wcf.acp.exceptionLog.exception.message') + ->sortable() + ->renderer(new TitleColumnRenderer()), + GridViewColumn::for('exceptionID') + ->label('wcf.global.objectID') + ->sortable(), + GridViewColumn::for('date') + ->label('wcf.acp.exceptionLog.exception.date') + ->sortable() + ->renderer(new TimeColumnRenderer()), + ]); + + $this->addRowLink(new GridViewRowLink(cssClass: 'jsExceptionLogEntry')); + $this->setSortField('date'); + $this->setSortOrder('DESC'); + } + + #[\Override] + public function isAccessible(): bool + { + return WCF::getSession()->getPermission('admin.management.canViewLog'); + } + + private function jumpToException(): void + { + $i = 1; + foreach ($this->dataArray as $key => $val) { + if ($key == $this->exceptionID) { + break; + } + $i++; + } + + $this->setPageNo(\ceil($i / $this->getRowsPerPage())); + } + + #[\Override] + public function getParameters(): array + { + return ['logFile' => $this->logFile]; + } + + #[\Override] + public function getObjectID(mixed $row): mixed + { + return $row['exceptionID']; + } + + protected function getDataArray(): array + { + if (!$this->logFile) { + return []; + } + + $contents = \file_get_contents(WCF_DIR . 'log/' . $this->logFile); + $exceptions = ExceptionLogUtil::splitLog($contents); + $parsedExceptions = []; + + foreach ($exceptions as $key => $val) { + $parsed = ExceptionLogUtil::parseException($val); + + $parsedExceptions[$key] = [ + 'exceptionID' => $key, + 'message' => $parsed['message'], + 'date' => $parsed['date'], + ]; + } + + return $parsedExceptions; + } +} diff --git a/wcfsetup/install/files/lib/system/view/grid/GridViewColumn.class.php b/wcfsetup/install/files/lib/system/view/grid/GridViewColumn.class.php index d8c699a82a..cafa07d190 100644 --- a/wcfsetup/install/files/lib/system/view/grid/GridViewColumn.class.php +++ b/wcfsetup/install/files/lib/system/view/grid/GridViewColumn.class.php @@ -6,6 +6,7 @@ use wcf\system\form\builder\field\AbstractFormField; use wcf\system\view\grid\filter\IGridViewFilter; use wcf\system\view\grid\renderer\DefaultColumnRenderer; use wcf\system\view\grid\renderer\IColumnRenderer; +use wcf\system\view\grid\renderer\TitleColumnRenderer; use wcf\system\WCF; final class GridViewColumn @@ -146,4 +147,15 @@ final class GridViewColumn return self::$defaultRenderer; } + + public function isTitleColumn(): bool + { + foreach ($this->getRenderers() as $renderer) { + if ($renderer instanceof TitleColumnRenderer) { + return true; + } + } + + return false; + } }