]> BookStack Code Mirror - bookstack/blob - app/Actions/ActivityLogger.php
Added ability to adjust stored IP address precision
[bookstack] / app / Actions / ActivityLogger.php
1 <?php
2
3 namespace BookStack\Actions;
4
5 use BookStack\Entities\Models\Entity;
6 use BookStack\Facades\Theme;
7 use BookStack\Interfaces\Loggable;
8 use BookStack\Theming\ThemeEvents;
9 use Illuminate\Database\Eloquent\Builder;
10 use Illuminate\Support\Facades\Log;
11
12 class ActivityLogger
13 {
14     /**
15      * Add a generic activity event to the database.
16      *
17      * @param string|Loggable $detail
18      */
19     public function add(string $type, $detail = '')
20     {
21         $detailToStore = ($detail instanceof Loggable) ? $detail->logDescriptor() : $detail;
22
23         $activity = $this->newActivityForUser($type);
24         $activity->detail = $detailToStore;
25
26         if ($detail instanceof Entity) {
27             $activity->entity_id = $detail->id;
28             $activity->entity_type = $detail->getMorphClass();
29         }
30
31         $activity->save();
32
33         $this->setNotification($type);
34         $this->dispatchWebhooks($type, $detail);
35         Theme::dispatch(ThemeEvents::ACTIVITY_LOGGED, $type, $detail);
36     }
37
38     /**
39      * Get a new activity instance for the current user.
40      */
41     protected function newActivityForUser(string $type): Activity
42     {
43         return (new Activity())->forceFill([
44             'type'     => strtolower($type),
45             'user_id'  => user()->id,
46             'ip'       => IpFormatter::fromCurrentRequest()->format(),
47         ]);
48     }
49
50     /**
51      * Removes the entity attachment from each of its activities
52      * and instead uses the 'extra' field with the entities name.
53      * Used when an entity is deleted.
54      */
55     public function removeEntity(Entity $entity)
56     {
57         $entity->activity()->update([
58             'detail'       => $entity->name,
59             'entity_id'    => null,
60             'entity_type'  => null,
61         ]);
62     }
63
64     /**
65      * Flashes a notification message to the session if an appropriate message is available.
66      */
67     protected function setNotification(string $type): void
68     {
69         $notificationTextKey = 'activities.' . $type . '_notification';
70         if (trans()->has($notificationTextKey)) {
71             $message = trans($notificationTextKey);
72             session()->flash('success', $message);
73         }
74     }
75
76     /**
77      * @param string|Loggable $detail
78      */
79     protected function dispatchWebhooks(string $type, $detail): void
80     {
81         $webhooks = Webhook::query()
82             ->whereHas('trackedEvents', function (Builder $query) use ($type) {
83                 $query->where('event', '=', $type)
84                     ->orWhere('event', '=', 'all');
85             })
86             ->where('active', '=', true)
87             ->get();
88
89         foreach ($webhooks as $webhook) {
90             dispatch(new DispatchWebhookJob($webhook, $type, $detail));
91         }
92     }
93
94     /**
95      * Log out a failed login attempt, Providing the given username
96      * as part of the message if the '%u' string is used.
97      */
98     public function logFailedLogin(string $username)
99     {
100         $message = config('logging.failed_login.message');
101         if (!$message) {
102             return;
103         }
104
105         $message = str_replace('%u', $username, $message);
106         $channel = config('logging.failed_login.channel');
107         Log::channel($channel)->warning($message);
108     }
109 }