Rewrote audit log on new database backend.
This commit is contained in:
parent
96be282a93
commit
1a11a8f8ba
25 changed files with 365 additions and 302 deletions
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use Misuzu\AuditLog;
|
|
||||||
use Misuzu\Config\IConfig;
|
use Misuzu\Config\IConfig;
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
use Misuzu\Users\UserNotFoundException;
|
use Misuzu\Users\UserNotFoundException;
|
||||||
|
@ -79,7 +78,7 @@ while($canResetPassword) {
|
||||||
->removeTOTPKey()
|
->removeTOTPKey()
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
AuditLog::create(AuditLog::PASSWORD_RESET, [], $userInfo);
|
$msz->createAuditLog('PASSWORD_RESET', [], $userInfo);
|
||||||
|
|
||||||
$tokenInfo->invalidate();
|
$tokenInfo->invalidate();
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,7 @@
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Misuzu\AuditLog;
|
|
||||||
use Misuzu\Comments\CommentsCategory;
|
|
||||||
use Misuzu\Comments\CommentsPost;
|
|
||||||
use Misuzu\Comments\CommentsVote;
|
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
use Misuzu\Users\UserNotFoundException;
|
|
||||||
|
|
||||||
require_once '../misuzu.php';
|
require_once '../misuzu.php';
|
||||||
|
|
||||||
|
@ -149,13 +144,13 @@ switch($commentMode) {
|
||||||
$comments->deletePost($commentInfo);
|
$comments->deletePost($commentInfo);
|
||||||
|
|
||||||
if($isModAction) {
|
if($isModAction) {
|
||||||
AuditLog::create(AuditLog::COMMENT_ENTRY_DELETE_MOD, [
|
$msz->createAuditLog('COMMENT_ENTRY_DELETE_MOD', [
|
||||||
$commentInfo->getId(),
|
$commentInfo->getId(),
|
||||||
$commentUserId = $commentInfo->getUserId(),
|
$commentUserId = $commentInfo->getUserId(),
|
||||||
'<username>',
|
'<username>',
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
AuditLog::create(AuditLog::COMMENT_ENTRY_DELETE, [$commentInfo->getId()]);
|
$msz->createAuditLog('COMMENT_ENTRY_DELETE', [$commentInfo->getId()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
redirect($redirect);
|
redirect($redirect);
|
||||||
|
@ -174,7 +169,7 @@ switch($commentMode) {
|
||||||
|
|
||||||
$comments->restorePost($commentInfo);
|
$comments->restorePost($commentInfo);
|
||||||
|
|
||||||
AuditLog::create(AuditLog::COMMENT_ENTRY_RESTORE, [
|
$msz->createAuditLog('COMMENT_ENTRY_RESTORE', [
|
||||||
$commentInfo->getId(),
|
$commentInfo->getId(),
|
||||||
$commentUserId = $commentInfo->getUserId(),
|
$commentUserId = $commentInfo->getUserId(),
|
||||||
'<username>',
|
'<username>',
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use Misuzu\AuditLog;
|
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
use Misuzu\Users\UserSession;
|
use Misuzu\Users\UserSession;
|
||||||
|
|
||||||
|
@ -104,7 +103,7 @@ switch($postMode) {
|
||||||
$deletePost = forum_post_delete($postInfo['post_id']);
|
$deletePost = forum_post_delete($postInfo['post_id']);
|
||||||
|
|
||||||
if($deletePost) {
|
if($deletePost) {
|
||||||
AuditLog::create(AuditLog::FORUM_POST_DELETE, [$postInfo['post_id']]);
|
$msz->createAuditLog('FORUM_POST_DELETE', [$postInfo['post_id']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$deletePost) {
|
if(!$deletePost) {
|
||||||
|
@ -147,7 +146,7 @@ switch($postMode) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
AuditLog::create(AuditLog::FORUM_POST_NUKE, [$postInfo['post_id']]);
|
$msz->createAuditLog('FORUM_POST_NUKE', [$postInfo['post_id']]);
|
||||||
|
|
||||||
url_redirect('forum-topic', ['topic' => $postInfo['topic_id']]);
|
url_redirect('forum-topic', ['topic' => $postInfo['topic_id']]);
|
||||||
break;
|
break;
|
||||||
|
@ -184,7 +183,7 @@ switch($postMode) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
AuditLog::create(AuditLog::FORUM_POST_RESTORE, [$postInfo['post_id']]);
|
$msz->createAuditLog('FORUM_POST_RESTORE', [$postInfo['post_id']]);
|
||||||
|
|
||||||
url_redirect('forum-topic', ['topic' => $postInfo['topic_id']]);
|
url_redirect('forum-topic', ['topic' => $postInfo['topic_id']]);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use Misuzu\AuditLog;
|
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
use Misuzu\Users\UserSession;
|
use Misuzu\Users\UserSession;
|
||||||
|
|
||||||
|
@ -164,7 +163,7 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
||||||
$deleteTopic = forum_topic_delete($topic['topic_id']);
|
$deleteTopic = forum_topic_delete($topic['topic_id']);
|
||||||
|
|
||||||
if($deleteTopic)
|
if($deleteTopic)
|
||||||
AuditLog::create(AuditLog::FORUM_TOPIC_DELETE, [$topic['topic_id']]);
|
$msz->createAuditLog('FORUM_TOPIC_DELETE', [$topic['topic_id']]);
|
||||||
|
|
||||||
if(!$deleteTopic) {
|
if(!$deleteTopic) {
|
||||||
echo render_error(500);
|
echo render_error(500);
|
||||||
|
@ -207,7 +206,7 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
AuditLog::create(AuditLog::FORUM_TOPIC_RESTORE, [$topic['topic_id']]);
|
$msz->createAuditLog('FORUM_TOPIC_RESTORE', [$topic['topic_id']]);
|
||||||
|
|
||||||
url_redirect('forum-category', [
|
url_redirect('forum-category', [
|
||||||
'forum' => $topic['forum_id'],
|
'forum' => $topic['forum_id'],
|
||||||
|
@ -245,7 +244,7 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
AuditLog::create(AuditLog::FORUM_TOPIC_NUKE, [$topic['topic_id']]);
|
$msz->createAuditLog('FORUM_TOPIC_NUKE', [$topic['topic_id']]);
|
||||||
|
|
||||||
url_redirect('forum-category', [
|
url_redirect('forum-category', [
|
||||||
'forum' => $topic['forum_id'],
|
'forum' => $topic['forum_id'],
|
||||||
|
@ -254,7 +253,7 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
||||||
|
|
||||||
case 'bump':
|
case 'bump':
|
||||||
if($canBumpTopic && forum_topic_bump($topic['topic_id'])) {
|
if($canBumpTopic && forum_topic_bump($topic['topic_id'])) {
|
||||||
AuditLog::create(AuditLog::FORUM_TOPIC_BUMP, [$topic['topic_id']]);
|
$msz->createAuditLog('FORUM_TOPIC_BUMP', [$topic['topic_id']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
url_redirect('forum-topic', [
|
url_redirect('forum-topic', [
|
||||||
|
@ -264,7 +263,7 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
||||||
|
|
||||||
case 'lock':
|
case 'lock':
|
||||||
if($canLockTopic && !$topicIsLocked && forum_topic_lock($topic['topic_id'])) {
|
if($canLockTopic && !$topicIsLocked && forum_topic_lock($topic['topic_id'])) {
|
||||||
AuditLog::create(AuditLog::FORUM_TOPIC_LOCK, [$topic['topic_id']]);
|
$msz->createAuditLog('FORUM_TOPIC_LOCK', [$topic['topic_id']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
url_redirect('forum-topic', [
|
url_redirect('forum-topic', [
|
||||||
|
@ -274,7 +273,7 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
||||||
|
|
||||||
case 'unlock':
|
case 'unlock':
|
||||||
if($canLockTopic && $topicIsLocked && forum_topic_unlock($topic['topic_id'])) {
|
if($canLockTopic && $topicIsLocked && forum_topic_unlock($topic['topic_id'])) {
|
||||||
AuditLog::create(AuditLog::FORUM_TOPIC_UNLOCK, [$topic['topic_id']]);
|
$msz->createAuditLog('FORUM_TOPIC_UNLOCK', [$topic['topic_id']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
url_redirect('forum-topic', [
|
url_redirect('forum-topic', [
|
||||||
|
|
|
@ -4,7 +4,6 @@ namespace Misuzu;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Index\DateTime;
|
use Index\DateTime;
|
||||||
use Misuzu\AuditLog;
|
|
||||||
use Misuzu\Changelog\Changelog;
|
use Misuzu\Changelog\Changelog;
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
use Misuzu\Users\UserNotFoundException;
|
use Misuzu\Users\UserNotFoundException;
|
||||||
|
@ -39,7 +38,7 @@ else
|
||||||
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
|
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
|
||||||
if(CSRF::validateRequest()) {
|
if(CSRF::validateRequest()) {
|
||||||
$changelog->deleteChange($changeInfo);
|
$changelog->deleteChange($changeInfo);
|
||||||
AuditLog::create(AuditLog::CHANGELOG_ENTRY_DELETE, [$changeInfo->getId()]);
|
$msz->createAuditLog('CHANGELOG_ENTRY_DELETE', [$changeInfo->getId()]);
|
||||||
url_redirect('manage-changelog-changes');
|
url_redirect('manage-changelog-changes');
|
||||||
} else render_error(403);
|
} else render_error(403);
|
||||||
return;
|
return;
|
||||||
|
@ -102,8 +101,8 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AuditLog::create(
|
$msz->createAuditLog(
|
||||||
$isNew ? AuditLog::CHANGELOG_ENTRY_CREATE : AuditLog::CHANGELOG_ENTRY_EDIT,
|
$isNew ? 'CHANGELOG_ENTRY_CREATE' : 'CHANGELOG_ENTRY_EDIT',
|
||||||
[$changeInfo->getId()]
|
[$changeInfo->getId()]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Misuzu\AuditLog;
|
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
|
|
||||||
require_once '../../../misuzu.php';
|
require_once '../../../misuzu.php';
|
||||||
|
@ -30,7 +29,7 @@ else
|
||||||
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
|
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
|
||||||
if(CSRF::validateRequest()) {
|
if(CSRF::validateRequest()) {
|
||||||
$changelog->deleteTag($tagInfo);
|
$changelog->deleteTag($tagInfo);
|
||||||
AuditLog::create(AuditLog::CHANGELOG_TAG_DELETE, [$tagInfo->getId()]);
|
$msz->createAuditLog('CHANGELOG_TAG_DELETE', [$tagInfo->getId()]);
|
||||||
url_redirect('manage-changelog-tags');
|
url_redirect('manage-changelog-tags');
|
||||||
} else render_error(403);
|
} else render_error(403);
|
||||||
return;
|
return;
|
||||||
|
@ -55,8 +54,8 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
$changelog->updateTag($tagInfo, $name, $description, $archive);
|
$changelog->updateTag($tagInfo, $name, $description, $archive);
|
||||||
}
|
}
|
||||||
|
|
||||||
AuditLog::create(
|
$msz->createAuditLog(
|
||||||
$isNew ? AuditLog::CHANGELOG_TAG_CREATE : AuditLog::CHANGELOG_TAG_EDIT,
|
$isNew ? 'CHANGELOG_TAG_CREATE' : 'CHANGELOG_TAG_EDIT',
|
||||||
[$tagInfo->getId()]
|
[$tagInfo->getId()]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
if($rTopicId < 1)
|
if($rTopicId < 1)
|
||||||
throw new \Exception("Invalid topic id.");
|
throw new \Exception("Invalid topic id.");
|
||||||
|
|
||||||
AuditLog::create(AuditLog::FORUM_TOPIC_REDIR_CREATE, [$rTopicId]);
|
$msz->createAuditLog('FORUM_TOPIC_REDIR_CREATE', [$rTopicId]);
|
||||||
forum_topic_redir_create($rTopicId, User::getCurrent()->getId(), $rTopicURL);
|
forum_topic_redir_create($rTopicId, User::getCurrent()->getId(), $rTopicURL);
|
||||||
url_redirect('manage-forum-topic-redirs');
|
url_redirect('manage-forum-topic-redirs');
|
||||||
return;
|
return;
|
||||||
|
@ -31,7 +31,7 @@ if(filter_input(INPUT_GET, 'm') === 'explode') {
|
||||||
throw new \Exception("Request verification failed.");
|
throw new \Exception("Request verification failed.");
|
||||||
|
|
||||||
$rTopicId = (int)filter_input(INPUT_GET, 't');
|
$rTopicId = (int)filter_input(INPUT_GET, 't');
|
||||||
AuditLog::create(AuditLog::FORUM_TOPIC_REDIR_REMOVE, [$rTopicId]);
|
$msz->createAuditLog('FORUM_TOPIC_REDIR_REMOVE', [$rTopicId]);
|
||||||
forum_topic_redir_remove($rTopicId);
|
forum_topic_redir_remove($rTopicId);
|
||||||
url_redirect('manage-forum-topic-redirs');
|
url_redirect('manage-forum-topic-redirs');
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -95,8 +95,8 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
$sCurrent[] = $string;
|
$sCurrent[] = $string;
|
||||||
}
|
}
|
||||||
|
|
||||||
AuditLog::create(
|
$msz->createAuditLog(
|
||||||
$isNew ? AuditLog::EMOTICON_CREATE : AuditLog::EMOTICON_EDIT,
|
$isNew ? 'EMOTICON_CREATE' : 'EMOTICON_EDIT',
|
||||||
[$emoteInfo->getId()]
|
[$emoteInfo->getId()]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -25,20 +25,20 @@ if(CSRF::validateRequest() && !empty($_GET['emote'])) {
|
||||||
|
|
||||||
if(!empty($_GET['delete'])) {
|
if(!empty($_GET['delete'])) {
|
||||||
$emotes->deleteEmote($emoteInfo);
|
$emotes->deleteEmote($emoteInfo);
|
||||||
AuditLog::create(AuditLog::EMOTICON_DELETE, [$emoteInfo->getId()]);
|
$msz->createAuditLog('EMOTICON_DELETE', [$emoteInfo->getId()]);
|
||||||
} else {
|
} else {
|
||||||
if(isset($_GET['order'])) {
|
if(isset($_GET['order'])) {
|
||||||
$order = filter_input(INPUT_GET, 'order');
|
$order = filter_input(INPUT_GET, 'order');
|
||||||
$offset = $order === 'i' ? 1 : ($order === 'd' ? -1 : 0);
|
$offset = $order === 'i' ? 1 : ($order === 'd' ? -1 : 0);
|
||||||
$emotes->updateEmoteOrderOffset($emoteInfo, $offset);
|
$emotes->updateEmoteOrderOffset($emoteInfo, $offset);
|
||||||
AuditLog::create(AuditLog::EMOTICON_ORDER, [$emoteInfo->getId()]);
|
$msz->createAuditLog('EMOTICON_ORDER', [$emoteInfo->getId()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($_GET['alias'])) {
|
if(isset($_GET['alias'])) {
|
||||||
$alias = (string)filter_input(INPUT_GET, 'alias');
|
$alias = (string)filter_input(INPUT_GET, 'alias');
|
||||||
if($emotes->checkEmoteString($alias) === '') {
|
if($emotes->checkEmoteString($alias) === '') {
|
||||||
$emotes->addEmoteString($emoteInfo, $alias);
|
$emotes->addEmoteString($emoteInfo, $alias);
|
||||||
AuditLog::create(AuditLog::EMOTICON_ALIAS, [$emoteInfo->getId(), $alias]);
|
$msz->createAuditLog('EMOTICON_ALIAS', [$emoteInfo->getId(), $alias]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use Misuzu\AuditLog;
|
|
||||||
use Misuzu\Pagination;
|
use Misuzu\Pagination;
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
|
|
||||||
|
@ -12,16 +11,25 @@ if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$pagination = new Pagination(AuditLog::countAll(), 50);
|
$auditLog = $msz->getAuditLog();
|
||||||
|
$pagination = new Pagination($auditLog->countLogs(), 50);
|
||||||
|
|
||||||
if(!$pagination->hasValidOffset()) {
|
if(!$pagination->hasValidOffset()) {
|
||||||
echo render_error(404);
|
echo render_error(404);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$logs = AuditLog::all($pagination);
|
$logs = $auditLog->getLogs(pagination: $pagination);
|
||||||
|
$userInfos = [];
|
||||||
|
foreach($logs as $log)
|
||||||
|
if($log->hasUserId()) {
|
||||||
|
$userId = $log->getUserId();
|
||||||
|
if(!array_key_exists($userId, $userInfos))
|
||||||
|
$userInfos[$userId] = User::byId($userId);
|
||||||
|
}
|
||||||
|
|
||||||
Template::render('manage.general.logs', [
|
Template::render('manage.general.logs', [
|
||||||
'global_logs' => $logs,
|
'global_logs' => $logs,
|
||||||
'global_logs_pagination' => $pagination,
|
'global_logs_pagination' => $pagination,
|
||||||
|
'global_logs_users' => $userInfos,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use Misuzu\AuditLog;
|
|
||||||
use Misuzu\Config;
|
use Misuzu\Config;
|
||||||
use Misuzu\Config\CfgTools;
|
use Misuzu\Config\CfgTools;
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
|
@ -22,7 +21,7 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
if(!CSRF::validateRequest())
|
if(!CSRF::validateRequest())
|
||||||
throw new \Exception("Request verification failed.");
|
throw new \Exception("Request verification failed.");
|
||||||
|
|
||||||
AuditLog::create(AuditLog::CONFIG_DELETE, [$sName]);
|
$msz->createAuditLog('CONFIG_DELETE', [$sName]);
|
||||||
$cfg->removeValue($sName);
|
$cfg->removeValue($sName);
|
||||||
url_redirect('manage-general-settings');
|
url_redirect('manage-general-settings');
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -52,12 +52,12 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
$sVar['name'] = $sName;
|
$sVar['name'] = $sName;
|
||||||
}
|
}
|
||||||
|
|
||||||
$sLogAction = AuditLog::CONFIG_CREATE;
|
$sLogAction = 'CONFIG_CREATE';
|
||||||
|
|
||||||
if($cfg->hasValue($sName)) {
|
if($cfg->hasValue($sName)) {
|
||||||
$sType = CfgTools::type($cfg->getValue($sName));
|
$sType = CfgTools::type($cfg->getValue($sName));
|
||||||
$sVar['new'] = false;
|
$sVar['new'] = false;
|
||||||
$sLogAction = AuditLog::CONFIG_UPDATE;
|
$sLogAction = 'CONFIG_UPDATE';
|
||||||
} elseif(empty($sType)) {
|
} elseif(empty($sType)) {
|
||||||
$sType = (string)filter_input(INPUT_POST, 'conf_type');
|
$sType = (string)filter_input(INPUT_POST, 'conf_type');
|
||||||
if(empty($sType) || !CfgTools::isValidType($sType))
|
if(empty($sType) || !CfgTools::isValidType($sType))
|
||||||
|
@ -95,7 +95,7 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
|
||||||
$sVar['value'] = $sValue;
|
$sVar['value'] = $sValue;
|
||||||
|
|
||||||
AuditLog::create($sLogAction, [$sName]);
|
$msz->createAuditLog($sLogAction, [$sName]);
|
||||||
$cfg->setValue($sName, $sValue);
|
$cfg->setValue($sName, $sValue);
|
||||||
url_redirect('manage-general-settings');
|
url_redirect('manage-general-settings');
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Misuzu\AuditLog;
|
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
|
|
||||||
require_once '../../../misuzu.php';
|
require_once '../../../misuzu.php';
|
||||||
|
@ -30,7 +29,7 @@ else
|
||||||
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
|
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
|
||||||
if(CSRF::validateRequest()) {
|
if(CSRF::validateRequest()) {
|
||||||
$news->deleteCategory($categoryInfo);
|
$news->deleteCategory($categoryInfo);
|
||||||
AuditLog::create(AuditLog::NEWS_CATEGORY_DELETE, [$categoryInfo->getId()]);
|
$msz->createAuditLog('NEWS_CATEGORY_DELETE', [$categoryInfo->getId()]);
|
||||||
url_redirect('manage-news-categories');
|
url_redirect('manage-news-categories');
|
||||||
} else render_error(403);
|
} else render_error(403);
|
||||||
return;
|
return;
|
||||||
|
@ -55,8 +54,8 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
$news->updateCategory($categoryInfo, $name, $description, $hidden);
|
$news->updateCategory($categoryInfo, $name, $description, $hidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
AuditLog::create(
|
$msz->createAuditLog(
|
||||||
$isNew ? AuditLog::NEWS_CATEGORY_CREATE : AuditLog::NEWS_CATEGORY_EDIT,
|
$isNew ? 'NEWS_CATEGORY_CREATE' : 'NEWS_CATEGORY_EDIT',
|
||||||
[$categoryInfo->getId()]
|
[$categoryInfo->getId()]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Misuzu\AuditLog;
|
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
|
|
||||||
require_once '../../../misuzu.php';
|
require_once '../../../misuzu.php';
|
||||||
|
@ -30,7 +29,7 @@ else
|
||||||
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
|
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
|
||||||
if(CSRF::validateRequest()) {
|
if(CSRF::validateRequest()) {
|
||||||
$news->deletePost($postInfo);
|
$news->deletePost($postInfo);
|
||||||
AuditLog::create(AuditLog::NEWS_POST_DELETE, [$postInfo->getId()]);
|
$msz->createAuditLog('NEWS_POST_DELETE', [$postInfo->getId()]);
|
||||||
url_redirect('manage-news-posts');
|
url_redirect('manage-news-posts');
|
||||||
} else render_error(403);
|
} else render_error(403);
|
||||||
return;
|
return;
|
||||||
|
@ -58,8 +57,8 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
$news->updatePost($postInfo, $category, $title, $body, $featured);
|
$news->updatePost($postInfo, $category, $title, $body, $featured);
|
||||||
}
|
}
|
||||||
|
|
||||||
AuditLog::create(
|
$msz->createAuditLog(
|
||||||
$isNew ? AuditLog::NEWS_POST_CREATE : AuditLog::NEWS_POST_EDIT,
|
$isNew ? 'NEWS_POST_CREATE' : 'NEWS_POST_EDIT',
|
||||||
[$postInfo->getId()]
|
[$postInfo->getId()]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use Misuzu\AuditLog;
|
|
||||||
use Misuzu\Config;
|
use Misuzu\Config;
|
||||||
use Misuzu\Config\IConfig;
|
use Misuzu\Config\IConfig;
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
|
@ -103,7 +102,7 @@ if($isVerifiedRequest && !empty($_POST['current_password'])) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$currentUser->setEMailAddress($_POST['email']['new']);
|
$currentUser->setEMailAddress($_POST['email']['new']);
|
||||||
AuditLog::create(AuditLog::PERSONAL_EMAIL_CHANGE, [
|
$msz->createAuditLog('PERSONAL_EMAIL_CHANGE', [
|
||||||
$_POST['email']['new'],
|
$_POST['email']['new'],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -121,7 +120,7 @@ if($isVerifiedRequest && !empty($_POST['current_password'])) {
|
||||||
$errors[] = 'The given passwords was too weak.';
|
$errors[] = 'The given passwords was too weak.';
|
||||||
} else {
|
} else {
|
||||||
$currentUser->setPassword($_POST['password']['new']);
|
$currentUser->setPassword($_POST['password']['new']);
|
||||||
AuditLog::create(AuditLog::PERSONAL_PASSWORD_CHANGE);
|
$msz->createAuditLog('PERSONAL_PASSWORD_CHANGE');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use ZipArchive;
|
use ZipArchive;
|
||||||
use Misuzu\AuditLog;
|
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
use Misuzu\Users\UserSession;
|
use Misuzu\Users\UserSession;
|
||||||
|
|
||||||
|
@ -36,7 +35,7 @@ if(isset($_POST['action']) && is_string($_POST['action'])) {
|
||||||
&& $currentUser->checkPassword($_POST['password'] ?? '')) {
|
&& $currentUser->checkPassword($_POST['password'] ?? '')) {
|
||||||
switch($_POST['action']) {
|
switch($_POST['action']) {
|
||||||
case 'data':
|
case 'data':
|
||||||
AuditLog::create(AuditLog::PERSONAL_DATA_DOWNLOAD);
|
$msz->createAuditLog('PERSONAL_DATA_DOWNLOAD');
|
||||||
|
|
||||||
$timeStamp = floor(time() / 3600) * 3600;
|
$timeStamp = floor(time() / 3600) * 3600;
|
||||||
$fileName = sprintf('msz-user-data-%d-%d.zip', $currentUserId, $timeStamp);
|
$fileName = sprintf('msz-user-data-%d-%d.zip', $currentUserId, $timeStamp);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use Misuzu\AuditLog;
|
|
||||||
use Misuzu\Pagination;
|
use Misuzu\Pagination;
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
use Misuzu\Users\UserLoginAttempt;
|
use Misuzu\Users\UserLoginAttempt;
|
||||||
|
@ -15,12 +14,16 @@ if($currentUser === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$auditLog = $msz->getAuditLog();
|
||||||
|
|
||||||
$loginHistoryPagination = new Pagination(UserLoginAttempt::countAll($currentUser), 15, 'hp');
|
$loginHistoryPagination = new Pagination(UserLoginAttempt::countAll($currentUser), 15, 'hp');
|
||||||
$accountLogPagination = new Pagination(AuditLog::countAll($currentUser), 15, 'ap');
|
$accountLogPagination = new Pagination($auditLog->countLogs(userInfo: $currentUser), 15, 'ap');
|
||||||
|
|
||||||
|
$auditLogs = $auditLog->getLogs(userInfo: $currentUser, pagination: $accountLogPagination);
|
||||||
|
|
||||||
Template::render('settings.logs', [
|
Template::render('settings.logs', [
|
||||||
'login_history_list' => UserLoginAttempt::all($loginHistoryPagination, $currentUser),
|
'login_history_list' => UserLoginAttempt::all($loginHistoryPagination, $currentUser),
|
||||||
'login_history_pagination' => $loginHistoryPagination,
|
'login_history_pagination' => $loginHistoryPagination,
|
||||||
'account_log_list' => AuditLog::all($accountLogPagination, $currentUser),
|
'account_log_list' => $auditLogs,
|
||||||
'account_log_pagination' => $accountLogPagination,
|
'account_log_pagination' => $accountLogPagination,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use Misuzu\AuditLog;
|
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
use Misuzu\Users\UserSession;
|
use Misuzu\Users\UserSession;
|
||||||
use Misuzu\Users\UserSessionNotFoundException;
|
use Misuzu\Users\UserSessionNotFoundException;
|
||||||
|
@ -38,12 +37,12 @@ if(!empty($_POST['session']) && CSRF::validateRequest()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$sessionInfo->delete();
|
$sessionInfo->delete();
|
||||||
AuditLog::create(AuditLog::PERSONAL_SESSION_DESTROY, [$sessionInfo->getId()]);
|
$msz->createAuditLog('PERSONAL_SESSION_DESTROY', [$sessionInfo->getId()]);
|
||||||
}
|
}
|
||||||
} elseif($_POST['session'] === 'all') {
|
} elseif($_POST['session'] === 'all') {
|
||||||
$currentSessionKilled = true;
|
$currentSessionKilled = true;
|
||||||
UserSession::purgeUser($currentUser);
|
UserSession::purgeUser($currentUser);
|
||||||
AuditLog::create(AuditLog::PERSONAL_SESSION_DESTROY_ALL);
|
$msz->createAuditLog('PERSONAL_SESSION_DESTROY_ALL');
|
||||||
}
|
}
|
||||||
|
|
||||||
if($currentSessionKilled) {
|
if($currentSessionKilled) {
|
||||||
|
|
230
src/AuditLog.php
230
src/AuditLog.php
|
@ -1,230 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Misuzu;
|
|
||||||
|
|
||||||
use Misuzu\DB;
|
|
||||||
use Misuzu\Pagination;
|
|
||||||
use Misuzu\Users\User;
|
|
||||||
use Misuzu\Users\UserNotFoundException;
|
|
||||||
|
|
||||||
class AuditLog {
|
|
||||||
public const PERSONAL_EMAIL_CHANGE = 'PERSONAL_EMAIL_CHANGE';
|
|
||||||
public const PERSONAL_PASSWORD_CHANGE = 'PERSONAL_PASSWORD_CHANGE';
|
|
||||||
public const PERSONAL_SESSION_DESTROY = 'PERSONAL_SESSION_DESTROY';
|
|
||||||
public const PERSONAL_SESSION_DESTROY_ALL = 'PERSONAL_SESSION_DESTROY_ALL';
|
|
||||||
public const PERSONAL_DATA_DOWNLOAD = 'PERSONAL_DATA_DOWNLOAD';
|
|
||||||
|
|
||||||
public const PASSWORD_RESET = 'PASSWORD_RESET';
|
|
||||||
|
|
||||||
public const CHANGELOG_ENTRY_CREATE = 'CHANGELOG_ENTRY_CREATE';
|
|
||||||
public const CHANGELOG_ENTRY_EDIT = 'CHANGELOG_ENTRY_EDIT';
|
|
||||||
public const CHANGELOG_TAG_ADD = 'CHANGELOG_TAG_ADD';
|
|
||||||
public const CHANGELOG_TAG_REMOVE = 'CHANGELOG_TAG_REMOVE';
|
|
||||||
public const CHANGELOG_TAG_CREATE = 'CHANGELOG_TAG_CREATE';
|
|
||||||
public const CHANGELOG_TAG_EDIT = 'CHANGELOG_TAG_EDIT';
|
|
||||||
public const CHANGELOG_TAG_DELETE = 'CHANGELOG_TAG_DELETE';
|
|
||||||
public const CHANGELOG_ACTION_CREATE = 'CHANGELOG_ACTION_CREATE';
|
|
||||||
public const CHANGELOG_ACTION_EDIT = 'CHANGELOG_ACTION_EDIT';
|
|
||||||
|
|
||||||
public const COMMENT_ENTRY_DELETE = 'COMMENT_ENTRY_DELETE';
|
|
||||||
public const COMMENT_ENTRY_DELETE_MOD = 'COMMENT_ENTRY_DELETE_MOD';
|
|
||||||
public const COMMENT_ENTRY_RESTORE = 'COMMENT_ENTRY_RESTORE';
|
|
||||||
|
|
||||||
public const NEWS_POST_CREATE = 'NEWS_POST_CREATE';
|
|
||||||
public const NEWS_POST_EDIT = 'NEWS_POST_EDIT';
|
|
||||||
public const NEWS_POST_DELETE = 'NEWS_POST_DELETE';
|
|
||||||
public const NEWS_CATEGORY_CREATE = 'NEWS_CATEGORY_CREATE';
|
|
||||||
public const NEWS_CATEGORY_EDIT = 'NEWS_CATEGORY_EDIT';
|
|
||||||
public const NEWS_CATEGORY_DELETE = 'NEWS_CATEGORY_DELETE';
|
|
||||||
|
|
||||||
public const FORUM_TOPIC_DELETE = 'FORUM_TOPIC_DELETE';
|
|
||||||
public const FORUM_TOPIC_RESTORE = 'FORUM_TOPIC_RESTORE';
|
|
||||||
public const FORUM_TOPIC_NUKE = 'FORUM_TOPIC_NUKE';
|
|
||||||
public const FORUM_TOPIC_BUMP = 'FORUM_TOPIC_BUMP';
|
|
||||||
public const FORUM_TOPIC_LOCK = 'FORUM_TOPIC_LOCK';
|
|
||||||
public const FORUM_TOPIC_UNLOCK = 'FORUM_TOPIC_UNLOCK';
|
|
||||||
public const FORUM_TOPIC_REDIR_CREATE = 'FORUM_TOPIC_REDIR_CREATE';
|
|
||||||
public const FORUM_TOPIC_REDIR_REMOVE = 'FORUM_TOPIC_REDIR_REMOVE';
|
|
||||||
|
|
||||||
public const FORUM_POST_EDIT = 'FORUM_POST_EDIT';
|
|
||||||
public const FORUM_POST_DELETE = 'FORUM_POST_DELETE';
|
|
||||||
public const FORUM_POST_RESTORE = 'FORUM_POST_RESTORE';
|
|
||||||
public const FORUM_POST_NUKE = 'FORUM_POST_NUKE';
|
|
||||||
|
|
||||||
public const CONFIG_CREATE = 'CONFIG_CREATE';
|
|
||||||
public const CONFIG_UPDATE = 'CONFIG_UPDATE';
|
|
||||||
public const CONFIG_DELETE = 'CONFIG_DELETE';
|
|
||||||
|
|
||||||
public const EMOTICON_CREATE = 'EMOTICON_CREATE';
|
|
||||||
public const EMOTICON_EDIT = 'EMOTICON_EDIT';
|
|
||||||
public const EMOTICON_DELETE = 'EMOTICON_DELETE';
|
|
||||||
public const EMOTICON_ORDER = 'EMOTICON_ORDER';
|
|
||||||
public const EMOTICON_ALIAS = 'EMOTICON_ALIAS';
|
|
||||||
|
|
||||||
public const FORMATS = [
|
|
||||||
self::PERSONAL_EMAIL_CHANGE => 'Changed e-mail address to %s.',
|
|
||||||
self::PERSONAL_PASSWORD_CHANGE => 'Changed account password.',
|
|
||||||
self::PERSONAL_SESSION_DESTROY => 'Ended session #%d.',
|
|
||||||
self::PERSONAL_SESSION_DESTROY_ALL => 'Ended all personal sessions.',
|
|
||||||
self::PERSONAL_DATA_DOWNLOAD => 'Downloaded archive of account data.',
|
|
||||||
|
|
||||||
self::PASSWORD_RESET => 'Successfully used the password reset form to change password.',
|
|
||||||
|
|
||||||
self::CHANGELOG_ENTRY_CREATE => 'Created a new changelog entry #%d.',
|
|
||||||
self::CHANGELOG_ENTRY_EDIT => 'Edited changelog entry #%d.',
|
|
||||||
self::CHANGELOG_TAG_ADD => 'Added tag #%2$d to changelog entry #%1$d.',
|
|
||||||
self::CHANGELOG_TAG_REMOVE => 'Removed tag #%2$d from changelog entry #%1$d.',
|
|
||||||
self::CHANGELOG_TAG_CREATE => 'Created new changelog tag #%d.',
|
|
||||||
self::CHANGELOG_TAG_EDIT => 'Edited changelog tag #%d.',
|
|
||||||
self::CHANGELOG_TAG_DELETE => 'Deleted changelog tag #%d.',
|
|
||||||
self::CHANGELOG_ACTION_CREATE => 'Created new changelog action #%d.',
|
|
||||||
self::CHANGELOG_ACTION_EDIT => 'Edited changelog action #%d.',
|
|
||||||
|
|
||||||
self::COMMENT_ENTRY_DELETE => 'Deleted comment #%d.',
|
|
||||||
self::COMMENT_ENTRY_DELETE_MOD => 'Deleted comment #%d by user #%d %s.',
|
|
||||||
self::COMMENT_ENTRY_RESTORE => 'Restored comment #%d by user #%d %s.',
|
|
||||||
|
|
||||||
self::NEWS_POST_CREATE => 'Created news post #%d.',
|
|
||||||
self::NEWS_POST_EDIT => 'Edited news post #%d.',
|
|
||||||
self::NEWS_POST_DELETE => 'Deleted news post #%d.',
|
|
||||||
self::NEWS_CATEGORY_CREATE => 'Created news category #%d.',
|
|
||||||
self::NEWS_CATEGORY_EDIT => 'Edited news category #%d.',
|
|
||||||
self::NEWS_CATEGORY_DELETE => 'Deleted news category #%d.',
|
|
||||||
|
|
||||||
self::FORUM_POST_EDIT => 'Edited forum post #%d.',
|
|
||||||
self::FORUM_POST_DELETE => 'Deleted forum post #%d.',
|
|
||||||
self::FORUM_POST_RESTORE => 'Restored forum post #%d.',
|
|
||||||
self::FORUM_POST_NUKE => 'Nuked forum post #%d.',
|
|
||||||
|
|
||||||
self::FORUM_TOPIC_DELETE => 'Deleted forum topic #%d.',
|
|
||||||
self::FORUM_TOPIC_RESTORE => 'Restored forum topic #%d.',
|
|
||||||
self::FORUM_TOPIC_NUKE => 'Nuked forum topic #%d.',
|
|
||||||
self::FORUM_TOPIC_BUMP => 'Manually bumped forum topic #%d.',
|
|
||||||
self::FORUM_TOPIC_LOCK => 'Locked forum topic #%d.',
|
|
||||||
self::FORUM_TOPIC_UNLOCK => 'Unlocked forum topic #%d.',
|
|
||||||
self::FORUM_TOPIC_REDIR_CREATE => 'Created redirect for topic #%d.',
|
|
||||||
self::FORUM_TOPIC_REDIR_REMOVE => 'Removed redirect for topic #%d.',
|
|
||||||
|
|
||||||
self::CONFIG_CREATE => 'Created config value with name "%s".',
|
|
||||||
self::CONFIG_UPDATE => 'Updated config value with name "%s".',
|
|
||||||
self::CONFIG_DELETE => 'Deleted config value with name "%s".',
|
|
||||||
|
|
||||||
self::EMOTICON_CREATE => 'Created emoticon #%s.',
|
|
||||||
self::EMOTICON_EDIT => 'Edited emoticon #%s.',
|
|
||||||
self::EMOTICON_DELETE => 'Deleted emoticon #%s.',
|
|
||||||
self::EMOTICON_ORDER => 'Changed order of emoticon #%s.',
|
|
||||||
self::EMOTICON_ALIAS => 'Added alias "%2$s" to emoticon #%1$s.',
|
|
||||||
];
|
|
||||||
|
|
||||||
// Database fields
|
|
||||||
private $user_id = null;
|
|
||||||
private $log_action = '';
|
|
||||||
private $log_params = [];
|
|
||||||
private $log_created = null;
|
|
||||||
private $log_ip = '::1';
|
|
||||||
private $log_country = 'XX';
|
|
||||||
|
|
||||||
private $user = null;
|
|
||||||
private $userLookedUp = false;
|
|
||||||
|
|
||||||
public const TABLE = 'audit_log';
|
|
||||||
private const QUERY_SELECT = 'SELECT %1$s FROM `' . DB::PREFIX . self::TABLE . '` AS '. self::TABLE;
|
|
||||||
private const SELECT = '%1$s.`user_id`, %1$s.`log_action`, %1$s.`log_params`, %1$s.`log_country`'
|
|
||||||
. ', INET6_NTOA(%1$s.`log_ip`) AS `log_ip`'
|
|
||||||
. ', UNIX_TIMESTAMP(%1$s.`log_created`) AS `log_created`';
|
|
||||||
|
|
||||||
public function getUserId(): int {
|
|
||||||
return $this->user_id < 1 ? -1 : $this->user_id;
|
|
||||||
}
|
|
||||||
public function getUser(): ?User {
|
|
||||||
if(!$this->userLookedUp && ($userId = $this->getUserId()) > 0) {
|
|
||||||
$this->userLookedUp = true;
|
|
||||||
try {
|
|
||||||
$this->user = User::byId($userId);
|
|
||||||
} catch(UserNotFoundException $ex) {}
|
|
||||||
}
|
|
||||||
return $this->user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAction(): string {
|
|
||||||
return $this->log_action;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getParams(): array {
|
|
||||||
if(is_string($this->log_params))
|
|
||||||
$this->log_params = json_decode($this->log_params) ?? [];
|
|
||||||
return $this->log_params;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCreatedTime(): int {
|
|
||||||
return $this->log_created === null ? -1 : $this->log_created;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRemoteAddress(): string {
|
|
||||||
return $this->log_ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCountry(): string {
|
|
||||||
return $this->log_country;
|
|
||||||
}
|
|
||||||
public function getCountryName(): string {
|
|
||||||
return get_country_name($this->getCountry());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getString(): string {
|
|
||||||
if(!array_key_exists($this->getAction(), self::FORMATS))
|
|
||||||
return sprintf('%s(%s)', $this->getAction(), json_encode($this->getParams()));
|
|
||||||
return vsprintf(self::FORMATS[$this->getAction()], $this->getParams());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function create(string $action, array $params = [], ?User $user = null): void {
|
|
||||||
$user = $user ?? User::getCurrent();
|
|
||||||
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '::1';
|
|
||||||
$countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX';
|
|
||||||
|
|
||||||
$createLog = DB::prepare(
|
|
||||||
'INSERT INTO `' . DB::PREFIX . self::TABLE . '` (`log_action`, `user_id`, `log_params`, `log_ip`, `log_country`)'
|
|
||||||
. ' VALUES (:action, :user, :params, INET6_ATON(:ip), :country)'
|
|
||||||
) ->bind('action', $action)
|
|
||||||
->bind('user', $user === null ? null : $user->getId())
|
|
||||||
->bind('params', json_encode($params))
|
|
||||||
->bind('ip', $remoteAddr)
|
|
||||||
->bind('country', $countryCode)
|
|
||||||
->execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function countQueryBase(): string {
|
|
||||||
return sprintf(self::QUERY_SELECT, 'COUNT(*)');
|
|
||||||
}
|
|
||||||
public static function countAll(?User $user = null): int {
|
|
||||||
$getCount = DB::prepare(
|
|
||||||
self::countQueryBase()
|
|
||||||
. ($user === null ? '' : ' WHERE `user_id` = :user')
|
|
||||||
);
|
|
||||||
if($user !== null)
|
|
||||||
$getCount->bind('user', $user->getId());
|
|
||||||
return (int)$getCount->fetchColumn();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function byQueryBase(): string {
|
|
||||||
return sprintf(self::QUERY_SELECT, sprintf(self::SELECT, self::TABLE));
|
|
||||||
}
|
|
||||||
public static function all(?Pagination $pagination = null, ?User $user = null): array {
|
|
||||||
$logsQuery = self::byQueryBase()
|
|
||||||
. ($user === null ? '' : ' WHERE `user_id` = :user')
|
|
||||||
. ' ORDER BY `log_created` DESC';
|
|
||||||
|
|
||||||
if($pagination !== null)
|
|
||||||
$logsQuery .= ' LIMIT :range OFFSET :offset';
|
|
||||||
|
|
||||||
$getLogs = DB::prepare($logsQuery);
|
|
||||||
|
|
||||||
if($user !== null)
|
|
||||||
$getLogs->bind('user', $user->getId());
|
|
||||||
|
|
||||||
if($pagination !== null)
|
|
||||||
$getLogs->bind('range', $pagination->getRange())
|
|
||||||
->bind('offset', $pagination->getOffset());
|
|
||||||
|
|
||||||
return $getLogs->fetchObjects(self::class);
|
|
||||||
}
|
|
||||||
}
|
|
151
src/AuditLog/AuditLog.php
Normal file
151
src/AuditLog/AuditLog.php
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
<?php
|
||||||
|
namespace Misuzu\AuditLog;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use RuntimeException;
|
||||||
|
use Index\Data\IDbConnection;
|
||||||
|
use Index\Data\IDbResult;
|
||||||
|
use Index\Net\IPAddress;
|
||||||
|
use Misuzu\DbStatementCache;
|
||||||
|
use Misuzu\Pagination;
|
||||||
|
use Misuzu\Users\User;
|
||||||
|
|
||||||
|
class AuditLog {
|
||||||
|
private IDbConnection $dbConn;
|
||||||
|
private DbStatementCache $cache;
|
||||||
|
|
||||||
|
public function __construct(IDbConnection $dbConn) {
|
||||||
|
$this->dbConn = $dbConn;
|
||||||
|
$this->cache = new DbStatementCache($dbConn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countLogs(
|
||||||
|
User|string|null $userInfo = null,
|
||||||
|
IPAddress|string|null $remoteAddr = null
|
||||||
|
): int {
|
||||||
|
if($userInfo instanceof User)
|
||||||
|
$userInfo = (string)$userInfo->getId();
|
||||||
|
if($remoteAddr instanceof IPAddress)
|
||||||
|
$remoteAddr = (string)$remoteAddr;
|
||||||
|
|
||||||
|
$hasUserInfo = $userInfo !== null;
|
||||||
|
$hasRemoteAddr = $remoteAddr !== null;
|
||||||
|
|
||||||
|
$args = 0;
|
||||||
|
$query = 'SELECT COUNT(*) FROM msz_audit_log';
|
||||||
|
if($hasUserInfo) {
|
||||||
|
$query .= (++$args > 1 ? ' AND' : ' WHERE');
|
||||||
|
$query .= ' user_id = ?';
|
||||||
|
}
|
||||||
|
if($hasRemoteAddr) {
|
||||||
|
$query .= (++$args > 1 ? ' AND' : ' WHERE');
|
||||||
|
$query .= ' log_ip = INET6_ATON(?)';
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->cache->get($query);
|
||||||
|
|
||||||
|
$args = 0;
|
||||||
|
if($hasUserInfo)
|
||||||
|
$stmt->addParameter(++$args, $userInfo);
|
||||||
|
if($hasRemoteAddr)
|
||||||
|
$stmt->addParameter(++$args, $remoteAddr);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->getResult();
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
if($result->next())
|
||||||
|
$count = $result->getInteger(0);
|
||||||
|
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLogs(
|
||||||
|
User|string|null $userInfo = null,
|
||||||
|
IPAddress|string|null $remoteAddr = null,
|
||||||
|
?Pagination $pagination = null
|
||||||
|
): array {
|
||||||
|
if($userInfo instanceof User)
|
||||||
|
$userInfo = (string)$userInfo->getId();
|
||||||
|
if($remoteAddr instanceof IPAddress)
|
||||||
|
$remoteAddr = (string)$remoteAddr;
|
||||||
|
|
||||||
|
$hasUserInfo = $userInfo !== null;
|
||||||
|
$hasRemoteAddr = $remoteAddr !== null;
|
||||||
|
$hasPagination = $pagination !== null;
|
||||||
|
|
||||||
|
$args = 0;
|
||||||
|
$query = 'SELECT user_id, log_action, log_params, UNIX_TIMESTAMP(log_created), INET6_NTOA(log_ip), log_country FROM msz_audit_log';
|
||||||
|
if($hasUserInfo) {
|
||||||
|
$query .= (++$args > 1 ? ' AND' : ' WHERE');
|
||||||
|
$query .= ' user_id = ?';
|
||||||
|
}
|
||||||
|
if($hasRemoteAddr) {
|
||||||
|
$query .= (++$args > 1 ? ' AND' : ' WHERE');
|
||||||
|
$query .= ' log_ip = INET6_ATON(?)';
|
||||||
|
}
|
||||||
|
$query .= ' ORDER BY log_created DESC';
|
||||||
|
if($hasPagination)
|
||||||
|
$query .= ' LIMIT ? OFFSET ?';
|
||||||
|
|
||||||
|
$stmt = $this->cache->get($query);
|
||||||
|
|
||||||
|
$args = 0;
|
||||||
|
if($hasUserInfo)
|
||||||
|
$stmt->addParameter(++$args, $userInfo);
|
||||||
|
if($hasRemoteAddr)
|
||||||
|
$stmt->addParameter(++$args, $remoteAddr);
|
||||||
|
if($hasPagination) {
|
||||||
|
$stmt->addParameter(++$args, $pagination->getRange());
|
||||||
|
$stmt->addParameter(++$args, $pagination->getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->getResult();
|
||||||
|
$logs = [];
|
||||||
|
|
||||||
|
while($result->next())
|
||||||
|
$logs[] = new AuditLogInfo($result);
|
||||||
|
|
||||||
|
return $logs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createLog(
|
||||||
|
User|string|null $userInfo,
|
||||||
|
string $action,
|
||||||
|
array $params = [],
|
||||||
|
IPAddress|string $remoteAddr = '::1',
|
||||||
|
string $countryCode = 'XX'
|
||||||
|
): void {
|
||||||
|
if($userInfo instanceof User)
|
||||||
|
$userInfo = (string)$userInfo->getId();
|
||||||
|
if($remoteAddr instanceof IPAddress)
|
||||||
|
$remoteAddr = (string)$remoteAddr;
|
||||||
|
|
||||||
|
// action names should have stricter validation,
|
||||||
|
// i do want to switch to a lowercase colon separated format later but i'll save that for the unified log in Hanyuu
|
||||||
|
$actionTrim = trim($action);
|
||||||
|
if($actionTrim !== $action || empty($actionTrim))
|
||||||
|
throw new InvalidArgumentException('$action may not be empty.');
|
||||||
|
|
||||||
|
if(strlen($countryCode) !== 2 || !ctype_alpha($countryCode))
|
||||||
|
throw new InvalidArgumentException('$countryCode must be two alpha characters.');
|
||||||
|
|
||||||
|
foreach($params as &$param) {
|
||||||
|
if(is_array($param))
|
||||||
|
$param = implode(', ', $param);
|
||||||
|
elseif(is_object($param))
|
||||||
|
$param = (string)$param;
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = json_encode($params);
|
||||||
|
|
||||||
|
$stmt = $this->cache->get('INSERT INTO msz_audit_log (user_id, log_action, log_params, log_ip, log_country) VALUES (?, ?, ?, INET6_ATON(?), UPPER(?))');
|
||||||
|
$stmt->addParameter(1, $userInfo);
|
||||||
|
$stmt->addParameter(2, $action);
|
||||||
|
$stmt->addParameter(3, $params);
|
||||||
|
$stmt->addParameter(4, $remoteAddr);
|
||||||
|
$stmt->addParameter(5, $countryCode);
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
}
|
126
src/AuditLog/AuditLogInfo.php
Normal file
126
src/AuditLog/AuditLogInfo.php
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
<?php
|
||||||
|
namespace Misuzu\AuditLog;
|
||||||
|
|
||||||
|
use ValueError;
|
||||||
|
use Index\DateTime;
|
||||||
|
use Index\Data\IDbResult;
|
||||||
|
use Index\Net\IPAddress;
|
||||||
|
use Misuzu\Users\User;
|
||||||
|
|
||||||
|
class AuditLogInfo {
|
||||||
|
private ?string $userId;
|
||||||
|
private string $action;
|
||||||
|
private array $params;
|
||||||
|
private int $created;
|
||||||
|
private string $address;
|
||||||
|
private string $country;
|
||||||
|
|
||||||
|
public function __construct(IDbResult $result) {
|
||||||
|
$this->userId = $result->isNull(0) ? null : (string)$result->getInteger(0);
|
||||||
|
$this->action = $result->getString(1);
|
||||||
|
$this->params = json_decode($result->getString(2));
|
||||||
|
$this->created = $result->getInteger(3);
|
||||||
|
$this->address = $result->isNull(4) ? '::1' : $result->getString(4); // apparently this being NULL is possible?
|
||||||
|
$this->country = $result->getString(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasUserId(): bool {
|
||||||
|
return $this->userId !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserId(): ?string {
|
||||||
|
return $this->userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAction(): string {
|
||||||
|
return $this->action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParams(): array {
|
||||||
|
return $this->params;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedTime(): int {
|
||||||
|
return $this->created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt(): DateTime {
|
||||||
|
return DateTime::fromUnixTimeSeconds($this->created);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRemoteAddressRaw(): string {
|
||||||
|
return $this->address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRemoteAddress(): IPAddress {
|
||||||
|
return IPAddress::parse($this->address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCountryCode(): string {
|
||||||
|
return $this->country;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFormatted(): string {
|
||||||
|
if(array_key_exists($this->action, self::FORMATS))
|
||||||
|
try {
|
||||||
|
return vsprintf(self::FORMATS[$this->action], $this->params);
|
||||||
|
} catch(ValueError $ex) {}
|
||||||
|
|
||||||
|
return sprintf('%s(%s)', $this->action, implode(', ', $this->params));
|
||||||
|
}
|
||||||
|
|
||||||
|
public const FORMATS = [
|
||||||
|
'PERSONAL_EMAIL_CHANGE' => 'Changed e-mail address to %s.',
|
||||||
|
'PERSONAL_PASSWORD_CHANGE' => 'Changed account password.',
|
||||||
|
'PERSONAL_SESSION_DESTROY' => 'Ended session #%d.',
|
||||||
|
'PERSONAL_SESSION_DESTROY_ALL' => 'Ended all personal sessions.',
|
||||||
|
'PERSONAL_DATA_DOWNLOAD' => 'Downloaded archive of account data.',
|
||||||
|
|
||||||
|
'PASSWORD_RESET' => 'Successfully used the password reset form to change password.',
|
||||||
|
|
||||||
|
'CHANGELOG_ENTRY_CREATE' => 'Created a new changelog entry #%d.',
|
||||||
|
'CHANGELOG_ENTRY_EDIT' => 'Edited changelog entry #%d.',
|
||||||
|
'CHANGELOG_TAG_ADD' => 'Added tag #%2$d to changelog entry #%1$d.',
|
||||||
|
'CHANGELOG_TAG_REMOVE' => 'Removed tag #%2$d from changelog entry #%1$d.',
|
||||||
|
'CHANGELOG_TAG_CREATE' => 'Created new changelog tag #%d.',
|
||||||
|
'CHANGELOG_TAG_EDIT' => 'Edited changelog tag #%d.',
|
||||||
|
'CHANGELOG_TAG_DELETE' => 'Deleted changelog tag #%d.',
|
||||||
|
'CHANGELOG_ACTION_CREATE' => 'Created new changelog action #%d.',
|
||||||
|
'CHANGELOG_ACTION_EDIT' => 'Edited changelog action #%d.',
|
||||||
|
|
||||||
|
'COMMENT_ENTRY_DELETE' => 'Deleted comment #%d.',
|
||||||
|
'COMMENT_ENTRY_DELETE_MOD' => 'Deleted comment #%d by user #%d %s.',
|
||||||
|
'COMMENT_ENTRY_RESTORE' => 'Restored comment #%d by user #%d %s.',
|
||||||
|
|
||||||
|
'NEWS_POST_CREATE' => 'Created news post #%d.',
|
||||||
|
'NEWS_POST_EDIT' => 'Edited news post #%d.',
|
||||||
|
'NEWS_POST_DELETE' => 'Deleted news post #%d.',
|
||||||
|
'NEWS_CATEGORY_CREATE' => 'Created news category #%d.',
|
||||||
|
'NEWS_CATEGORY_EDIT' => 'Edited news category #%d.',
|
||||||
|
'NEWS_CATEGORY_DELETE' => 'Deleted news category #%d.',
|
||||||
|
|
||||||
|
'FORUM_POST_EDIT' => 'Edited forum post #%d.',
|
||||||
|
'FORUM_POST_DELETE' => 'Deleted forum post #%d.',
|
||||||
|
'FORUM_POST_RESTORE' => 'Restored forum post #%d.',
|
||||||
|
'FORUM_POST_NUKE' => 'Nuked forum post #%d.',
|
||||||
|
|
||||||
|
'FORUM_TOPIC_DELETE' => 'Deleted forum topic #%d.',
|
||||||
|
'FORUM_TOPIC_RESTORE' => 'Restored forum topic #%d.',
|
||||||
|
'FORUM_TOPIC_NUKE' => 'Nuked forum topic #%d.',
|
||||||
|
'FORUM_TOPIC_BUMP' => 'Manually bumped forum topic #%d.',
|
||||||
|
'FORUM_TOPIC_LOCK' => 'Locked forum topic #%d.',
|
||||||
|
'FORUM_TOPIC_UNLOCK' => 'Unlocked forum topic #%d.',
|
||||||
|
'FORUM_TOPIC_REDIR_CREATE' => 'Created redirect for topic #%d.',
|
||||||
|
'FORUM_TOPIC_REDIR_REMOVE' => 'Removed redirect for topic #%d.',
|
||||||
|
|
||||||
|
'CONFIG_CREATE' => 'Created config value with name "%s".',
|
||||||
|
'CONFIG_UPDATE' => 'Updated config value with name "%s".',
|
||||||
|
'CONFIG_DELETE' => 'Deleted config value with name "%s".',
|
||||||
|
|
||||||
|
'EMOTICON_CREATE' => 'Created emoticon #%s.',
|
||||||
|
'EMOTICON_EDIT' => 'Edited emoticon #%s.',
|
||||||
|
'EMOTICON_DELETE' => 'Deleted emoticon #%s.',
|
||||||
|
'EMOTICON_ORDER' => 'Changed order of emoticon #%s.',
|
||||||
|
'EMOTICON_ALIAS' => 'Added alias "%2$s" to emoticon #%1$s.',
|
||||||
|
];
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ use Index\Data\IDbConnection;
|
||||||
use Index\Data\IDbResult;
|
use Index\Data\IDbResult;
|
||||||
use Misuzu\DbStatementCache;
|
use Misuzu\DbStatementCache;
|
||||||
use Misuzu\Pagination;
|
use Misuzu\Pagination;
|
||||||
use Misuzu\Comments\CommentsCategory;
|
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
|
|
||||||
class Comments {
|
class Comments {
|
||||||
|
|
|
@ -2,12 +2,14 @@
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use Misuzu\Template;
|
use Misuzu\Template;
|
||||||
|
use Misuzu\AuditLog\AuditLog;
|
||||||
use Misuzu\Changelog\Changelog;
|
use Misuzu\Changelog\Changelog;
|
||||||
use Misuzu\Comments\Comments;
|
use Misuzu\Comments\Comments;
|
||||||
use Misuzu\Config\IConfig;
|
use Misuzu\Config\IConfig;
|
||||||
use Misuzu\Emoticons\Emotes;
|
use Misuzu\Emoticons\Emotes;
|
||||||
use Misuzu\News\News;
|
use Misuzu\News\News;
|
||||||
use Misuzu\SharpChat\SharpChatRoutes;
|
use Misuzu\SharpChat\SharpChatRoutes;
|
||||||
|
use Misuzu\Users\User;
|
||||||
use Misuzu\Users\Users;
|
use Misuzu\Users\Users;
|
||||||
use Index\Data\IDbConnection;
|
use Index\Data\IDbConnection;
|
||||||
use Index\Data\Migration\IDbMigrationRepo;
|
use Index\Data\Migration\IDbMigrationRepo;
|
||||||
|
@ -25,6 +27,7 @@ class MisuzuContext {
|
||||||
private IConfig $config;
|
private IConfig $config;
|
||||||
private Users $users;
|
private Users $users;
|
||||||
private HttpFx $router;
|
private HttpFx $router;
|
||||||
|
private AuditLog $auditLog;
|
||||||
private Emotes $emotes;
|
private Emotes $emotes;
|
||||||
private Changelog $changelog;
|
private Changelog $changelog;
|
||||||
private News $news;
|
private News $news;
|
||||||
|
@ -34,6 +37,7 @@ class MisuzuContext {
|
||||||
$this->dbConn = $dbConn;
|
$this->dbConn = $dbConn;
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->users = new Users($this->dbConn);
|
$this->users = new Users($this->dbConn);
|
||||||
|
$this->auditLog = new AuditLog($this->dbConn);
|
||||||
$this->emotes = new Emotes($this->dbConn);
|
$this->emotes = new Emotes($this->dbConn);
|
||||||
$this->changelog = new Changelog($this->dbConn);
|
$this->changelog = new Changelog($this->dbConn);
|
||||||
$this->news = new News($this->dbConn);
|
$this->news = new News($this->dbConn);
|
||||||
|
@ -85,6 +89,23 @@ class MisuzuContext {
|
||||||
return $this->comments;
|
return $this->comments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getAuditLog(): AuditLog {
|
||||||
|
return $this->auditLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createAuditLog(string $action, array $params = [], User|string|null $userInfo = null): void {
|
||||||
|
if($userInfo === null && User::hasCurrent())
|
||||||
|
$userInfo = User::getCurrent();
|
||||||
|
|
||||||
|
$this->auditLog->createLog(
|
||||||
|
$userInfo,
|
||||||
|
$action,
|
||||||
|
$params,
|
||||||
|
$_SERVER['REMOTE_ADDR'] ?? '::1',
|
||||||
|
$_SERVER['COUNTRY_CODE'] ?? 'XX'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function setUpHttp(bool $legacy = false): void {
|
public function setUpHttp(bool $legacy = false): void {
|
||||||
$this->router = new HttpFx;
|
$this->router = new HttpFx;
|
||||||
$this->router->use('/', function($response) {
|
$this->router->use('/', function($response) {
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% for log in global_logs %}
|
{% for log in global_logs %}
|
||||||
{{ user_account_log(log, true) }}
|
{{ user_account_log(log, global_logs_users) }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<div class="settings__account-logs__pagination">
|
<div class="settings__account-logs__pagination">
|
||||||
|
|
|
@ -327,23 +327,24 @@
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro user_account_log(data, is_manage) %}
|
{% macro user_account_log(data, users) %}
|
||||||
{% from 'macros.twig' import avatar %}
|
{% from 'macros.twig' import avatar %}
|
||||||
|
|
||||||
<div class="settings__account-log">
|
<div class="settings__account-log">
|
||||||
{% if is_manage %}
|
{% if data.hasUserId and users[data.userId] is defined %}
|
||||||
<a href="{{ url('user-profile', {'user': data.user.id}) }}" class="settings__account-log__user" style="--user-colour: {{ data.user.colour }}">
|
{% set user = users[data.userId] %}
|
||||||
<div class="settings__account-log__user__avatar">{{ avatar(data.user.id, 20, data.user.username) }}</div>
|
<a href="{{ url('user-profile', {'user': user.id}) }}" class="settings__account-log__user" style="--user-colour: {{ user.colour }}">
|
||||||
<div class="settings__account-log__user__name">{{ data.user.username }}</div>
|
<div class="settings__account-log__user__avatar">{{ avatar(user.id, 20, user.username) }}</div>
|
||||||
|
<div class="settings__account-log__user__name">{{ user.username }}</div>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="settings__account-log__container">
|
<div class="settings__account-log__container">
|
||||||
<div class="settings__account-log__important">
|
<div class="settings__account-log__important">
|
||||||
<div class="flag flag--{{ data.country|lower }} settings__login-attempt__flag" title="{{ data.countryName }}">{{ data.country }}</div>
|
<div class="flag flag--{{ data.countryCode|lower }} settings__login-attempt__flag" title="{{ data.countryCode|country_name }}">{{ data.countryCode }}</div>
|
||||||
|
|
||||||
<div class="settings__login-attempt__description">
|
<div class="settings__login-attempt__description">
|
||||||
{{ data.string }}
|
{{ data.formatted }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue