From: Alexander Ebert Date: Thu, 12 Jul 2018 09:42:49 +0000 (+0200) Subject: Global modification log list X-Git-Tag: 5.2.0_Alpha_1~364^2~126 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=00b759ae8b32ffa69b240efdd77dd9af8226337b;p=GitHub%2FWoltLab%2FWCF.git Global modification log list See #2597 --- diff --git a/com.woltlab.wcf/acpMenu.xml b/com.woltlab.wcf/acpMenu.xml index 027a420f03..c02b2c9715 100644 --- a/com.woltlab.wcf/acpMenu.xml +++ b/com.woltlab.wcf/acpMenu.xml @@ -820,6 +820,12 @@ admin.management.canViewLog enable_user_authentication_failure + + + wcf\acp\page\ModificationLogListPage + wcf.acp.menu.link.log + admin.management.canViewLog + diff --git a/com.woltlab.wcf/objectTypeDefinition.xml b/com.woltlab.wcf/objectTypeDefinition.xml index 61a6f0227b..59cb5051d4 100644 --- a/com.woltlab.wcf/objectTypeDefinition.xml +++ b/com.woltlab.wcf/objectTypeDefinition.xml @@ -24,6 +24,7 @@ com.woltlab.wcf.modifiableContent + wcf\system\log\modification\IExtendedModificationLogHandler diff --git a/wcfsetup/install/files/acp/templates/modificationLogList.tpl b/wcfsetup/install/files/acp/templates/modificationLogList.tpl new file mode 100644 index 0000000000..9360f8b6d1 --- /dev/null +++ b/wcfsetup/install/files/acp/templates/modificationLogList.tpl @@ -0,0 +1,161 @@ +{include file='header' pageTitle='wcf.acp.modificationLog.list'} + +
+
+

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

+
+ + {hascontent} + + {/hascontent} +
+ +{if !$unsupportedObjectTypes|empty} +
+

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

+
    + {foreach from=$unsupportedObjectTypes item=unsupportedObjectType} +
  • {$unsupportedObjectType->objectType} ({$unsupportedObjectType->getPackage()})
  • + {/foreach} +
+
+{/if} + +
+
+

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

+ +
+
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+ + {event name='filterFields'} +
+ +
+ + {@SECURITY_TOKEN_INPUT_TAG} +
+
+
+ +{capture assign=pageParameters}{if $username}&username={$username}{/if}{if $packageID}&packageID={@$packageID}{/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}
+
+ +
+ {hascontent} +
+ {content}{@$pagesLinks}{/content} +
+ {/hascontent} + + {hascontent} + + {/hascontent} +
+{else} +

{lang}wcf.global.noItems{/lang}

+{/if} + +{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 new file mode 100644 index 0000000000..82555b92c5 --- /dev/null +++ b/wcfsetup/install/files/lib/acp/page/ModificationLogListPage.class.php @@ -0,0 +1,316 @@ + + * @package WoltLabSuite\Core\Acp\Page + * + * @property ModificationLogList $objectList + * @since 3.2 + */ +class ModificationLogListPage extends SortablePage { + /** + * 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['packageID'])) { + $this->packageID = intval($_REQUEST['packageID']); + } + if (!empty($_REQUEST['username'])) { + $this->username = StringUtil::trim($_REQUEST['username']); + } + } + + protected function initObjectTypes() { + foreach (ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.modifiableContent') as $objectType) { + /** @noinspection PhpUndefinedFieldInspection */ + if ($objectType->excludeFromLogList) { + continue; + } + + $this->objectTypes[$objectType->objectTypeID] = $objectType; + + /** @var IExtendedModificationLogHandler $processor */ + $processor = $objectType->getProcessor(); + if ($processor === null) { + $this->unsupportedObjectTypes[] = $objectType; + } + else { + $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, function (Package $a, Package $b) { + return strnatcasecmp($a->package, $b->package); + }); + } + + /** + * @inheritDoc + */ + public function initObjectList() { + parent::initObjectList(); + + if (!empty($this->availableObjectTypeIDs)) { + $action = ''; + $objectTypeID = 0; + if (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[$this->packageID])) { + $objectTypeIDs = []; + foreach ($this->objectTypes as $objectType) { + if ($objectType->packageID == $this->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]); + } + else if ($beforeDate) { + $this->objectList->getConditionBuilder()->add('modification_log.time < ?', [$beforeDate]); + } + } + } + else { + $this->objectList->getConditionBuilder()->add('1=0'); + } + } + + /** + * @inheritDoc + */ + public function readData() { + 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])); + } + } + } + } + + /** + * @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, + 'packageID' => $this->packageID, + 'packages' => $this->packages, + 'unsupportedObjectTypes' => $this->unsupportedObjectTypes, + 'username' => $this->username, + ]); + } +} diff --git a/wcfsetup/install/files/lib/data/modification/log/IViewableModificationLog.class.php b/wcfsetup/install/files/lib/data/modification/log/IViewableModificationLog.class.php new file mode 100644 index 0000000000..3df40b5901 --- /dev/null +++ b/wcfsetup/install/files/lib/data/modification/log/IViewableModificationLog.class.php @@ -0,0 +1,25 @@ + + * @package WoltLabSuite\Core\System\Log\Modification + * @since 3.2 + */ +interface IViewableModificationLog { + /** + * Returns the title of the affected object. If the object does not exist + * anymore, this method should return an empty string instead. (nullable + * requires PHP 7.1) + * + * @return ITitledLinkObject|null + */ + public function getAffectedObject(); +} diff --git a/wcfsetup/install/files/lib/data/object/type/ObjectType.class.php b/wcfsetup/install/files/lib/data/object/type/ObjectType.class.php index 86cfe732b3..dd3e5038ab 100644 --- a/wcfsetup/install/files/lib/data/object/type/ObjectType.class.php +++ b/wcfsetup/install/files/lib/data/object/type/ObjectType.class.php @@ -2,6 +2,8 @@ declare(strict_types=1); namespace wcf\data\object\type; use wcf\data\object\type\definition\ObjectTypeDefinition; +use wcf\data\package\Package; +use wcf\data\package\PackageCache; use wcf\data\ProcessibleDatabaseObject; use wcf\data\TDatabaseObjectOptions; use wcf\data\TDatabaseObjectPermissions; @@ -114,4 +116,14 @@ class ObjectType extends ProcessibleDatabaseObject { public function getDefinition() { return ObjectTypeCache::getInstance()->getDefinition($this->definitionID); } + + /** + * Returns the package that this object type belongs to. + * + * @return Package + * @since 3.2 + */ + public function getPackage(): Package { + return PackageCache::getInstance()->getPackage($this->packageID); + } } diff --git a/wcfsetup/install/files/lib/system/log/modification/AbstractExtendedModificationLogHandler.class.php b/wcfsetup/install/files/lib/system/log/modification/AbstractExtendedModificationLogHandler.class.php new file mode 100644 index 0000000000..978cf012ca --- /dev/null +++ b/wcfsetup/install/files/lib/system/log/modification/AbstractExtendedModificationLogHandler.class.php @@ -0,0 +1,16 @@ + + * @package WoltLabSuite\Core\System\Log\Modification + * @since 3.2 + */ +abstract class AbstractExtendedModificationLogHandler extends AbstractModificationLogHandler implements IExtendedModificationLogHandler { +} diff --git a/wcfsetup/install/files/lib/system/log/modification/AbstractModificationLogHandler.class.php b/wcfsetup/install/files/lib/system/log/modification/AbstractModificationLogHandler.class.php index 3bd4561c65..a7bc4ab13a 100644 --- a/wcfsetup/install/files/lib/system/log/modification/AbstractModificationLogHandler.class.php +++ b/wcfsetup/install/files/lib/system/log/modification/AbstractModificationLogHandler.class.php @@ -24,7 +24,7 @@ abstract class AbstractModificationLogHandler extends SingletonFactory { * modifiable content object type * @var ObjectType */ - protected $objectType = null; + protected $objectType; /** * name of the modifiable content object type diff --git a/wcfsetup/install/files/lib/system/log/modification/IExtendedModificationLogHandler.class.php b/wcfsetup/install/files/lib/system/log/modification/IExtendedModificationLogHandler.class.php new file mode 100644 index 0000000000..35eaf4856f --- /dev/null +++ b/wcfsetup/install/files/lib/system/log/modification/IExtendedModificationLogHandler.class.php @@ -0,0 +1,33 @@ + + * @package WoltLabSuite\Core\System\Log\Modification + * @since 3.2 + */ +interface IExtendedModificationLogHandler { + /** + * Returns the list of possible actions for this object type. + * + * @return string[] + */ + public function getAvailableActions(): array; + + /** + * Processes a list of items by converting them into IViewableModificationLog + * instances and pre-loading their data. + * + * @param ModificationLog[] $items + * @return IViewableModificationLog[] + */ + public function processItems(array $items): array; +} diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index 27671b6a73..07c41f895d 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -954,6 +954,20 @@ + + + + + + + + + objectID}]]> + + + + + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 96712a2562..af2ee72c66 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -936,6 +936,20 @@ + + + + + + + + + objectID}]]> + + + + +