Path: blob/1.0-develop/app/Services/Activity/ActivityLogService.php
10266 views
<?php12namespace Pterodactyl\Services\Activity;34use Illuminate\Support\Arr;5use Webmozart\Assert\Assert;6use Illuminate\Support\Collection;7use Illuminate\Support\Facades\Log;8use Pterodactyl\Models\ActivityLog;9use Illuminate\Database\Eloquent\Model;10use Illuminate\Support\Facades\Request;11use Pterodactyl\Models\ActivityLogSubject;12use Illuminate\Database\ConnectionInterface;13use Illuminate\Contracts\Auth\Factory as AuthFactory;1415class ActivityLogService16{17protected ?ActivityLog $activity = null;1819protected array $subjects = [];2021public function __construct(22protected AuthFactory $manager,23protected ActivityLogBatchService $batch,24protected ActivityLogTargetableService $targetable,25protected ConnectionInterface $connection,26) {27}2829/**30* Sets the activity logger as having been caused by an anonymous31* user type.32*/33public function anonymous(): self34{35$this->getActivity()->actor_id = null;36$this->getActivity()->actor_type = null;37$this->getActivity()->setRelation('actor', null);3839return $this;40}4142/**43* Sets the action for this activity log.44*/45public function event(string $action): self46{47$this->getActivity()->event = $action;4849return $this;50}5152/**53* Set the description for this activity.54*/55public function description(?string $description): self56{57$this->getActivity()->description = $description;5859return $this;60}6162/**63* Sets the subject model instance.64*65* @template T extends \Illuminate\Database\Eloquent\Model|\Illuminate\Contracts\Auth\Authenticatable66*67* @param T|T[]|null $subjects68*/69public function subject(...$subjects): self70{71foreach (Arr::wrap($subjects) as $subject) {72if (is_null($subject)) {73continue;74}7576foreach ($this->subjects as $entry) {77// If this subject is already tracked in our array of subjects just skip over78// it and move on to the next one in the list.79if ($entry->is($subject)) {80continue 2;81}82}8384$this->subjects[] = $subject;85}8687return $this;88}8990/**91* Sets the actor model instance.92*/93public function actor(Model $actor): self94{95$this->getActivity()->actor()->associate($actor);9697return $this;98}99100/**101* Sets a custom property on the activity log instance.102*103* @param string|array $key104*/105public function property($key, $value = null): self106{107$properties = $this->getActivity()->properties;108$this->activity->properties = is_array($key)109? $properties->merge($key)110: $properties->put($key, $value);111112return $this;113}114115/**116* Attaches the instance request metadata to the activity log event.117*/118public function withRequestMetadata(): self119{120return $this->property([121'ip' => Request::getClientIp(),122'useragent' => Request::userAgent(),123]);124}125126/**127* Logs an activity log entry with the set values and then returns the128* model instance to the caller. If there is an exception encountered while129* performing this action it will be logged to the disk but will not interrupt130* the code flow.131*/132public function log(?string $description = null): ActivityLog133{134$activity = $this->getActivity();135136if (!is_null($description)) {137$activity->description = $description;138}139140try {141return $this->save();142} catch (\Throwable $exception) {143if (config('app.env') !== 'production') {144/* @noinspection PhpUnhandledExceptionInspection */145throw $exception;146}147148Log::error($exception);149}150151return $activity;152}153154/**155* Returns a cloned instance of the service allowing for the creation of a base156* activity log with the ability to change values on the fly without impact.157*/158public function clone(): self159{160return clone $this;161}162163/**164* Executes the provided callback within the scope of a database transaction165* and will only save the activity log entry if everything else successfully166* settles.167*168* @param \Closure($this): mixed $callback169*170* @throws \Throwable171*/172public function transaction(\Closure $callback)173{174return $this->connection->transaction(function () use ($callback) {175$response = $callback($this);176177$this->save();178179return $response;180});181}182183/**184* Resets the instance and clears out the log.185*/186public function reset(): void187{188$this->activity = null;189$this->subjects = [];190}191192/**193* Returns the current activity log instance.194*/195protected function getActivity(): ActivityLog196{197if ($this->activity) {198return $this->activity;199}200201$this->activity = new ActivityLog([202'ip' => Request::ip(),203'batch' => $this->batch->uuid(),204'properties' => Collection::make([]),205'api_key_id' => $this->targetable->apiKeyId(),206]);207208if ($subject = $this->targetable->subject()) {209$this->subject($subject);210}211212if ($actor = $this->targetable->actor()) {213$this->actor($actor);214} elseif (! is_null($user = $this->manager->guard()->user())) {215$this->actor($user);216}217218return $this->activity;219}220221/**222* Saves the activity log instance and attaches all of the subject models.223*224* @throws \Throwable225*/226protected function save(): ActivityLog227{228Assert::notNull($this->activity);229230$response = $this->connection->transaction(function () {231$this->activity->save();232233$subjects = Collection::make($this->subjects)234->map(fn (Model $subject) => [235'activity_log_id' => $this->activity->id,236'subject_id' => $subject->getKey(),237'subject_type' => $subject->getMorphClass(),238])239->values()240->toArray();241242ActivityLogSubject::insert($subjects);243244return $this->activity;245});246247$this->activity = null;248$this->subjects = [];249250return $response;251}252}253254255