Completed
Push — master ( a7fe22...5b3d57 )
by Ehsan
02:56
created

Slackbot::verifyRequest()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4.432

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 7
cts 10
cp 0.7
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 15
nc 4
nop 0
crap 4.432
1
<?php
2
3
namespace Botonomous;
4
5
use Botonomous\listener\EventListener;
6
use Botonomous\plugin\AbstractPlugin;
7
8
/**
9
 * Class Botonomous.
10
 */
11
class Slackbot extends AbstractBot
12
{
13
    private $commands;
14
    private $lastError;
15
    private $currentCommand;
16
17
    /**
18
     * Botonomous constructor.
19
     *
20
     * @param Config|null $config
21
     *
22
     * @throws \Exception
23
     */
24 35
    public function __construct(Config $config = null)
25
    {
26 35
        if ($config !== null) {
27 4
            $this->setConfig($config);
28
        }
29
30
        // set timezone
31 35
        date_default_timezone_set($this->getConfig()->get('timezone'));
32 35
    }
33
34
    /**
35
     * @param null|string $key
36
     *
37
     * @return mixed
38
     */
39 8
    public function getRequest($key = null)
40
    {
41 8
        return $this->getListener()->getRequest($key);
42
    }
43
44
    /**
45
     * @return string|null
46
     */
47 7
    private function determineAction()
48
    {
49 7
        $utility = $this->getRequestUtility();
50 7
        $getRequest = $utility->getGet();
51
52 7
        if (!empty($getRequest['action'])) {
53 2
            return strtolower($getRequest['action']);
54
        }
55
56 5
        $request = $utility->getPostedBody();
57
58 5
        if (isset($request['type']) && $request['type'] === 'url_verification') {
59 1
            return 'url_verification';
60
        }
61 4
    }
62
63
    /**
64
     * @return null|AbstractBaseSlack
65
     */
66 1
    private function handleMessageActions()
67
    {
68 1
        $post = $this->getRequestUtility()->getPost();
69
70
        // ignore if payload is not set
71 1
        if (!isset($post['payload'])) {
72
            /* @noinspection PhpInconsistentReturnPointsInspection */
73 1
            return;
74
        }
75
76
        // posted payload is in JSON
77 1
        $payload = json_decode($post['payload'], true);
78
79 1
        return (new MessageAction())->load($payload);
80
    }
81
82
    /**
83
     * @throws \Exception
84
     */
85 4
    private function handleSendResponse()
86
    {
87
        // 1. Start listening
88 4
        $this->getListener()->listen();
89
90
        // 2. verify the request
91
        try {
92 4
            $verificationResult = $this->verifyRequest();
93
94 4
            if ($verificationResult['success'] !== true) {
95 4
                throw new \Exception($verificationResult['message']);
96
            }
97 1
        } catch (\Exception $e) {
98 1
            throw $e;
99
        }
100
101
        // 3. pre process the request
102 3
        $this->preProcessRequest();
103
104
        // 4. check access control
105 3
        if ($this->checkAccessControl() !== true) {
106 2
            return;
107
        }
108
109
        // 5. set the current command
110 1
        $message = $this->getMessage();
111 1
        $this->setCurrentCommand($this->getMessageUtility()->extractCommandName($message));
112
113
        // 6. log the message
114 1
        if (empty($this->getRequest('debug'))) {
115 1
            $this->getLoggerUtility()->logRaw($this->getFormattingUtility()->newLine());
116 1
            $this->getLoggerUtility()->logChat(__METHOD__, $message);
117
        }
118
119 1
        $this->getLoggerUtility()->logRaw($this->getRequestUtility()->getContent());
120
121
        // 7. send confirmation message if is enabled
122 1
        $this->getSender()->sendConfirmation();
123
124
        // 8. And send the response to the channel, only if the response is not empty
125 1
        $response = $this->respond($message);
126
127 1
        if (!empty($response)) {
128 1
            $this->getSender()->send($response);
129
        }
130 1
    }
131
132
    /**
133
     * @return bool
134
     */
135 3
    private function checkAccessControl()
136
    {
137
        // if accessControlEnabled is not set true ignore the check and return true
138 3
        if ($this->getConfig()->get('accessControlEnabled') !== true) {
139 1
            return true;
140
        }
141
142 2
        if ($this->getBlackList()->isBlackListed() !== false) {
143
            // found in blacklist
144 1
            $this->getSender()->send($this->getConfig()->get('blacklistedMessage'));
145
146 1
            return false;
147
        }
148
149 1
        if ($this->getWhiteList()->isWhiteListed() !== true) {
150
            // not found in whitelist
151 1
            $this->getSender()->send($this->getConfig()->get('whitelistedMessage'));
152
153 1
            return false;
154
        }
155
156
        return true;
157
    }
158
159
    /**
160
     * @throws \Exception
161
     */
162 7
    public function run()
163
    {
164 7
        switch ($this->determineAction()) {
165 7
            case 'oauth':
166 1
                return $this->handleOAuth();
167 6
            case 'message_actions':
168 1
                return $this->handleMessageActions();
169 5
            case 'url_verification':
170 1
                return $this->handleUrlVerification();
171
            default:
172 4
                return $this->handleSendResponse();
173
        }
174
    }
175
176
    /**
177
     * handle OAuth.
178
     */
179 1
    private function handleOAuth()
180
    {
181 1
        return $this->getOauth()->doOauth();
182
    }
183
184
    /**
185
     * @throws \Exception
186
     *
187
     * @return mixed
188
     */
189 1
    private function handleUrlVerification()
190
    {
191 1
        $request = $this->getRequestUtility()->getPostedBody();
192
193 1
        if (empty($request['challenge'])) {
194
            throw new \Exception('Challenge is missing for URL verification');
195
        }
196
197 1
        echo $request['challenge'];
198 1
    }
199
200
    /**
201
     * Pre-process the request.
202
     */
203 3
    private function preProcessRequest()
204
    {
205 3
        $request = $this->getListener()->getRequest();
206
207
        // remove the trigger_word from beginning of the message
208 3
        if (!empty($request['trigger_word'])) {
209 3
            $request['text'] = $this->getMessageUtility()->removeTriggerWord(
210 3
                $request['text'],
211 3
                $request['trigger_word']
212
            );
213
214 3
            $this->getListener()->setRequest($request);
215
        }
216 3
    }
217
218
    /**
219
     * @param null $message
220
     *
221
     * @throws \Exception
222
     *
223
     * @return mixed
224
     */
225 4
    public function respond($message = null)
226
    {
227
        try {
228 4
            $command = $this->getCommandByMessage($message);
229
230 4
            if (!$command instanceof Command) {
231
                // something went wrong, error will tell us!
232 2
                return $this->getLastError();
233
            }
234
235
            // create the class
236 3
            $pluginClassFile = $command->getClass();
237 3
            $pluginClass = new $pluginClassFile($this);
238
239
            // check class is valid
240 3
            if (!$pluginClass instanceof AbstractPlugin) {
241
                throw new \Exception("Couldn't create class: '{$pluginClassFile}'");
242
            }
243
244
            // check action exists
245 3
            $action = $command->getAction();
246 3
            if (!method_exists($pluginClass, $action)) {
247 1
                throw new \Exception("Action / function: '{$action}' does not exist in '{$pluginClassFile}'");
248
            }
249
250 2
            return $pluginClass->$action();
251 1
        } catch (\Exception $e) {
252 1
            throw $e;
253
        }
254
    }
255
256
    /**
257
     * @param null $message
258
     *
259
     * @throws \Exception
260
     *
261
     * @return bool|Command
262
     */
263 7
    public function getCommandByMessage($message = null)
264
    {
265
        // If message is not set, get it from the current request
266 7
        if ($message === null) {
267 4
            $message = $this->getMessage();
268
        }
269
270 7
        if (empty($message)) {
271 1
            $this->setLastError('Message is empty');
272
273 1
            return false;
274
        }
275
276
        /**
277
         * Process the message.
278
         */
279 6
        $command = $this->getMessageUtility()->extractCommandName($message);
280
281 6
        $config = $this->getConfig();
282
283
        // check command name
284 6
        if (empty($command)) {
285
            // get the default command if no command is find in the message
286 2
            $command = $config->get('defaultCommand');
287
288 2
            if (empty($command)) {
289 2
                $this->setLastError($config->get('noCommandMessage'));
290
291 2
                return false;
292
            }
293
        }
294
295 4
        $commandObject = $this->getCommandContainer()->getAsObject($command);
296
297
        // check command details
298 4
        if (empty($commandObject)) {
299 1
            $this->setLastError($config->get('unknownCommandMessage', ['command' => $command]));
300
301 1
            return false;
302
        }
303
304 4
        if (!$commandObject instanceof Command) {
305
            throw new \Exception('Command is not an object');
306
        }
307
308
        // check the plugin for the command
309 4
        if (empty($commandObject->getPlugin())) {
310
            throw new \Exception('Plugin is not set for this command');
311
        }
312
313 4
        return $commandObject;
314
    }
315
316
    /**
317
     * @throws \Exception
318
     *
319
     * @return array<string,boolean|string>
320
     */
321 4
    private function verifyRequest()
322
    {
323 4
        $originCheck = $this->getListener()->verifyOrigin();
324
325 4
        if (!isset($originCheck['success'])) {
326
            throw new \Exception('Success must be provided in verifyOrigin response');
327
        }
328
329 4
        if ($originCheck['success'] !== true) {
330
            return [
331
                'success' => false,
332
                'message' => $originCheck['message'],
333
            ];
334
        }
335
336 4
        if ($this->getListener()->isThisBot() !== false) {
337
            return [
338 1
                'success' => false,
339
                'message' => 'Request comes from the bot',
340
            ];
341
        }
342
343
        return [
344 3
            'success' => true,
345
            'message' => 'Yay!',
346
        ];
347
    }
348
349
    /**
350
     * @return array
351
     */
352 2
    public function getCommands()
353
    {
354 2
        if (!isset($this->commands)) {
355 1
            $this->setCommands($this->getCommandContainer()->getAllAsObject());
356
        }
357
358 2
        return $this->commands;
359
    }
360
361
    /**
362
     * @param array $commands
363
     */
364 2
    public function setCommands(array $commands)
365
    {
366 2
        $this->commands = $commands;
367 2
    }
368
369
    /**
370
     * @return string
371
     */
372 4
    public function getLastError()
373
    {
374 4
        return $this->lastError;
375
    }
376
377
    /**
378
     * @param string $lastError
379
     */
380 4
    public function setLastError($lastError)
381
    {
382 4
        $this->lastError = $lastError;
383 4
    }
384
385
    /**
386
     * @return string
387
     */
388 1
    public function getCurrentCommand()
389
    {
390 1
        return $this->currentCommand;
391
    }
392
393
    /**
394
     * @param string $currentCommand
395
     */
396 2
    public function setCurrentCommand($currentCommand)
397
    {
398 2
        $this->currentCommand = $currentCommand;
399 2
    }
400
401
    /**
402
     * Return message based on the listener
403
     * If listener is event and event text is empty, fall back to request text.
404
     *
405
     * @return mixed|string
406
     */
407 11
    public function getMessage()
408
    {
409 11
        $listener = $this->getListener();
410 11
        if ($listener instanceof EventListener && $listener->getEvent() instanceof Event) {
411 1
            $message = $listener->getEvent()->getText();
412
413 1
            if (!empty($message)) {
414 1
                return $message;
415
            }
416
        }
417
418 10
        return $listener->getRequest('text');
419
    }
420
421
    /**
422
     * Determine if bot user id is mentioned in the message.
423
     *
424
     * @return bool
425
     */
426 1
    public function youTalkingToMe()
427
    {
428 1
        $message = $this->getMessage();
429
430 1
        if (empty($message)) {
431 1
            return false;
432
        }
433
434 1
        if ($this->getMessageUtility()->isBotMentioned($message) === true) {
435 1
            return true;
436
        }
437
438 1
        $listener = $this->getListener();
439
        // check direct messages
440 1
        return $listener instanceof EventListener && $listener->getEvent()->isDirectMessage() === true;
441
    }
442
}
443