Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
50.00% covered (danger)
50.00%
21 / 42
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
AuditController
50.00% covered (danger)
50.00%
21 / 42
50.00% covered (danger)
50.00%
2 / 4
16.00
0.00% covered (danger)
0.00%
0 / 1
 index
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 export
20.00% covered (danger)
20.00%
3 / 15
0.00% covered (danger)
0.00%
0 / 1
4.05
 query
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 changeSummary
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3namespace App\Http\Controllers\admin;
4
5use App\Http\Controllers\Controller;
6use App\Models\User;
7use Illuminate\Http\Request;
8use Illuminate\View\View;
9use Spatie\Activitylog\Models\Activity;
10use Symfony\Component\HttpFoundation\StreamedResponse;
11
12/**
13 * People → Activity log. A plain, filterable history of who changed what.
14 * Gated by the `audit_log` feature + the view_audit_log ability.
15 */
16class AuditController extends Controller
17{
18    public function index(Request $request): View
19    {
20        $entries = $this->query($request)
21            ->with('causer')
22            ->latest()
23            ->paginate(30)
24            ->withQueryString();
25
26        return view('admin.audit.index', [
27            'entries' => $entries,
28            'logNames' => Activity::query()->distinct()->orderBy('log_name')->pluck('log_name')->filter()->values(),
29            'people' => User::query()->where('role', '1')->orderBy('name')->pluck('name', 'id'),
30            'filters' => $request->only(['q', 'log', 'causer', 'event', 'from', 'to']),
31        ]);
32    }
33
34    public function export(Request $request): StreamedResponse
35    {
36        $rows = $this->query($request)->with('causer')->latest()->limit(5000)->get();
37
38        return response()->streamDownload(function () use ($rows) {
39            $out = fopen('php://output', 'w');
40            fputcsv($out, ['When', 'Who', 'Action', 'Area', 'Details', 'Changes']);
41
42            foreach ($rows as $a) {
43                fputcsv($out, [
44                    $a->created_at?->format('Y-m-d H:i:s'),
45                    $a->causer?->name ?? 'System',
46                    $a->description,
47                    $a->log_name,
48                    $a->event,
49                    $this->changeSummary($a),
50                ]);
51            }
52
53            fclose($out);
54        }, 'activity-log-' . now()->format('Y-m-d') . '.csv', ['Content-Type' => 'text/csv']);
55    }
56
57    private function query(Request $request)
58    {
59        return Activity::query()
60            ->when($request->filled('q'), fn ($q) => $q->where('description', 'like', '%' . $request->input('q') . '%'))
61            ->when($request->filled('log'), fn ($q) => $q->where('log_name', $request->input('log')))
62            ->when($request->filled('event'), fn ($q) => $q->where('event', $request->input('event')))
63            ->when($request->filled('causer'), fn ($q) => $q->where('causer_id', $request->input('causer'))->where('causer_type', User::class))
64            ->when($request->filled('from'), fn ($q) => $q->whereDate('created_at', '>=', $request->date('from')))
65            ->when($request->filled('to'), fn ($q) => $q->whereDate('created_at', '<=', $request->date('to')));
66    }
67
68    private function changeSummary(Activity $a): string
69    {
70        $old = (array) data_get($a->properties, 'old', []);
71        $new = (array) data_get($a->properties, 'attributes', []);
72
73        $lines = [];
74        foreach ($new as $key => $value) {
75            $before = $old[$key] ?? null;
76            $lines[] = is_scalar($value) || $value === null
77                ? "{$key}" . json_encode($before) . ' → ' . json_encode($value)
78                : "{$key} changed";
79        }
80
81        return implode('; ', $lines);
82    }
83}