]> BookStack Code Mirror - bookstack/blob - app/Activity/Tools/WebhookFormatter.php
Webhooks: Fixed failing delete-based events
[bookstack] / app / Activity / Tools / WebhookFormatter.php
1 <?php
2
3 namespace BookStack\Activity\Tools;
4
5 use BookStack\Activity\ActivityType;
6 use BookStack\Activity\Models\Loggable;
7 use BookStack\Activity\Models\Webhook;
8 use BookStack\App\Model;
9 use BookStack\Entities\Models\Entity;
10 use BookStack\Entities\Models\Page;
11 use BookStack\Users\Models\User;
12 use Illuminate\Support\Carbon;
13
14 class WebhookFormatter
15 {
16     protected Webhook $webhook;
17     protected string $event;
18     protected User $initiator;
19     protected int $initiatedTime;
20     protected string|Loggable $detail;
21
22     /**
23      * @var array{condition: callable(string, Model):bool, format: callable(Model):void}[]
24      */
25     protected $modelFormatters = [];
26
27     public function __construct(string $event, Webhook $webhook, string|Loggable $detail, User $initiator, int $initiatedTime)
28     {
29         $this->webhook = $webhook;
30         $this->event = $event;
31         $this->initiator = $initiator;
32         $this->initiatedTime = $initiatedTime;
33         $this->detail = is_object($detail) ? clone $detail : $detail;
34     }
35
36     public function format(): array
37     {
38         $data = [
39             'event'                    => $this->event,
40             'text'                     => $this->formatText(),
41             'triggered_at'             => Carbon::createFromTimestampUTC($this->initiatedTime)->toISOString(),
42             'triggered_by'             => $this->initiator->attributesToArray(),
43             'triggered_by_profile_url' => $this->initiator->getProfileUrl(),
44             'webhook_id'               => $this->webhook->id,
45             'webhook_name'             => $this->webhook->name,
46         ];
47
48         if (method_exists($this->detail, 'getUrl')) {
49             $data['url'] = $this->detail->getUrl();
50         }
51
52         if ($this->detail instanceof Model) {
53             $data['related_item'] = $this->formatModel();
54         }
55
56         return $data;
57     }
58
59     /**
60      * @param callable(string, Model):bool $condition
61      * @param callable(Model):void         $format
62      */
63     public function addModelFormatter(callable $condition, callable $format): void
64     {
65         $this->modelFormatters[] = [
66             'condition' => $condition,
67             'format'    => $format,
68         ];
69     }
70
71     public function addDefaultModelFormatters(): void
72     {
73         // Load entity owner, creator, updater details
74         $this->addModelFormatter(
75             fn ($event, $model) => ($model instanceof Entity),
76             fn ($model) => $model->load(['ownedBy', 'createdBy', 'updatedBy'])
77         );
78
79         // Load revision detail for page update and create events
80         $this->addModelFormatter(
81             fn ($event, $model) => ($model instanceof Page && ($event === ActivityType::PAGE_CREATE || $event === ActivityType::PAGE_UPDATE)),
82             fn ($model) => $model->load('currentRevision')
83         );
84     }
85
86     protected function formatModel(): array
87     {
88         /** @var Model $model */
89         $model = $this->detail;
90         $model->unsetRelations();
91
92         foreach ($this->modelFormatters as $formatter) {
93             if ($formatter['condition']($this->event, $model)) {
94                 $formatter['format']($model);
95             }
96         }
97
98         return $model->toArray();
99     }
100
101     protected function formatText(): string
102     {
103         $textParts = [
104             $this->initiator->name,
105             trans('activities.' . $this->event),
106         ];
107
108         if ($this->detail instanceof Entity) {
109             $textParts[] = '"' . $this->detail->name . '"';
110         }
111
112         return implode(' ', $textParts);
113     }
114
115     public static function getDefault(string $event, Webhook $webhook, $detail, User $initiator, int $initiatedTime): self
116     {
117         $instance = new self($event, $webhook, $detail, $initiator, $initiatedTime);
118         $instance->addDefaultModelFormatters();
119
120         return $instance;
121     }
122 }