Use grid view filters for exception log filtering
authorMarcel Werk <burntime@woltlab.com>
Tue, 5 Nov 2024 14:15:48 +0000 (15:15 +0100)
committerMarcel Werk <burntime@woltlab.com>
Tue, 12 Nov 2024 11:53:43 +0000 (12:53 +0100)
com.woltlab.wcf/templates/shared_exceptionLogDetails.tpl
wcfsetup/install/files/acp/templates/exceptionLogView.tpl
wcfsetup/install/files/lib/acp/page/ExceptionLogViewPage.class.php
wcfsetup/install/files/lib/system/view/grid/ArrayGridView.class.php
wcfsetup/install/files/lib/system/view/grid/ExceptionLogGridView.class.php

index 0f8df80d2d65a241127c3748f27510c63f5f61ed..2e87d96359d7a97085d8507bf004f273412e5f46 100644 (file)
@@ -5,6 +5,10 @@
                <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>
index 88c1702b725b5cb7e25954d086e5a3c6b40919ff..b87769500779bc0f53801947bf95ca94ab60260c 100644 (file)
        {/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">
index 36005b821af517527dd5bf61689c7037a8f4d8a3..8f3dc20cc588ac3632ab0cced38c62ccdfa6e7ea 100644 (file)
@@ -3,19 +3,9 @@
 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.
@@ -36,48 +26,10 @@ class ExceptionLogViewPage extends AbstractGridViewPage
      */
     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();
     }
@@ -87,186 +39,9 @@ class ExceptionLogViewPage extends AbstractGridViewPage
         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);
     }
 }
index 58efe7400b1b9c5d8a3a56d56929b1c9032dee52..b3c648069e33ef4b53c44460ca84a3414d76b994 100644 (file)
@@ -4,14 +4,7 @@ namespace wcf\system\view\grid;
 
 abstract class ArrayGridView extends AbstractGridView
 {
-    protected array $dataArray = [];
-
-    public function __construct()
-    {
-        parent::__construct();
-
-        $this->dataArray = $this->getDataArray();
-    }
+    protected array $dataArray;
 
     public function getRows(): array
     {
@@ -22,6 +15,8 @@ abstract class ArrayGridView extends AbstractGridView
 
     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()]);
@@ -33,13 +28,22 @@ abstract class ArrayGridView extends AbstractGridView
 
     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;
 }
index f2860ac5bb28309317929451438b18a8352c1460..10b041d4c533f81528513e18d9514aabe4f8ed62 100644 (file)
@@ -2,22 +2,27 @@
 
 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(),
+            ]);
         }
     }
 
@@ -30,12 +35,17 @@ final class ExceptionLogGridView extends ArrayGridView
                 ->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'));
@@ -49,51 +59,88 @@ final class ExceptionLogGridView extends ArrayGridView
         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());
     }
 }