220 lines
8.3 KiB
PHP
220 lines
8.3 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use CodeIgniter\Model;
|
|
|
|
class ActivityLogModel extends Model
|
|
{
|
|
protected $table = 'activity_logs';
|
|
protected $primaryKey = 'id';
|
|
protected $useAutoIncrement = true;
|
|
protected $returnType = 'array';
|
|
protected $useSoftDeletes = false;
|
|
protected $protectFields = true;
|
|
protected $allowedFields = ['ip', 'action', 'description', 'activity_user_id', 'activity_user_type', 'target_user_id', 'target_user_type', 'activity_page', 'activity_at'];
|
|
|
|
protected bool $allowEmptyInserts = false;
|
|
protected bool $updateOnlyChanged = true;
|
|
|
|
protected array $casts = [];
|
|
protected array $castHandlers = [];
|
|
|
|
protected $useTimestamps = false;
|
|
protected $dateFormat = 'datetime';
|
|
protected $createdField = 'activity_at';
|
|
protected $updatedField = 'updated_at';
|
|
protected $deletedField = 'deleted_at';
|
|
|
|
protected $validationRules = [];
|
|
protected $validationMessages = [];
|
|
protected $skipValidation = false;
|
|
protected $cleanValidationRules = true;
|
|
|
|
protected $allowCallbacks = true;
|
|
protected $beforeInsert = [];
|
|
protected $afterInsert = [];
|
|
protected $beforeUpdate = [];
|
|
protected $afterUpdate = [];
|
|
protected $beforeFind = [];
|
|
protected $afterFind = [];
|
|
protected $beforeDelete = [];
|
|
protected $afterDelete = [];
|
|
|
|
public function log(string $action, ?string $description = null, ?string $targetType = null, ?int $targetId = null, ?int $actorId = null, ?string $actorRole = null, ?string $ipAddress = null, ?string $userAgent = null): bool
|
|
{
|
|
$actorId = $actorId ?? session()->get('id');
|
|
$actorRole = $actorRole ?? session()->get('role') ?? 'guest';
|
|
|
|
$request = service('request');
|
|
$ipAddress = $ipAddress ?? ($request->hasHeader('X-Forwarded-For') ? trim(explode(',', $request->getHeaderLine('X-Forwarded-For'))[0]) : $request->getIPAddress());
|
|
$userAgent = $userAgent ?? ($request->hasHeader('User-Agent') ? $request->getHeaderLine('User-Agent') : null);
|
|
$validTargetTypes = ['admin', 'doctor', 'patient'];
|
|
$targetUserType = in_array($targetType, $validTargetTypes, true) ? $targetType : null;
|
|
|
|
$data = [
|
|
'ip' => $ipAddress,
|
|
'action' => $action,
|
|
'description' => $description,
|
|
'activity_user_id' => $actorId,
|
|
'activity_user_type' => $actorRole,
|
|
'target_user_id' => $targetId,
|
|
'target_user_type' => $targetUserType,
|
|
'activity_page' => $request->getPath(),
|
|
'activity_at' => date('Y-m-d H:i:s'),
|
|
];
|
|
|
|
try {
|
|
$result = $this->insert($data);
|
|
if (!$result) {
|
|
log_message('error', 'Failed to insert activity log: ' . json_encode($data) . ' - Errors: ' . json_encode($this->errors()));
|
|
}
|
|
return (bool) $result;
|
|
} catch (\Exception $e) {
|
|
log_message('error', 'Exception in ActivityLogModel::log: ' . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function getFilteredLogs(array $filters = [], int $limit = 200): array
|
|
{
|
|
$builder = $this->select('activity_logs.*, COALESCE(NULLIF(CONCAT(users.first_name, " ", users.last_name), " "), users.email, "Guest") AS actor_name')
|
|
->join('users', 'users.id = activity_logs.activity_user_id', 'left');
|
|
|
|
// Apply filters
|
|
if (!empty($filters['search'])) {
|
|
$search = $filters['search'];
|
|
$builder->groupStart()
|
|
->like('users.first_name', $search)
|
|
->orLike('users.last_name', $search)
|
|
->orLike('users.email', $search)
|
|
->orLike('activity_logs.description', $search)
|
|
->groupEnd();
|
|
}
|
|
|
|
if (!empty($filters['action'])) {
|
|
$builder->where('activity_logs.action', $filters['action']);
|
|
}
|
|
|
|
if (!empty($filters['user_type'])) {
|
|
$builder->where('activity_logs.activity_user_type', $filters['user_type']);
|
|
}
|
|
|
|
if (!empty($filters['date_from'])) {
|
|
$builder->where('activity_logs.activity_at >=', $filters['date_from'] . ' 00:00:00');
|
|
}
|
|
|
|
if (!empty($filters['date_to'])) {
|
|
$builder->where('activity_logs.activity_at <=', $filters['date_to'] . ' 23:59:59');
|
|
}
|
|
|
|
if (!empty($filters['ip'])) {
|
|
$builder->where('activity_logs.ip', $filters['ip']);
|
|
}
|
|
|
|
return $builder->orderBy('activity_logs.activity_at', 'DESC')
|
|
->findAll($limit);
|
|
}
|
|
|
|
public function getAvailableActions(): array
|
|
{
|
|
$rows = $this->select('action')
|
|
->distinct()
|
|
->orderBy('action', 'ASC')
|
|
->findAll();
|
|
|
|
return array_column($rows, 'action');
|
|
}
|
|
|
|
public function getActionSummary(): array
|
|
{
|
|
$rows = $this->select('action, COUNT(*) AS count')
|
|
->groupBy('action')
|
|
->orderBy('count', 'DESC')
|
|
->findAll();
|
|
|
|
return array_column($rows, 'count', 'action');
|
|
}
|
|
|
|
public function getRoleSummary(): array
|
|
{
|
|
$rows = $this->select('activity_user_type AS actor_role, COUNT(*) AS count')
|
|
->groupBy('activity_user_type')
|
|
->orderBy('count', 'DESC')
|
|
->findAll();
|
|
|
|
return array_column($rows, 'count', 'actor_role');
|
|
}
|
|
|
|
public function getSummary(string $period = '7_days'): array
|
|
{
|
|
$period = $period === '30_days' ? '30_days' : '7_days';
|
|
$startDate = $period === '30_days' ? date('Y-m-d H:i:s', strtotime('-30 days')) : date('Y-m-d H:i:s', strtotime('-7 days'));
|
|
|
|
return [
|
|
'total_actions' => (int) $this->where('activity_at >=', $startDate)->countAllResults(false),
|
|
'by_action' => $this->getActionSummaryForPeriod($startDate),
|
|
'by_role' => $this->getRoleSummaryForPeriod($startDate),
|
|
'most_active_users' => $this->getMostActiveUsers($startDate),
|
|
'unique_ips' => $this->getUniqueIPs($startDate),
|
|
];
|
|
}
|
|
|
|
protected function getActionSummaryForPeriod(string $startDate): array
|
|
{
|
|
$rows = $this->select('action, COUNT(*) AS count')
|
|
->where('activity_at >=', $startDate)
|
|
->groupBy('action')
|
|
->orderBy('count', 'DESC')
|
|
->findAll();
|
|
|
|
return array_column($rows, 'count', 'action');
|
|
}
|
|
|
|
protected function getRoleSummaryForPeriod(string $startDate): array
|
|
{
|
|
$rows = $this->select('activity_user_type AS actor_role, COUNT(*) AS count')
|
|
->where('activity_at >=', $startDate)
|
|
->groupBy('activity_user_type')
|
|
->orderBy('count', 'DESC')
|
|
->findAll();
|
|
|
|
return array_column($rows, 'count', 'actor_role');
|
|
}
|
|
|
|
protected function getMostActiveUsers(string $startDate, int $limit = 10): array
|
|
{
|
|
return $this->select('COALESCE(NULLIF(TRIM(CONCAT(users.first_name, " ", users.last_name)), ""), users.email, "Guest") AS actor, COUNT(activity_logs.id) AS count')
|
|
->join('users', 'users.id = activity_logs.activity_user_id', 'left')
|
|
->where('activity_logs.activity_at >=', $startDate)
|
|
->groupBy('activity_logs.activity_user_id')
|
|
->orderBy('count', 'DESC')
|
|
->findAll($limit);
|
|
}
|
|
|
|
protected function getUniqueIPs(string $startDate, int $limit = 10): array
|
|
{
|
|
return $this->select('ip, COUNT(*) AS count')
|
|
->where('activity_at >=', $startDate)
|
|
->groupBy('ip')
|
|
->orderBy('count', 'DESC')
|
|
->findAll($limit);
|
|
}
|
|
|
|
public function getCriticalActions(int $limit = 50): array
|
|
{
|
|
$criticalActions = ['admin_login', 'admin_password_change', 'system_error', 'security_breach'];
|
|
|
|
return $this->select('activity_logs.*, COALESCE(NULLIF(TRIM(CONCAT(users.first_name, " ", users.last_name)), ""), users.email, "System") AS actor_name, users.email AS actor_email')
|
|
->join('users', 'users.id = activity_logs.activity_user_id', 'left')
|
|
->whereIn('action', $criticalActions)
|
|
->orderBy('activity_at', 'DESC')
|
|
->findAll($limit);
|
|
}
|
|
|
|
public function clearAll(): bool
|
|
{
|
|
return $this->truncate();
|
|
}
|
|
}
|