<textarea rows="5" cols="40" class="jsCopyException" hidden>{$exception[0]}</textarea>
</dd>
</dl>
+<dl>
+ <dt>{lang}wcf.acp.exceptionLog.search.exceptionID{/lang}</dt>
+ <dd>{$exceptionID}</dd>
+</dl>
<dl>
<dt>{lang}wcf.acp.exceptionLog.exception.date{/lang}</dt>
<dd>{$exception[date]|plainTime}</dd>
{/hascontent}
</header>
-{include file='shared_formError'}
-
-{if !$logFiles|empty}
- <form method="post" action="{link controller='ExceptionLogView'}{/link}">
- <section class="section">
- <h2 class="sectionTitle">{lang}wcf.acp.exceptionLog.search{/lang}</h2>
-
- <div class="row rowColGap formGrid">
- <dl class="col-xs-12 col-md-4">
- <dt></dt>
- <dd>
- <input type="text" id="exceptionID" name="exceptionID" value="{$exceptionID}" placeholder="{lang}wcf.acp.exceptionLog.search.exceptionID{/lang}" autofocus class="long">
- </dd>
- </dl>
-
- <dl class="col-xs-12 col-md-4">
- <dt></dt>
- <dd>
- <select id="logFile" name="logFile">
- <option value="">{lang}wcf.acp.exceptionLog.search.logFile{/lang}</option>
- {htmlOptions options=$logFiles selected=$logFile}
- </select>
- </dd>
- </dl>
- </div>
- </section>
-
- <div class="formSubmit">
- <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
- </div>
- </form>
-{/if}
-
{unsafe:$gridView->render()}
<script data-relocate="true">
namespace wcf\acp\page;
use wcf\page\AbstractGridViewPage;
-use wcf\page\AbstractPage;
-use wcf\page\MultipleLinkPage;
-use wcf\system\event\EventHandler;
-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;
-use wcf\util\StringUtil;
/**
* Shows the exception log.
*/
public $neededPermissions = ['admin.management.canViewLog'];
- /**
- * @inheritDoc
- */
- public $forceCanonicalURL = true;
-
- public string $exceptionID = '';
- public string $logFile = '';
-
- /**
- * available logfiles
- * @var string[]
- */
- public array $logFiles = [];
-
- #[\Override]
- public function readParameters()
- {
- parent::readParameters();
-
- if (isset($_REQUEST['exceptionID'])) {
- $this->exceptionID = StringUtil::trim($_REQUEST['exceptionID']);
- }
- if (isset($_REQUEST['logFile'])) {
- $this->logFile = StringUtil::trim($_REQUEST['logFile']);
- }
-
- $parameters = [];
- if ($this->exceptionID !== '') {
- $parameters['exceptionID'] = $this->exceptionID;
- } elseif ($this->logFile !== '') {
- $parameters['logFile'] = $this->logFile;
- }
-
- $this->canonicalURL = LinkHandler::getInstance()->getControllerLink(self::class, $parameters);
- }
-
#[\Override]
public function readData()
{
$this->markNotificationsAsRead();
- $this->readLogFiles();
- $this->validateParameters();
parent::readData();
}
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
- 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();
- }
-
- $contents = \file_get_contents(WCF_DIR . 'log/' . $this->logFile);
- } else {
- return;
- }
-
- try {
- $this->exceptions = ExceptionLogUtil::splitLog($contents);
- } catch (\Exception $e) {
- return;
- }
-
- // show latest exceptions first
- $this->exceptions = \array_reverse($this->exceptions, true);
-
- if ($this->exceptionID) {
- $this->searchPage($this->exceptionID);
- }
- $this->calculateNumberOfPages();
-
- $i = 0;
- $seenHashes = [];
- foreach ($this->exceptions as $key => $val) {
- $i++;
-
- $parsed = ExceptionLogUtil::parseException($val);
- if (isset($seenHashes[$parsed['stackHash']])) {
- $parsed['collapsed'] = true;
- }
- $seenHashes[$parsed['stackHash']] = true;
-
- if ($i < $this->startIndex || $i > $this->endIndex) {
- unset($this->exceptions[$key]);
- continue;
- }
- try {
- $this->exceptions[$key] = $parsed;
- } catch (\InvalidArgumentException $e) {
- unset($this->exceptions[$key]);
- }
- }
- }*/
-
- /**
- * @inheritDoc
- */
- /*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)
- {
- $i = 1;
-
- foreach ($this->exceptions as $key => $val) {
- if ($key == $exceptionID) {
- break;
- }
- $i++;
- }
-
- $this->pageNo = \ceil($i / $this->itemsPerPage);
- }*/
-
- #[\Override]
- public function assignVariables()
- {
- parent::assignVariables();
-
- WCF::getTPL()->assign([
- 'exceptionID' => $this->exceptionID,
- 'logFiles' => \array_flip(\array_map('basename', $this->logFiles)),
- 'logFile' => $this->logFile,
- ]);
+ return new ExceptionLogGridView(true);
}
}
abstract class ArrayGridView extends AbstractGridView
{
- protected array $dataArray = [];
-
- public function __construct()
- {
- parent::__construct();
-
- $this->dataArray = $this->getDataArray();
- }
+ protected array $dataArray;
public function getRows(): array
{
protected function sortRows(): void
{
+ $this->getDataArray();
+
\uasort($this->dataArray, function (array $a, array $b) {
if ($this->getSortOrder() === 'ASC') {
return \strcmp($a[$this->getSortField()], $b[$this->getSortField()]);
protected function getRowsForPage(): array
{
- return \array_slice($this->dataArray, ($this->getPageNo() - 1) * $this->getRowsPerPage(), $this->getRowsPerPage());
+ return \array_slice($this->getDataArray(), ($this->getPageNo() - 1) * $this->getRowsPerPage(), $this->getRowsPerPage());
}
public function countRows(): int
{
- return \count($this->dataArray);
+ return \count($this->getDataArray());
+ }
+
+ protected function getDataArray(): array
+ {
+ if (!isset($this->dataArray)) {
+ $this->dataArray = $this->loadDataArray();
+ }
+
+ return $this->dataArray;
}
- protected abstract function getDataArray(): array;
+ protected abstract function loadDataArray(): array;
}
namespace wcf\system\view\grid;
+use wcf\system\Regex;
+use wcf\system\view\grid\filter\SelectFilter;
+use wcf\system\view\grid\filter\TextFilter;
use wcf\system\view\grid\renderer\TimeColumnRenderer;
use wcf\system\view\grid\renderer\TitleColumnRenderer;
use wcf\system\WCF;
+use wcf\util\DirectoryUtil;
use wcf\util\ExceptionLogUtil;
final class ExceptionLogGridView extends ArrayGridView
{
- public function __construct(
- private readonly string $logFile,
- private readonly string $exceptionID = ''
- ) {
+ private array $availableLogFiles;
+
+ public function __construct(bool $applyDefaultFilter = false)
+ {
parent::__construct();
- if ($this->exceptionID) {
- $this->sortRows();
- $this->jumpToException();
+ if ($applyDefaultFilter && $this->getDefaultLogFile() !== null) {
+ $this->setActiveFilters([
+ 'logFile' => $this->getDefaultLogFile(),
+ ]);
}
}
->sortable()
->renderer(new TitleColumnRenderer()),
GridViewColumn::for('exceptionID')
- ->label('wcf.global.objectID')
+ ->label('wcf.acp.exceptionLog.search.exceptionID')
+ ->filter(new TextFilter())
->sortable(),
GridViewColumn::for('date')
->label('wcf.acp.exceptionLog.exception.date')
->sortable()
->renderer(new TimeColumnRenderer()),
+ GridViewColumn::for('logFile')
+ ->label('wcf.acp.exceptionLog.search.logFile')
+ ->filter(new SelectFilter($this->getAvailableLogFiles()))
+ ->hidden(true),
]);
$this->addRowLink(new GridViewRowLink(cssClass: 'jsExceptionLogEntry'));
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
+ protected function loadDataArray(): array
{
- if (!$this->logFile) {
- return [];
- }
+ if (!empty($this->getActiveFilters()['exceptionID'])) {
+ $exceptionID = $this->getActiveFilters()['exceptionID'];
+ $contents = $logFile = '';
+ foreach ($this->getAvailableLogFiles() as $logFile) {
+ $contents = \file_get_contents(WCF_DIR . $logFile);
+
+ if (\str_contains($contents, '<<<<<<<<' . $exceptionID . '<<<<')) {
+ break;
+ }
+
+ unset($contents);
+ }
- $contents = \file_get_contents(WCF_DIR . 'log/' . $this->logFile);
- $exceptions = ExceptionLogUtil::splitLog($contents);
- $parsedExceptions = [];
+ if ($contents === '') {
+ return [];
+ }
+
+ $exceptions = ExceptionLogUtil::splitLog($contents);
+ $parsedExceptions = [];
+
+ foreach ($exceptions as $key => $val) {
+ if ($key !== $exceptionID) {
+ continue;
+ }
+
+ $parsed = ExceptionLogUtil::parseException($val);
+
+ $parsedExceptions[$key] = [
+ 'exceptionID' => $key,
+ 'message' => $parsed['message'],
+ 'date' => $parsed['date'],
+ 'logFile' => $logFile,
+ ];
+ }
+
+ return $parsedExceptions;
+ } elseif (!empty($this->getActiveFilters()['logFile'])) {
+ $contents = \file_get_contents(WCF_DIR . $this->getActiveFilters()['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'],
+ 'logFile' => $this->getActiveFilters()['logFile'],
+ ];
+ }
- foreach ($exceptions as $key => $val) {
- $parsed = ExceptionLogUtil::parseException($val);
+ return $parsedExceptions;
+ }
+
+ return [];
+ }
- $parsedExceptions[$key] = [
- 'exceptionID' => $key,
- 'message' => $parsed['message'],
- 'date' => $parsed['date'],
- ];
+ private function getAvailableLogFiles(): array
+ {
+ if (!isset($this->availableLogFiles)) {
+ $this->availableLogFiles = [];
+ $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) {
+ $this->availableLogFiles['log/' . $logFile] = 'log/' . $logFile;
+ }
}
- return $parsedExceptions;
+ return $this->availableLogFiles;
+ }
+
+ private function getDefaultLogFile(): ?string
+ {
+ return \array_key_first($this->getAvailableLogFiles());
}
}