]> BookStack Code Mirror - bookstack/blob - app/Actions/DispatchWebhookJob.php
Added timeout and debugging statuses to webhooks
[bookstack] / app / Actions / DispatchWebhookJob.php
1 <?php
2
3 namespace BookStack\Actions;
4
5 use BookStack\Auth\User;
6 use BookStack\Entities\Models\Entity;
7 use BookStack\Facades\Theme;
8 use BookStack\Interfaces\Loggable;
9 use BookStack\Model;
10 use BookStack\Theming\ThemeEvents;
11 use Illuminate\Bus\Queueable;
12 use Illuminate\Contracts\Queue\ShouldQueue;
13 use Illuminate\Foundation\Bus\Dispatchable;
14 use Illuminate\Queue\InteractsWithQueue;
15 use Illuminate\Queue\SerializesModels;
16 use Illuminate\Support\Carbon;
17 use Illuminate\Support\Facades\Http;
18 use Illuminate\Support\Facades\Log;
19
20 class DispatchWebhookJob implements ShouldQueue
21 {
22     use Dispatchable;
23     use InteractsWithQueue;
24     use Queueable;
25     use SerializesModels;
26
27     /**
28      * @var Webhook
29      */
30     protected $webhook;
31
32     /**
33      * @var string
34      */
35     protected $event;
36
37     /**
38      * @var string|Loggable
39      */
40     protected $detail;
41
42     /**
43      * @var User
44      */
45     protected $initiator;
46
47     /**
48      * @var int
49      */
50     protected $initiatedTime;
51
52     /**
53      * Create a new job instance.
54      *
55      * @return void
56      */
57     public function __construct(Webhook $webhook, string $event, $detail)
58     {
59         $this->webhook = $webhook;
60         $this->event = $event;
61         $this->detail = $detail;
62         $this->initiator = user();
63         $this->initiatedTime = time();
64     }
65
66     /**
67      * Execute the job.
68      *
69      * @return void
70      */
71     public function handle()
72     {
73         $themeResponse = Theme::dispatch(ThemeEvents::WEBHOOK_CALL_BEFORE, $this->event, $this->webhook, $this->detail);
74         $webhookData = $themeResponse ?? $this->buildWebhookData();
75         $lastError = null;
76
77         try {
78             $response = Http::asJson()
79                 ->withOptions(['allow_redirects' => ['strict' => true]])
80                 ->timeout($this->webhook->timeout)
81                 ->post($this->webhook->endpoint, $webhookData);
82
83         } catch (\Exception $exception) {
84             $lastError = $exception->getMessage();
85             Log::error("Webhook call to endpoint {$this->webhook->endpoint} failed with error \"{$lastError}\"");
86         }
87
88         if (isset($response) && $response->failed()) {
89             $lastError = "Response status from endpoint was {$response->status()}";
90             Log::error("Webhook call to endpoint {$this->webhook->endpoint} failed with status {$response->status()}");
91         }
92
93         $this->webhook->last_called_at = now();
94         if ($lastError) {
95             $this->webhook->last_errored_at = now();
96             $this->webhook->last_error = $lastError;
97         }
98
99         $this->webhook->save();
100     }
101
102     protected function buildWebhookData(): array
103     {
104         $textParts = [
105             $this->initiator->name,
106             trans('activities.' . $this->event),
107         ];
108
109         if ($this->detail instanceof Entity) {
110             $textParts[] = '"' . $this->detail->name . '"';
111         }
112
113         $data = [
114             'event'                    => $this->event,
115             'text'                     => implode(' ', $textParts),
116             'triggered_at'             => Carbon::createFromTimestampUTC($this->initiatedTime)->toISOString(),
117             'triggered_by'             => $this->initiator->attributesToArray(),
118             'triggered_by_profile_url' => $this->initiator->getProfileUrl(),
119             'webhook_id'               => $this->webhook->id,
120             'webhook_name'             => $this->webhook->name,
121         ];
122
123         if (method_exists($this->detail, 'getUrl')) {
124             $data['url'] = $this->detail->getUrl();
125         }
126
127         if ($this->detail instanceof Model) {
128             $data['related_item'] = $this->detail->attributesToArray();
129         }
130
131         return $data;
132     }
133 }