From c9523a4e2e55b098c3e82b0f8e330f902521ebef Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Tue, 10 Dec 2024 16:33:37 +0100 Subject: [PATCH] Migrate modification log to grid view --- .../acp/templates/modificationLogList.tpl | 135 +------- .../page/ModificationLogListPage.class.php | 307 +----------------- ...dificationLogGridViewInitialized.class.php | 19 ++ .../ModificationLogGridView.class.php | 269 +++++++++++++++ wcfsetup/install/lang/de.xml | 6 - wcfsetup/install/lang/en.xml | 6 - 6 files changed, 300 insertions(+), 442 deletions(-) create mode 100644 wcfsetup/install/files/lib/event/gridView/ModificationLogGridViewInitialized.class.php create mode 100644 wcfsetup/install/files/lib/system/gridView/ModificationLogGridView.class.php diff --git a/wcfsetup/install/files/acp/templates/modificationLogList.tpl b/wcfsetup/install/files/acp/templates/modificationLogList.tpl index 55321fdf81..b139caf6eb 100644 --- a/wcfsetup/install/files/acp/templates/modificationLogList.tpl +++ b/wcfsetup/install/files/acp/templates/modificationLogList.tpl @@ -2,7 +2,7 @@
-

{lang}wcf.acp.modificationLog.list{/lang} {#$items}

+

{lang}wcf.acp.modificationLog.list{/lang} {#$gridView->countRows()}

{hascontent} @@ -14,135 +14,8 @@ {/hascontent}
-{if !$unsupportedObjectTypes|empty} - -

{lang}wcf.acp.modificationLog.unsupportedObjectTypes{/lang}

- -
-{/if} - -
-
-

{lang}wcf.global.filter{/lang}

- -
-
-
-
- -
-
- -
-
-
- -
-
- -
-
-
- -
-
- -
-
-
- -
-
- - {event name='filterFields'} -
- -
- - {csrfToken} -
-
-
- -{capture assign=pageParameters}{if $username}&username={$username}{/if}{if $action}&action={$action}{/if}{if $afterDate}&afterDate={$afterDate}{/if}{if $beforeDate}&beforeDate={$beforeDate}{/if}{/capture} -{hascontent} -
- {content}{pages print=true assign=pagesLinks controller="ModificationLogList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder$pageParameters"}{/content} -
-{/hascontent} - -{if $logItems|count} -
- - - - - - - - - - {event name='columnHeads'} - - - - - {foreach from=$logItems item=modificationLog} - {assign var=_objectType value=$objectTypes[$modificationLog->objectTypeID]} - - - - - - - - - {event name='columns'} - - {/foreach} - -
{lang}wcf.global.objectID{/lang}{lang}wcf.user.username{/lang}{lang}wcf.acp.modificationLog.action{/lang}{lang}wcf.acp.modificationLog.affectedObject{/lang}{lang}wcf.global.date{/lang}
{@$modificationLog->logID}{if $modificationLog->userID}{$modificationLog->username}{else}{$modificationLog->username}{/if}{lang}wcf.acp.modificationLog.{$_objectType->objectType}.{$modificationLog->action}{/lang} - {if $modificationLog->getAffectedObject()} - {$modificationLog->getAffectedObject()->getTitle()} - {else} - {lang}wcf.acp.modificationLog.affectedObject.unknown{/lang} - {/if} - {@$modificationLog->time|time}
-
- - -{else} - {lang}wcf.global.noItems{/lang} -{/if} +
+ {unsafe:$gridView->render()} +
{include file='footer'} diff --git a/wcfsetup/install/files/lib/acp/page/ModificationLogListPage.class.php b/wcfsetup/install/files/lib/acp/page/ModificationLogListPage.class.php index d9ec34631c..4f6bf9e6ba 100644 --- a/wcfsetup/install/files/lib/acp/page/ModificationLogListPage.class.php +++ b/wcfsetup/install/files/lib/acp/page/ModificationLogListPage.class.php @@ -2,16 +2,9 @@ namespace wcf\acp\page; -use wcf\data\DatabaseObject; -use wcf\data\modification\log\IViewableModificationLog; -use wcf\data\modification\log\ModificationLogList; -use wcf\data\object\type\ObjectType; -use wcf\data\object\type\ObjectTypeCache; -use wcf\data\package\Package; -use wcf\page\SortablePage; -use wcf\system\log\modification\IExtendedModificationLogHandler; -use wcf\system\WCF; -use wcf\util\StringUtil; +use wcf\page\AbstractGridViewPage; +use wcf\system\gridView\AbstractGridView; +use wcf\system\gridView\ModificationLogGridView; /** * Shows a list of modification log items. @@ -20,308 +13,24 @@ use wcf\util\StringUtil; * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License * - * @property ModificationLogList $objectList + * @property ModificationLogGridView $gridView * @since 5.2 */ -class ModificationLogListPage extends SortablePage +class ModificationLogListPage extends AbstractGridViewPage { - /** - * filter by action - * - * @var string - */ - public $action = ''; - - /** - * list of available actions per package - * - * @var string[][] - */ - public $actions = []; - /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.log.modification'; - /** - * filter by time - * - * @var string - */ - public $afterDate = ''; - - /** - * @var int[] - */ - public $availableObjectTypeIDs = []; - - /** - * filter by time - * - * @var string - */ - public $beforeDate = ''; - - /** - * @inheritDoc - */ - public $defaultSortField = 'time'; - - /** - * @inheritDoc - */ - public $defaultSortOrder = 'DESC'; - - /** - * @inheritDoc - */ - public $objectListClassName = ModificationLogList::class; - - /** - * @var IViewableModificationLog[] - */ - public $logItems = []; - /** * @inheritDoc */ public $neededPermissions = ['admin.management.canViewLog']; - /** - * @var ObjectType[] - */ - public $objectTypes = []; - - /** - * @var Package[] - */ - public $packages = []; - - /** - * filter by package id - * - * @var int - */ - public $packageID = 0; - - /** - * list of object types that are not implementing the new API - * - * @var ObjectType - */ - public $unsupportedObjectTypes = []; - - /** - * filter by username - * - * @var string - */ - public $username = ''; - - /** - * @inheritDoc - */ - public $validSortFields = [ - 'logID', - 'username', - 'time', - ]; - - /** - * @inheritDoc - */ - public function readParameters() - { - parent::readParameters(); - - $this->initObjectTypes(); - - if (!empty($_REQUEST['action'])) { - $this->action = StringUtil::trim($_REQUEST['action']); - } - if (!empty($_REQUEST['afterDate'])) { - $this->afterDate = StringUtil::trim($_REQUEST['afterDate']); - } - if (!empty($_REQUEST['beforeDate'])) { - $this->beforeDate = StringUtil::trim($_REQUEST['beforeDate']); - } - if (!empty($_REQUEST['username'])) { - $this->username = StringUtil::trim($_REQUEST['username']); - } - } - - protected function initObjectTypes() - { - foreach (ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.modifiableContent') as $objectType) { - $this->objectTypes[$objectType->objectTypeID] = $objectType; - - /** @var IExtendedModificationLogHandler $processor */ - $processor = $objectType->getProcessor(); - if ($processor === null) { - $this->unsupportedObjectTypes[] = $objectType; - } elseif ($processor->includeInLogList()) { - $this->availableObjectTypeIDs[] = $objectType->objectTypeID; - if (!isset($this->packages[$objectType->packageID])) { - $this->actions[$objectType->packageID] = []; - $this->packages[$objectType->packageID] = $objectType->getPackage(); - } - - foreach ($processor->getAvailableActions() as $action) { - $this->actions[$objectType->packageID]["{$objectType->objectType}-{$action}"] = WCF::getLanguage()->get("wcf.acp.modificationLog.{$objectType->objectType}.{$action}"); - } - } - } - - foreach ($this->actions as &$actions) { - \asort($actions, \SORT_NATURAL); - } - unset($actions); - - \uasort($this->packages, static function (Package $a, Package $b) { - return \strnatcasecmp($a->package, $b->package); - }); - } - - /** - * @inheritDoc - */ - public function initObjectList() - { - parent::initObjectList(); - - if (!empty($this->availableObjectTypeIDs)) { - $action = ''; - $objectTypeID = 0; - $packageID = 0; - - // an integer signals all actions from the package with the relevant id - if (\preg_match('/^[0-9]+$/', $this->action)) { - $packageID = $this->action; - } elseif (\preg_match('~^(?P.+)\-(?P[^\-]+)$~', $this->action, $matches)) { - foreach ($this->objectTypes as $objectType) { - if ($objectType->objectType === $matches['objectType']) { - /** @var IExtendedModificationLogHandler $processor */ - $processor = $objectType->getProcessor(); - if ($processor !== null && \in_array($matches['action'], $processor->getAvailableActions())) { - $action = $matches['action']; - $objectTypeID = $objectType->objectTypeID; - } - - break; - } - } - } - - if ($objectTypeID) { - $this->objectList->getConditionBuilder()->add('modification_log.objectTypeID = ?', [$objectTypeID]); - $this->objectList->getConditionBuilder()->add('modification_log.action = ?', [$action]); - } else { - if (isset($this->packages[$packageID])) { - $objectTypeIDs = []; - foreach ($this->objectTypes as $objectType) { - if ($objectType->packageID == $packageID) { - $objectTypeIDs[] = $objectType->objectTypeID; - } - } - - $this->objectList->getConditionBuilder()->add( - 'modification_log.objectTypeID IN (?)', - [$objectTypeIDs] - ); - } else { - $this->objectList->getConditionBuilder()->add( - 'modification_log.objectTypeID IN (?)', - [$this->availableObjectTypeIDs] - ); - } - } - - if (!empty($this->username)) { - $this->objectList->getConditionBuilder()->add( - 'modification_log.username LIKE ?', - [\addcslashes($this->username, '%') . '%'] - ); - } - - $afterDate = $beforeDate = 0; - if (!empty($this->afterDate)) { - $afterDate = \intval(@\strtotime($this->afterDate)); - } - if (!empty($this->beforeDate)) { - $beforeDate = \intval(@\strtotime($this->beforeDate)); - } - - if ($afterDate && $beforeDate) { - $this->objectList->getConditionBuilder()->add('modification_log.time BETWEEN ? AND ?', [ - $afterDate, - $beforeDate, - ]); - } else { - if ($afterDate) { - $this->objectList->getConditionBuilder()->add('modification_log.time > ?', [$afterDate]); - } elseif ($beforeDate) { - $this->objectList->getConditionBuilder()->add('modification_log.time < ?', [$beforeDate]); - } - } - } else { - $this->objectList->getConditionBuilder()->add('1=0'); - } - } - - /** - * @inheritDoc - */ - public function readData() + #[\Override] + protected function createGridViewController(): AbstractGridView { - parent::readData(); - - $itemsPerType = []; - foreach ($this->objectList as $modificationLog) { - if (!isset($itemsPerType[$modificationLog->objectTypeID])) { - $itemsPerType[$modificationLog->objectTypeID] = []; - } - - $itemsPerType[$modificationLog->objectTypeID][] = $modificationLog; - } - - if (!empty($itemsPerType)) { - foreach ($this->objectTypes as $objectType) { - /** @var IExtendedModificationLogHandler $processor */ - $processor = $objectType->getProcessor(); - if ($processor === null) { - continue; - } - - if (isset($itemsPerType[$objectType->objectTypeID])) { - $this->logItems = \array_merge( - $this->logItems, - $processor->processItems($itemsPerType[$objectType->objectTypeID]) - ); - } - } - } - - DatabaseObject::sort($this->logItems, $this->sortField, $this->sortOrder); - } - - /** - * @inheritDoc - */ - public function assignVariables() - { - parent::assignVariables(); - - WCF::getTPL()->assign([ - 'action' => $this->action, - 'actions' => $this->actions, - 'afterDate' => $this->afterDate, - 'beforeDate' => $this->beforeDate, - 'logItems' => $this->logItems, - 'objectTypes' => $this->objectTypes, - 'packages' => $this->packages, - 'unsupportedObjectTypes' => $this->unsupportedObjectTypes, - 'username' => $this->username, - ]); + return new ModificationLogGridView(); } } diff --git a/wcfsetup/install/files/lib/event/gridView/ModificationLogGridViewInitialized.class.php b/wcfsetup/install/files/lib/event/gridView/ModificationLogGridViewInitialized.class.php new file mode 100644 index 0000000000..5333140766 --- /dev/null +++ b/wcfsetup/install/files/lib/event/gridView/ModificationLogGridViewInitialized.class.php @@ -0,0 +1,19 @@ + + * @since 6.2 + */ +final class ModificationLogGridViewInitialized implements IPsr14Event +{ + public function __construct(public readonly ModificationLogGridView $gridView) {} +} diff --git a/wcfsetup/install/files/lib/system/gridView/ModificationLogGridView.class.php b/wcfsetup/install/files/lib/system/gridView/ModificationLogGridView.class.php new file mode 100644 index 0000000000..82e1227c58 --- /dev/null +++ b/wcfsetup/install/files/lib/system/gridView/ModificationLogGridView.class.php @@ -0,0 +1,269 @@ + + * @since 6.2 + */ +final class ModificationLogGridView extends DatabaseObjectListGridView +{ + /** + * @var IViewableModificationLog[] + */ + private array $logItems; + + public function __construct() + { + $this->addColumns([ + GridViewColumn::for('logID') + ->label('wcf.global.objectID') + ->renderer(new ObjectIdColumnRenderer()) + ->sortable(), + GridViewColumn::for('userID') + ->label('wcf.user.username') + ->sortable() + ->sortById('username') + ->renderer(new UserLinkColumnRenderer()) + ->filter(new TextFilter('username')), + GridViewColumn::for('action') + ->label('wcf.acp.modificationLog.action') + ->renderer([ + new class extends DefaultColumnRenderer { + #[\Override] + public function render(mixed $value, mixed $context = null): string + { + \assert($context instanceof DatabaseObjectDecorator); + $log = $context->getDecoratedObject(); + \assert($log instanceof ModificationLog); + $objectType = ObjectTypeCache::getInstance()->getObjectType($log->objectTypeID); + if (!$objectType) { + return ''; + } + + return WCF::getLanguage()->get( + 'wcf.acp.modificationLog.' . $objectType->objectType . '.' . $log->action + ); + } + }, + ]) + ->filter( + new class($this->getAvailableActions()) implements IGridViewFilter { + public function __construct(private readonly array $options) {} + + #[\Override] + public function getFormField(string $id, string $label): AbstractFormField + { + return SelectFormField::create($id) + ->label($label) + ->options($this->options, false, false); + } + + #[\Override] + public function applyFilter(DatabaseObjectList $list, string $id, string $value): void + { + if (\is_numeric($value)) { + $list->getConditionBuilder()->add( + "objectTypeID IN (SELECT objectTypeID FROM wcf1_object_type WHERE packageID = ?)", + [$value] + ); + } else if (\preg_match('~^(?P.+)\-(?P[^\-]+)$~', $value, $matches)) { + $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName( + 'com.woltlab.wcf.modifiableContent', + $matches['objectType'] + ); + if (!$objectType) { + return; + } + + $list->getConditionBuilder()->add( + "objectTypeID = ? AND action = ?", + [$objectType->objectTypeID, $matches['action']] + ); + } + } + + #[\Override] + public function matches(string $filterValue, string $rowValue): bool + { + throw new LogicException('unreachable'); + } + + #[\Override] + public function renderValue(string $value): string + { + if (\is_numeric($value)) { + return WCF::getLanguage()->get($this->options[$value]); + } + + return \substr(WCF::getLanguage()->get($this->options[$value]), 24); + } + } + ), + GridViewColumn::for('affectedObject') + ->label('wcf.acp.modificationLog.affectedObject') + ->renderer([ + new class extends DefaultColumnRenderer implements ILinkColumnRenderer { + #[\Override] + public function render(mixed $value, mixed $context = null): string + { + \assert($context instanceof IViewableModificationLog); + if ($context->getAffectedObject() === null) { + return WCF::getLanguage()->get('wcf.acp.modificationLog.affectedObject.unknown'); + } + + return \sprintf( + '%s', + StringUtil::encodeHTML($context->getAffectedObject()->getLink()), + StringUtil::encodeHTML($context->getAffectedObject()->getTitle()) + ); + } + }, + ]), + GridViewColumn::for('time') + ->label('wcf.global.date') + ->sortable() + ->renderer(new TimeColumnRenderer()) + ->filter(new TimeFilter()), + ]); + + $this->setSortField('time'); + $this->setSortOrder('DESC'); + } + + #[\Override] + public function isAccessible(): bool + { + return WCF::getSession()->getPermission('admin.management.canViewLog'); + } + + #[\Override] + protected function createObjectList(): DatabaseObjectList + { + return new ModificationLogList(); + } + + #[\Override] + public function getRows(): array + { + if (!isset($this->logItems)) { + $this->logItems = []; + $this->getObjectList()->readObjects(); + + $itemsPerType = []; + foreach ($this->getObjectList() as $modificationLog) { + if (!isset($itemsPerType[$modificationLog->objectTypeID])) { + $itemsPerType[$modificationLog->objectTypeID] = []; + } + + $itemsPerType[$modificationLog->objectTypeID][] = $modificationLog; + } + + if (!empty($itemsPerType)) { + foreach ($itemsPerType as $objectTypeID => $items) { + $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID); + if (!$objectType) { + continue; + } + $processor = $objectType->getProcessor(); + if (!$processor) { + continue; + } + \assert($processor instanceof IExtendedModificationLogHandler); + + $this->logItems = \array_merge( + $this->logItems, + $processor->processItems($items) + ); + } + } + + DatabaseObject::sort($this->logItems, $this->getSortField(), $this->getSortOrder()); + } + + return $this->logItems; + } + + #[\Override] + protected function getInitializedEvent(): ?IPsr14Event + { + return new ModificationLogGridViewInitialized($this); + } + + private function getAvailableActions(): array + { + $packages = $actions = $availableActions = []; + + foreach (ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.modifiableContent') as $objectType) { + $processor = $objectType->getProcessor(); + if ($processor === null) { + continue; + } + \assert($processor instanceof IExtendedModificationLogHandler); + + if (!$processor->includeInLogList()) { + continue; + } + + if (!isset($packages[$objectType->packageID])) { + $actions[$objectType->packageID] = []; + $packages[$objectType->packageID] = $objectType->getPackage(); + } + + foreach ($processor->getAvailableActions() as $action) { + $actions[$objectType->packageID]["{$objectType->objectType}-{$action}"] + = WCF::getLanguage()->get("wcf.acp.modificationLog.{$objectType->objectType}.{$action}"); + } + } + + foreach ($actions as &$actionsPerPackage) { + \asort($actionsPerPackage, \SORT_NATURAL); + } + \uasort($packages, static function (Package $a, Package $b) { + return \strnatcasecmp($a->package, $b->package); + }); + + foreach ($packages as $package) { + $availableActions[$package->packageID] + = WCF::getLanguage()->getDynamicVariable( + 'wcf.acp.modificationLog.action.allPackageActions', + ['package' => $package] + ); + + foreach ($actions[$package->packageID] as $actionName => $actionLabel) { + $availableActions[$actionName] = \str_repeat(' ', 4) . $actionLabel; + } + } + + return $availableActions; + } +} diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index 19a6b14cac..f50f9e75f3 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -1254,14 +1254,8 @@ Die Entwickler-Lizenz gestattet ausschließlich den Einsatz während der Entwick - - objectID}]]> - - - - getName()}“]]> diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index c32f92617a..54dfc2bae3 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -1232,14 +1232,8 @@ The developer license permits exclusively the use during the development as well - - objectID}]]> - - - - getName()}”]]> -- 2.20.1