misuzu/src/Logs/LogsData.php
2025-03-28 20:19:54 +00:00

143 lines
4.4 KiB
PHP

<?php
namespace Misuzu\Logs;
use InvalidArgumentException;
use Index\Db\{DbConnection,DbStatementCache};
use Misuzu\Pagination;
use Misuzu\Users\UserInfo;
class LogsData {
private DbStatementCache $cache;
public function __construct(DbConnection $dbConn) {
$this->cache = new DbStatementCache($dbConn);
}
public function countLogs(
UserInfo|string|null $userInfo = null,
string|null|false $remoteAddress = false,
): int {
if($userInfo instanceof UserInfo)
$userInfo = $userInfo->id;
$hasUserInfo = $userInfo !== null;
$hasRemoteAddr = $remoteAddress !== false;
$args = 0;
$query = 'SELECT COUNT(*) FROM msz_audit_log';
if($hasUserInfo) {
++$args;
$query .= ' WHERE user_id = ?';
}
if($remoteAddress !== false)
$query .= sprintf(
' %s log_remote_addr %s',
++$args > 1 ? ' AND' : ' WHERE',
$remoteAddress === null ? 'IS NULL' : '= INET6_ATON(?)'
);
$stmt = $this->cache->get($query);
if($hasUserInfo)
$stmt->nextParameter($userInfo);
if(is_string($remoteAddress))
$stmt->nextParameter($remoteAddress);
$stmt->execute();
$result = $stmt->getResult();
$count = 0;
if($result->next())
$count = $result->getInteger(0);
return $count;
}
/** @return \Iterator<int, LogInfo> */
public function getLogs(
UserInfo|string|null $userInfo = null,
string|null|false $remoteAddress = false,
?Pagination $pagination = null
): iterable {
if($userInfo instanceof UserInfo)
$userInfo = $userInfo->id;
$hasUserInfo = $userInfo !== null;
$hasPagination = $pagination !== null;
$args = 0;
$query = <<<SQL
SELECT log_id, user_id, log_action, log_params,
UNIX_TIMESTAMP(log_created),
INET6_NTOA(log_remote_addr),
log_country
FROM msz_audit_log
SQL;
if($hasUserInfo) {
++$args;
$query .= ' WHERE user_id = ?';
}
if($remoteAddress !== false)
$query .= sprintf(
' %s log_remote_addr %s',
++$args > 1 ? ' AND' : ' WHERE',
$remoteAddress === null ? 'IS NULL' : '= INET6_ATON(?)'
);
$query .= ' ORDER BY log_created DESC';
if($hasPagination)
$query .= ' LIMIT ? OFFSET ?';
$stmt = $this->cache->get($query);
if($hasUserInfo)
$stmt->nextParameter($userInfo);
if(is_string($remoteAddress))
$stmt->nextParameter($remoteAddress);
if($hasPagination)
$pagination->addToStatement($stmt);
$stmt->execute();
return $stmt->getResultIterator(LogInfo::fromResult(...));
}
/** @param mixed[] $params */
public function createLog(
UserInfo|string|null $userInfo,
string $action,
array $params = [],
?string $remoteAddress = null,
string $countryCode = 'XX',
): string {
if($userInfo instanceof UserInfo)
$userInfo = $userInfo->id;
// 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_remote_addr, log_country) VALUES (?, ?, ?, INET6_ATON(?), UPPER(?))');
$stmt->nextParameter($userInfo);
$stmt->nextParameter($action);
$stmt->nextParameter($params);
$stmt->nextParameter($remoteAddress);
$stmt->nextParameter($countryCode);
$stmt->execute();
return (string)$stmt->lastInsertId;
}
}