106 lines
3.4 KiB
PHP
106 lines
3.4 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace Core\Services;
|
|
|
|
use PDO;
|
|
use Throwable;
|
|
|
|
class Audit
|
|
{
|
|
public static function ensureTable(): void
|
|
{
|
|
$db = Database::get();
|
|
if (!($db instanceof PDO)) {
|
|
return;
|
|
}
|
|
try {
|
|
$db->exec("
|
|
CREATE TABLE IF NOT EXISTS ac_audit_logs (
|
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
actor_id INT UNSIGNED NULL,
|
|
actor_name VARCHAR(120) NULL,
|
|
actor_role VARCHAR(40) NULL,
|
|
action VARCHAR(120) NOT NULL,
|
|
context_json MEDIUMTEXT NULL,
|
|
ip_address VARCHAR(45) NULL,
|
|
user_agent VARCHAR(255) NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
");
|
|
} catch (Throwable $e) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
public static function log(string $action, array $context = []): void
|
|
{
|
|
$db = Database::get();
|
|
if (!($db instanceof PDO)) {
|
|
return;
|
|
}
|
|
self::ensureTable();
|
|
try {
|
|
$stmt = $db->prepare("
|
|
INSERT INTO ac_audit_logs
|
|
(actor_id, actor_name, actor_role, action, context_json, ip_address, user_agent)
|
|
VALUES
|
|
(:actor_id, :actor_name, :actor_role, :action, :context_json, :ip_address, :user_agent)
|
|
");
|
|
$stmt->execute([
|
|
':actor_id' => Auth::id() > 0 ? Auth::id() : null,
|
|
':actor_name' => Auth::name() !== '' ? Auth::name() : null,
|
|
':actor_role' => Auth::role() !== '' ? Auth::role() : null,
|
|
':action' => $action,
|
|
':context_json' => $context ? json_encode($context, JSON_UNESCAPED_SLASHES) : null,
|
|
':ip_address' => self::ip(),
|
|
':user_agent' => mb_substr((string)($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 255),
|
|
]);
|
|
} catch (Throwable $e) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
public static function latest(int $limit = 100): array
|
|
{
|
|
$db = Database::get();
|
|
if (!($db instanceof PDO)) {
|
|
return [];
|
|
}
|
|
self::ensureTable();
|
|
$limit = max(1, min(500, $limit));
|
|
try {
|
|
$stmt = $db->prepare("
|
|
SELECT id, actor_name, actor_role, action, context_json, ip_address, created_at
|
|
FROM ac_audit_logs
|
|
ORDER BY id DESC
|
|
LIMIT :limit
|
|
");
|
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
|
} catch (Throwable $e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
private static function ip(): ?string
|
|
{
|
|
$candidates = [
|
|
(string)($_SERVER['HTTP_CF_CONNECTING_IP'] ?? ''),
|
|
(string)($_SERVER['HTTP_X_FORWARDED_FOR'] ?? ''),
|
|
(string)($_SERVER['REMOTE_ADDR'] ?? ''),
|
|
];
|
|
foreach ($candidates as $candidate) {
|
|
if ($candidate === '') {
|
|
continue;
|
|
}
|
|
$first = trim(explode(',', $candidate)[0] ?? '');
|
|
if ($first !== '' && filter_var($first, FILTER_VALIDATE_IP)) {
|
|
return $first;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|