Passed
Push — master ( b808b9...919917 )
by Ehsan
04:54
created

Slackbot::getLastError()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
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 9
    public function getRequest($key = null)
40
    {
41 9
        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
        // 7. send confirmation message if is enabled
120 1
        $this->sendConfirmation();
121
122
        // 8. And send the response to the channel, only if the response is not empty
123 1
        $response = $this->respond($message);
124
125 1
        if (!empty($response)) {
126 1
            $this->getSender()->send($this->getRequest('channel_name'), $response);
127
        }
128 1
    }
129
130
    /**
131
     * @return bool
132
     */
133 3
    private function checkAccessControl()
134
    {
135
        // if enabledAccessControl is not set true ignore the check and return true
136 3
        if ($this->getConfig()->get('enabledAccessControl') !== true) {
137 1
            return true;
138
        }
139
140 2
        if ($this->getBlackList()->isBlackListed() !== false) {
141
            // found in blacklist
142 1
            $this->getSender()->send($this->getRequest('channel_name'), $this->getConfig()->get('blacklistedMessage'));
143
144 1
            return false;
145
        }
146
147 1
        if ($this->getWhiteList()->isWhiteListed() !== true) {
148
            // not found in whitelist
149 1
            $this->getSender()->send($this->getRequest('channel_name'), $this->getConfig()->get('whitelistedMessage'));
150
151 1
            return false;
152
        }
153
154
        return true;
155
    }
156
157
    /**
158
     * @throws \Exception
159
     */
160 7
    public function run()
161
    {
162 7
        switch ($this->determineAction()) {
163 7
            case 'oauth':
164 1
                return $this->handleOAuth();
165 6
            case 'message_actions':
166 1
                return $this->handleMessageActions();
167 5
            case 'url_verification':
168 1
                return $this->handleUrlVerification();
169
            default:
170 4
                return $this->handleSendResponse();
171
        }
172
    }
173
174
    /**
175
     * handle OAuth.
176
     */
177 1
    private function handleOAuth()
178
    {
179 1
        return $this->getOauth()->doOauth();
180
    }
181
182
    /**
183
     * @throws \Exception
184
     *
185
     * @return mixed
186
     */
187 1
    private function handleUrlVerification()
188
    {
189 1
        $request = $this->getRequestUtility()->getPostedBody();
190
191 1
        if (empty($request['challenge'])) {
192
            throw new \Exception('Challenge is missing for URL verification');
193
        }
194
195 1
        echo $request['challenge'];
196 1
    }
197
198
    /**
199
     * Send confirmation.
200
     */
201 1
    private function sendConfirmation()
202
    {
203 1
        $userId = $this->getRequest('user_id');
204
205 1
        $user = '';
206 1
        if (!empty($userId)) {
207 1
            $user = $this->getMessageUtility()->linkToUser($userId).' ';
208
        }
209
210 1
        $confirmMessage = $this->getConfig()->get('confirmReceivedMessage', ['user' => $user]);
211
212 1
        $channel = $this->getRequest('channel_name');
213 1
        if (!empty($confirmMessage)) {
214 1
            $this->getSender()->send($channel, $confirmMessage);
215
        }
216 1
    }
217
218
    /**
219
     * Pre-process the request.
220
     */
221 3
    private function preProcessRequest()
222
    {
223 3
        $request = $this->getListener()->getRequest();
224
225
        // remove the trigger_word from beginning of the message
226 3
        if (!empty($request['trigger_word'])) {
227 3
            $request['text'] = $this->getMessageUtility()->removeTriggerWord(
228 3
                $request['text'],
229 3
                $request['trigger_word']
230
            );
231
232 3
            $this->getListener()->setRequest($request);
233
        }
234 3
    }
235
236
    /**
237
     * @param null $message
238
     *
239
     * @throws \Exception
240
     *
241
     * @return mixed
242
     */
243 4
    public function respond($message = null)
244
    {
245
        try {
246 4
            $command = $this->getCommandByMessage($message);
247
248 4
            if (!$command instanceof Command) {
249
                // something went wrong, error will tell us!
250 2
                return $this->getLastError();
251
            }
252
253
            // create the class
254 3
            $pluginClassFile = $command->getClass();
255 3
            $pluginClass = new $pluginClassFile($this);
256
257
            // check class is valid
258 3
            if (!$pluginClass instanceof AbstractPlugin) {
259
                throw new \Exception("Couldn't create class: '{$pluginClassFile}'");
260
            }
261
262
            // check action exists
263 3
            $action = $command->getAction();
264 3
            if (!method_exists($pluginClass, $action)) {
265 1
                throw new \Exception("Action / function: '{$action}' does not exist in '{$pluginClassFile}'");
266
            }
267
268 2
            return $pluginClass->$action();
269 1
        } catch (\Exception $e) {
270 1
            throw $e;
271
        }
272
    }
273
274
    /**
275
     * @param null $message
276
     *
277
     * @throws \Exception
278
     *
279
     * @return bool|Command
280
     */
281 7
    public function getCommandByMessage($message = null)
282
    {
283
        // If message is not set, get it from the current request
284 7
        if ($message === null) {
285 4
            $message = $this->getMessage();
286
        }
287
288 7
        if (empty($message)) {
289 1
            $this->setLastError('Message is empty');
290
291 1
            return false;
292
        }
293
294
        /**
295
         * Process the message.
296
         */
297 6
        $command = $this->getMessageUtility()->extractCommandName($message);
298
299 6
        $config = $this->getConfig();
300
301
        // check command name
302 6
        if (empty($command)) {
303
            // get the default command if no command is find in the message
304 2
            $command = $config->get('defaultCommand');
305
306 2
            if (empty($command)) {
307 2
                $this->setLastError($config->get('noCommandMessage'));
308
309 2
                return false;
310
            }
311
        }
312
313 4
        $commandObject = $this->getCommandContainer()->getAsObject($command);
314
315
        // check command details
316 4
        if (empty($commandObject)) {
317 1
            $this->setLastError($config->get('unknownCommandMessage', ['command' => $command]));
318
319 1
            return false;
320
        }
321
322 4
        if (!$commandObject instanceof Command) {
323
            throw new \Exception('Command is not an object');
324
        }
325
326
        // check the plugin for the command
327 4
        if (empty($commandObject->getPlugin())) {
328
            throw new \Exception('Plugin is not set for this command');
329
        }
330
331 4
        return $commandObject;
332
    }
333
334
    /**
335
     * @throws \Exception
336
     *
337
     * @return array<string,boolean|string>
338
     */
339 4
    private function verifyRequest()
340
    {
341 4
        $originCheck = $this->getListener()->verifyOrigin();
342
343 4
        if (!isset($originCheck['success'])) {
344
            throw new \Exception('Success must be provided in verifyOrigin response');
345
        }
346
347 4
        if ($originCheck['success'] !== true) {
348
            return [
349
                'success' => false,
350
                'message' => $originCheck['message'],
351
            ];
352
        }
353
354 4
        if ($this->getListener()->isThisBot() !== false) {
355
            return [
356 1
                'success' => false,
357
                'message' => 'Request comes from the bot',
358
            ];
359
        }
360
361
        return [
362 3
            'success' => true,
363
            'message' => 'Yay!',
364
        ];
365
    }
366
367
    /**
368
     * @return array
369
     */
370 2
    public function getCommands()
371
    {
372 2
        if (!isset($this->commands)) {
373 1
            $this->setCommands($this->getCommandContainer()->getAllAsObject());
374
        }
375
376 2
        return $this->commands;
377
    }
378
379
    /**
380
     * @param array $commands
381
     */
382 2
    public function setCommands(array $commands)
383
    {
384 2
        $this->commands = $commands;
385 2
    }
386
387
    /**
388
     * @return string
389
     */
390 4
    public function getLastError()
391
    {
392 4
        return $this->lastError;
393
    }
394
395
    /**
396
     * @param string $lastError
397
     */
398 4
    public function setLastError($lastError)
399
    {
400 4
        $this->lastError = $lastError;
401 4
    }
402
403
    /**
404
     * @return string
405
     */
406 1
    public function getCurrentCommand()
407
    {
408 1
        return $this->currentCommand;
409
    }
410
411
    /**
412
     * @param string $currentCommand
413
     */
414 2
    public function setCurrentCommand($currentCommand)
415
    {
416 2
        $this->currentCommand = $currentCommand;
417 2
    }
418
419
    /**
420
     * Return message based on the listener
421
     * If listener is event and event text is empty, fall back to request text.
422
     *
423
     * @return mixed|string
424
     */
425 11
    public function getMessage()
426
    {
427 11
        $listener = $this->getListener();
428 11
        if ($listener instanceof EventListener && $listener->getEvent() instanceof Event) {
429 1
            $message = $listener->getEvent()->getText();
430
431 1
            if (!empty($message)) {
432 1
                return $message;
433
            }
434
        }
435
436 10
        return $listener->getRequest('text');
437
    }
438
439
    /**
440
     * Determine if bot user id is mentioned in the message.
441
     *
442
     * @return bool
443
     */
444 1
    public function youTalkingToMe()
445
    {
446 1
        $message = $this->getMessage();
447
448 1
        if (empty($message)) {
449 1
            return false;
450
        }
451
452 1
        return $this->getMessageUtility()->isBotMentioned($message);
453
    }
454
}
455