misuzu/src/AuditLog/AuditLog.php

144 lines
4.5 KiB
PHP

<?php
namespace Misuzu\AuditLog;
use InvalidArgumentException;
use Index\Data\DbStatementCache;
use Index\Data\IDbConnection;
use Index\Data\IDbResult;
use Index\Net\IPAddress;
use Misuzu\Pagination;
use Misuzu\Users\UserInfo;
class AuditLog {
private DbStatementCache $cache;
public function __construct(IDbConnection $dbConn) {
$this->cache = new DbStatementCache($dbConn);
}
public function countLogs(
UserInfo|string|null $userInfo = null,
IPAddress|string|null $remoteAddr = null
): int {
if($userInfo instanceof UserInfo)
$userInfo = $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) {
++$args;
$query .= ' WHERE 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(
UserInfo|string|null $userInfo = null,
IPAddress|string|null $remoteAddr = null,
?Pagination $pagination = null
): iterable {
if($userInfo instanceof UserInfo)
$userInfo = $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) {
++$args;
$query .= ' WHERE 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();
return $stmt->getResult()->getIterator(AuditLogInfo::fromResult(...));
}
public function createLog(
UserInfo|string|null $userInfo,
string $action,
array $params = [],
IPAddress|string $remoteAddr = '::1',
string $countryCode = 'XX'
): void {
if($userInfo instanceof UserInfo)
$userInfo = $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();
}
}