Initial dev export (exclude uploads/runtime)
This commit is contained in:
105
core/services/Audit.php
Normal file
105
core/services/Audit.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user