get('user_id'); $userId = is_numeric($uid) ? (int) $uid : null; } $ip = method_exists($request, 'getIPAddress') ? $request->getIPAddress() : null; $ua = ''; if (method_exists($request, 'getUserAgent')) { $ua = substr((string) $request->getUserAgent(), 0, 500); } $oldJson = self::toJsonColumn(self::redact(self::normalize($oldValue))); $newJson = self::toJsonColumn(self::redact(self::normalize($newValue))); (new AuditLogModel())->insert([ 'workspace_id' => $workspaceId, 'user_id' => $userId, 'action' => substr($action, 0, 100), 'resource_type' => $resourceType !== null ? substr($resourceType, 0, 50) : null, 'resource_id' => $resourceId, 'old_value' => $oldJson, 'new_value' => $newJson, 'ip_address' => $ip !== '' && $ip !== null ? substr((string) $ip, 0, 45) : null, 'user_agent' => $ua !== '' ? $ua : null, ]); } catch (\Throwable $e) { log_message('error', 'AuditLogger failed: ' . $e->getMessage()); } } private static function normalize(mixed $value): mixed { if ($value === null) { return null; } if (is_scalar($value) || $value instanceof \Stringable) { return (string) $value; } if (is_array($value)) { return $value; } if (is_object($value)) { $json = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); return is_string($json) ? json_decode($json, true) : null; } return null; } private static function redact(mixed $value): mixed { if (! is_array($value)) { return $value; } $out = []; foreach ($value as $k => $v) { $key = (string) $k; if (in_array($key, self::SENSITIVE_KEYS, true)) { $out[$key] = $v !== null && $v !== '' ? '[redacted]' : null; continue; } $out[$key] = is_array($v) ? self::redact($v) : $v; } return $out; } private static function toJsonColumn(mixed $value): ?string { if ($value === null) { return null; } $json = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); return $json === false ? '{}' : $json; } }