Completed
Push — master ( 903616...f1d369 )
by Ehsan
03:15
created

Slackbot   C

Complexity

Total Complexity 49

Size/Duplication

Total Lines 374
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Test Coverage

Coverage 94.89%

Importance

Changes 0
Metric Value
wmc 49
c 0
b 0
f 0
lcom 1
cbo 17
dl 0
loc 374
ccs 130
cts 137
cp 0.9489
rs 5.7127

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
A setTimezone() 0 5 1
A getRequest() 0 4 1
A handleMessageActions() 0 15 2
B handleSendResponse() 0 46 6
B checkAccessControl() 0 23 4
A run() 0 13 4
A handleOAuth() 0 4 1
A handleUrlVerification() 0 10 2
A preProcessRequest() 0 14 2
B respond() 0 30 5
B getCommandByMessage() 0 58 8
A getCommands() 0 8 2
A setCommands() 0 4 1
A getLastError() 0 4 1
A setLastError() 0 4 1
A getCurrentCommand() 0 4 1
A setCurrentCommand() 0 4 1
A youTalkingToMe() 0 16 4

How to fix   Complexity   

Complex Class

Complex classes like Slackbot often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Slackbot, and based on these observations, apply Extract Interface, too.

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