Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pterodactyl
GitHub Repository: pterodactyl/panel
Path: blob/1.0-develop/app/Services/Activity/ActivityLogService.php
10266 views
1
<?php
2
3
namespace Pterodactyl\Services\Activity;
4
5
use Illuminate\Support\Arr;
6
use Webmozart\Assert\Assert;
7
use Illuminate\Support\Collection;
8
use Illuminate\Support\Facades\Log;
9
use Pterodactyl\Models\ActivityLog;
10
use Illuminate\Database\Eloquent\Model;
11
use Illuminate\Support\Facades\Request;
12
use Pterodactyl\Models\ActivityLogSubject;
13
use Illuminate\Database\ConnectionInterface;
14
use Illuminate\Contracts\Auth\Factory as AuthFactory;
15
16
class ActivityLogService
17
{
18
protected ?ActivityLog $activity = null;
19
20
protected array $subjects = [];
21
22
public function __construct(
23
protected AuthFactory $manager,
24
protected ActivityLogBatchService $batch,
25
protected ActivityLogTargetableService $targetable,
26
protected ConnectionInterface $connection,
27
) {
28
}
29
30
/**
31
* Sets the activity logger as having been caused by an anonymous
32
* user type.
33
*/
34
public function anonymous(): self
35
{
36
$this->getActivity()->actor_id = null;
37
$this->getActivity()->actor_type = null;
38
$this->getActivity()->setRelation('actor', null);
39
40
return $this;
41
}
42
43
/**
44
* Sets the action for this activity log.
45
*/
46
public function event(string $action): self
47
{
48
$this->getActivity()->event = $action;
49
50
return $this;
51
}
52
53
/**
54
* Set the description for this activity.
55
*/
56
public function description(?string $description): self
57
{
58
$this->getActivity()->description = $description;
59
60
return $this;
61
}
62
63
/**
64
* Sets the subject model instance.
65
*
66
* @template T extends \Illuminate\Database\Eloquent\Model|\Illuminate\Contracts\Auth\Authenticatable
67
*
68
* @param T|T[]|null $subjects
69
*/
70
public function subject(...$subjects): self
71
{
72
foreach (Arr::wrap($subjects) as $subject) {
73
if (is_null($subject)) {
74
continue;
75
}
76
77
foreach ($this->subjects as $entry) {
78
// If this subject is already tracked in our array of subjects just skip over
79
// it and move on to the next one in the list.
80
if ($entry->is($subject)) {
81
continue 2;
82
}
83
}
84
85
$this->subjects[] = $subject;
86
}
87
88
return $this;
89
}
90
91
/**
92
* Sets the actor model instance.
93
*/
94
public function actor(Model $actor): self
95
{
96
$this->getActivity()->actor()->associate($actor);
97
98
return $this;
99
}
100
101
/**
102
* Sets a custom property on the activity log instance.
103
*
104
* @param string|array $key
105
*/
106
public function property($key, $value = null): self
107
{
108
$properties = $this->getActivity()->properties;
109
$this->activity->properties = is_array($key)
110
? $properties->merge($key)
111
: $properties->put($key, $value);
112
113
return $this;
114
}
115
116
/**
117
* Attaches the instance request metadata to the activity log event.
118
*/
119
public function withRequestMetadata(): self
120
{
121
return $this->property([
122
'ip' => Request::getClientIp(),
123
'useragent' => Request::userAgent(),
124
]);
125
}
126
127
/**
128
* Logs an activity log entry with the set values and then returns the
129
* model instance to the caller. If there is an exception encountered while
130
* performing this action it will be logged to the disk but will not interrupt
131
* the code flow.
132
*/
133
public function log(?string $description = null): ActivityLog
134
{
135
$activity = $this->getActivity();
136
137
if (!is_null($description)) {
138
$activity->description = $description;
139
}
140
141
try {
142
return $this->save();
143
} catch (\Throwable $exception) {
144
if (config('app.env') !== 'production') {
145
/* @noinspection PhpUnhandledExceptionInspection */
146
throw $exception;
147
}
148
149
Log::error($exception);
150
}
151
152
return $activity;
153
}
154
155
/**
156
* Returns a cloned instance of the service allowing for the creation of a base
157
* activity log with the ability to change values on the fly without impact.
158
*/
159
public function clone(): self
160
{
161
return clone $this;
162
}
163
164
/**
165
* Executes the provided callback within the scope of a database transaction
166
* and will only save the activity log entry if everything else successfully
167
* settles.
168
*
169
* @param \Closure($this): mixed $callback
170
*
171
* @throws \Throwable
172
*/
173
public function transaction(\Closure $callback)
174
{
175
return $this->connection->transaction(function () use ($callback) {
176
$response = $callback($this);
177
178
$this->save();
179
180
return $response;
181
});
182
}
183
184
/**
185
* Resets the instance and clears out the log.
186
*/
187
public function reset(): void
188
{
189
$this->activity = null;
190
$this->subjects = [];
191
}
192
193
/**
194
* Returns the current activity log instance.
195
*/
196
protected function getActivity(): ActivityLog
197
{
198
if ($this->activity) {
199
return $this->activity;
200
}
201
202
$this->activity = new ActivityLog([
203
'ip' => Request::ip(),
204
'batch' => $this->batch->uuid(),
205
'properties' => Collection::make([]),
206
'api_key_id' => $this->targetable->apiKeyId(),
207
]);
208
209
if ($subject = $this->targetable->subject()) {
210
$this->subject($subject);
211
}
212
213
if ($actor = $this->targetable->actor()) {
214
$this->actor($actor);
215
} elseif (! is_null($user = $this->manager->guard()->user())) {
216
$this->actor($user);
217
}
218
219
return $this->activity;
220
}
221
222
/**
223
* Saves the activity log instance and attaches all of the subject models.
224
*
225
* @throws \Throwable
226
*/
227
protected function save(): ActivityLog
228
{
229
Assert::notNull($this->activity);
230
231
$response = $this->connection->transaction(function () {
232
$this->activity->save();
233
234
$subjects = Collection::make($this->subjects)
235
->map(fn (Model $subject) => [
236
'activity_log_id' => $this->activity->id,
237
'subject_id' => $subject->getKey(),
238
'subject_type' => $subject->getMorphClass(),
239
])
240
->values()
241
->toArray();
242
243
ActivityLogSubject::insert($subjects);
244
245
return $this->activity;
246
});
247
248
$this->activity = null;
249
$this->subjects = [];
250
251
return $response;
252
}
253
}
254
255