WebHook::getMessageContent()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 36
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 23
c 2
b 0
f 0
nc 1
nop 2
dl 0
loc 36
rs 9.552
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Quantum\Hub;
6
7
use DateTime;
8
use Exception;
9
use Platine\Config\Config;
10
use Platine\Http\Client\HttpClient;
11
use Platine\Http\ServerRequest;
12
use Platine\Http\ServerRequestInterface;
13
use Platine\Stdlib\Helper\Arr;
14
use Platine\Stdlib\Helper\Json;
15
use Platine\Stdlib\Helper\Str;
16
use Quantum\Hub\Entity\Commit;
17
use Quantum\Hub\Entity\Repository;
18
use Quantum\Hub\Entity\Sender;
19
20
/**
21
 * @class WebHook
22
 * @package Quantum\Hub
23
 * @template T
24
 */
25
class WebHook
26
{
27
    /**
28
     * The request instance to use
29
     * @var ServerRequestInterface
30
     */
31
    protected ServerRequestInterface $request;
32
33
    /**
34
     * The payload data
35
     * @var array<string, mixed>
36
     */
37
    protected array $payload = [];
38
39
    /**
40
     * Create new instance
41
     * @param Config<T> $config
42
     * @param ServerRequestInterface|null $request
43
     */
44
    public function __construct(
45
        protected Config $config,
46
        ?ServerRequestInterface $request = null,
47
    ) {
48
        if ($request === null) {
49
            $this->request = ServerRequest::createFromGlobals();
50
        }
51
    }
52
53
54
    /**
55
     * Handle the request
56
     * @return void
57
     */
58
    public function handle(): void
59
    {
60
        $this->processPayload();
61
62
        $repository = $this->getRepository();
63
        $commit = $this->getCommit();
64
        $disabledRepos = $this->getDisabledRepositories();
65
        if (
66
            $this->getEvent() !== 'push' ||
67
            $commit === null ||
68
            in_array(
69
                $repository->getFullName(),
70
                $disabledRepos
71
            )
72
        ) {
73
            return;
74
        }
75
76
        // Now send the message
77
        $this->sendMessage($commit, $repository);
78
    }
79
80
    /**
81
     * Return the commit branch
82
     * @return string
83
     */
84
    public function getBranch(): string
85
    {
86
        $value = $this->getPayloadValue('ref');
87
        return Arr::last(explode('/', $value));
88
    }
89
90
    /**
91
     * Return the GitHub Event. Currently only push is supported
92
     * @return string
93
     */
94
    public function getEvent(): string
95
    {
96
        return $this->request->getHeaderLine('X-GitHub-Event');
97
    }
98
99
    /**
100
     * Return the value of the payload
101
     * @param string $name
102
     * @param mixed $default
103
     * @return mixed
104
     */
105
    public function getPayloadValue(
106
        string $name,
107
        mixed $default = null
108
    ): mixed {
109
        return Arr::get(
110
            $this->payload,
111
            $name,
112
            $default
113
        );
114
    }
115
116
    /**
117
     * Return the repository info
118
     * @return Repository
119
     */
120
    public function getRepository(): Repository
121
    {
122
        $data = $this->getPayloadValue('repository', []);
123
124
        return new Repository($data);
125
    }
126
127
     /**
128
     * Return the sender (user who make push) info
129
     * @return Sender
130
     */
131
    public function getSender(): Sender
132
    {
133
        $data = $this->getPayloadValue('sender', []);
134
135
        return new Sender($data);
136
    }
137
138
    /**
139
     * Return the commit information if have
140
     * @return Commit
141
     */
142
    public function getCommit(): ?Commit
143
    {
144
        $data = $this->getPayloadValue('commits', []);
145
        if (count($data) === 0) {
146
            return null;
147
        }
148
149
        return new Commit($data[0]);
150
    }
151
152
    /**
153
     * Get message content
154
     * @param Commit $commit
155
     * @param Repository $repository
156
     * @return string
157
     */
158
    public function getMessageContent(
159
        Commit $commit,
160
        Repository $repository
161
    ): string {
162
        $sender = $this->getSender();
163
164
        $text = sprintf(
165
            "<a href = \"%s\"><b>@%s</b></a> just push to repository <a href = \"%s\"><b>%s</b></a>%s%s",
166
            $sender->getHtmlUrl(),
167
            $sender->getLogin(),
168
            $repository->getHtmlUrl(),
169
            $repository->getFullName(),
170
            PHP_EOL,
171
            PHP_EOL
172
        );
173
174
        $date = new DateTime($commit->getTimestamp());
175
176
        $text .= $this->buildMessageRow('Description', $commit->getMessage(), 2);
177
        $text .= $this->buildMessageRow('Date', $date->format('Y-m-d H:i:s'));
178
        $text .= $this->buildMessageRow('Branch/Tag', $this->getBranch());
179
        $text .= $this->buildMessageRow(
180
            'Commit ID',
181
            sprintf(
182
                '<a href = "%s"><b>%s</b></a>',
183
                $commit->getUrl(),
184
                $commit->getId()
185
            )
186
        );
187
188
        $text .= $this->getCommitFilesChangeMessage($commit->getAdded(), 'added');
189
        $text .= $this->getCommitFilesChangeMessage($commit->getModified(), 'modified');
190
        $text .= $this->getCommitFilesChangeMessage($commit->getRemoved(), 'deleted');
191
192
193
        return $text;
194
    }
195
196
    /**
197
     * Return the list of repository that is disabled
198
     * @return array<string>
199
     */
200
    protected function getDisabledRepositories(): array
201
    {
202
        $params = $this->request->getQueryParams();
203
        $repositories = $params['disable_repos'] ?? '';
204
        if (empty($repositories)) {
205
            return [];
206
        }
207
208
        return explode(',', $repositories);
209
    }
210
211
    /**
212
     * Process the payload
213
     * @return void
214
     */
215
    protected function processPayload(): void
216
    {
217
        $body = $this->request->getBody()->getContents();
218
        if (!empty($body)) {
219
            $payload = Json::decode($body, true);
220
221
            $this->payload = $payload;
222
        }
223
    }
224
225
    /**
226
     * Return the commit message files changes (added/removed/modified)
227
     * @param array<string> $files
228
     * @param string $type
229
     * @return string
230
     */
231
    protected function getCommitFilesChangeMessage(array $files, string $type): string
232
    {
233
        if (count($files) === 0) {
234
            return '';
235
        }
236
237
        // Use double quote in order to format EOL
238
        $text = sprintf("%s<b>Files %s:</b>%s", PHP_EOL, $type, PHP_EOL);
239
        $text .= sprintf("- %s %s", implode(PHP_EOL . '- ', $files), PHP_EOL);
240
241
        return $text;
242
    }
243
244
    /**
245
     * Build the message row
246
     * @param string $label
247
     * @param string $value
248
     * @param int $eolCount
249
     * @return string
250
     */
251
    protected function buildMessageRow(
252
        string $label,
253
        string $value,
254
        int $eolCount = 1
255
    ): string {
256
        return sprintf(
257
            "<b>%s:</b> %s%s",
258
            $label,
259
            $value,
260
            Str::repeat(PHP_EOL, $eolCount)
261
        );
262
    }
263
264
    /**
265
     * Return the chat id and bot token to be used
266
     * @param Repository $repository
267
     * @return array{chat:string, token:string}|null
268
     */
269
    protected function getConfigInfo(Repository $repository): ?array
270
    {
271
        $organization = $repository->getOrganization();
272
        $repoName = $repository->getName();
273
        $configKey = 'config.organization.%s.%s';
274
275
        // Check for one repository configuration
276
        $repoConfig = $this->config->get(
277
            sprintf($configKey, $organization, $repoName),
278
            []
279
        );
280
        if (count($repoConfig) === 2) {
281
            return [
282
                'chat' => $repoConfig[0],
283
                'token' => $repoConfig[1],
284
            ];
285
        }
286
287
        // Default
288
        $allConfig = $this->config->get(
289
            sprintf($configKey, $organization, '*'),
290
            []
291
        );
292
        if (count($allConfig) === 2) {
293
            return [
294
                'chat' => $allConfig[0],
295
                'token' => $allConfig[1],
296
            ];
297
        }
298
299
        return null;
300
    }
301
302
    /**
303
     * Send the message
304
     * @param Commit $commit
305
     * @param Repository $repository
306
     * @return bool
307
     */
308
    protected function sendMessage(
309
        Commit $commit,
310
        Repository $repository
311
    ): bool {
312
        $config = $this->getConfigInfo($repository);
313
        if ($config === null) {
314
            return false;
315
        }
316
317
        $message = $this->getMessageContent($commit, $repository);
318
        if (empty($message)) {
319
            return false;
320
        }
321
322
        $url = sprintf('https://api.telegram.org/bot%s/sendMessage', $config['token']);
323
        $params = [
324
            'chat_id' => $config['chat'],
325
            'parse_mode' => 'HTML',
326
            'text' => $message,
327
        ];
328
329
        $client = new HttpClient($url);
330
        $client->json()
331
               ->verifySslCertificate(false);
332
        try {
333
            $response = $client->post('', $params);
334
335
            if ($response->isError()) {
336
                error_log($response->getError());
337
338
                return false;
339
            }
340
341
            return true;
342
        } catch (Exception $ex) {
343
            error_log($ex->getMessage());
344
            return false;
345
        }
346
    }
347
}
348