Completed
Push — feature/improve-code ( e986a1...36787d )
by Avtandil
14:14
created

Telegram::enableAdmins()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 8
rs 9.4285
ccs 4
cts 4
cp 1
cc 2
eloc 4
nc 2
nop 1
crap 2
1
<?php
2
/**
3
 * This file is part of the TelegramBot package.
4
 *
5
 * (c) Avtandil Kikabidze aka LONGMAN <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Longman\TelegramBot;
12
13 1
define('BASE_PATH', __DIR__);
14 1
define('BASE_COMMANDS_PATH', BASE_PATH . '/Commands');
15
16
use Exception;
17
use Longman\TelegramBot\Commands\Command;
18
use Longman\TelegramBot\Entities\ServerResponse;
19
use Longman\TelegramBot\Entities\Update;
20
use Longman\TelegramBot\Exception\TelegramException;
21
use RecursiveDirectoryIterator;
22
use RecursiveIteratorIterator;
23
use RegexIterator;
24
25
class Telegram
26
{
27
    /**
28
     * Version
29
     *
30
     * @var string
31
     */
32
    protected $version = '0.35.0';
33
34
    /**
35
     * Telegram API key
36
     *
37
     * @var string
38
     */
39
    protected $api_key = '';
40
41
    /**
42
     * Telegram Bot name
43
     *
44
     * @var string
45
     */
46
    protected $bot_name = '';
47
48
    /**
49
     * Raw request data (json) for webhook methods
50
     *
51
     * @var string
52
     */
53
    protected $input;
54
55
    /**
56
     * Custom commands paths
57
     *
58
     * @var array
59
     */
60
    protected $commands_paths = [];
61
62
    /**
63
     * Current Update object
64
     *
65
     * @var \Longman\TelegramBot\Entities\Update
66
     */
67
    protected $update;
68
69
    /**
70
     * Upload path
71
     *
72
     * @var string
73
     */
74
    protected $upload_path;
75
76
    /**
77
     * Download path
78
     *
79
     * @var string
80
     */
81
    protected $download_path;
82
83
    /**
84
     * MySQL integration
85
     *
86
     * @var boolean
87
     */
88
    protected $mysql_enabled = false;
89
90
    /**
91
     * PDO object
92
     *
93
     * @var \PDO
94
     */
95
    protected $pdo;
96
97
    /**
98
     * Commands config
99
     *
100
     * @var array
101
     */
102
    protected $commands_config = [];
103
104
    /**
105
     * Admins list
106
     *
107
     * @var array
108
     */
109
    protected $admins_list = [];
110
111
    /**
112
     * ServerResponse of the last Command execution
113
     *
114
     * @var \Longman\TelegramBot\Entities\ServerResponse
115
     */
116
    protected $last_command_response;
117
118
    /**
119
     * Botan.io integration
120
     *
121
     * @var boolean
122
     */
123
    protected $botan_enabled = false;
124
125
    /**
126
     * Telegram constructor.
127
     *
128
     * @param $api_key
129
     * @param $bot_name
130
     * @throws \Longman\TelegramBot\Exception\TelegramException
131
     */
132 39
    public function __construct($api_key, $bot_name)
133
    {
134 39
        if (empty($api_key)) {
135 1
            throw new TelegramException('API KEY not defined!');
136
        }
137
138 39
        if (empty($bot_name)) {
139 1
            throw new TelegramException('Bot Username not defined!');
140
        }
141
142 39
        $this->api_key  = $api_key;
143 39
        $this->bot_name = $bot_name;
144
145
        //Set default download and upload path
146 39
        $this->setDownloadPath(BASE_PATH . '/../Download');
147 39
        $this->setUploadPath(BASE_PATH . '/../Upload');
148
149
        //Add default system commands path
150 39
        $this->addCommandsPath(BASE_COMMANDS_PATH . '/SystemCommands');
151
152 39
        Request::initialize($this);
153 39
    }
154
155
    /**
156
     * Initialize Database connection
157
     *
158
     * @param array  $credential
159
     * @param string $table_prefix
160
     * @param string $encoding
161
     *
162
     * @return \Longman\TelegramBot\Telegram
163
     */
164 9
    public function enableMySql(array $credential, $table_prefix = null, $encoding = 'utf8mb4')
165
    {
166 9
        $this->pdo = DB::initialize($credential, $this, $table_prefix, $encoding);
167 9
        ConversationDB::initializeConversation();
168 9
        $this->mysql_enabled = true;
169 9
        return $this;
170
    }
171
172
    /**
173
     * Initialize Database external connection
174
     *
175
     * @param /PDO    $external_pdo_connection PDO database object
0 ignored issues
show
Documentation introduced by
The doc-type /PDO could not be parsed: Unknown type name "/PDO" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
176
     * @param string $table_prefix
177
     */
178
    public function enableExternalMysql($external_pdo_connection, $table_prefix = null)
179
    {
180
        $this->pdo = DB::externalInitialize($external_pdo_connection, $this, $table_prefix);
181
        ConversationDB::initializeConversation();
182
        $this->mysql_enabled = true;
183
    }
184
185
    /**
186
     * Get commands list
187
     *
188
     * @return array $commands
189
     * @throws \Longman\TelegramBot\Exception\TelegramException
190
     */
191 10
    public function getCommandsList()
192
    {
193 10
        $commands = [];
194
195 10
        foreach ($this->commands_paths as $path) {
196
            try {
197
                //Get all "*Command.php" files
198 10
                $files = new RegexIterator(
199 10
                    new RecursiveIteratorIterator(
200 10
                        new RecursiveDirectoryIterator($path)
201
                    ),
202 10
                    '/^.+Command.php$/'
203
                );
204
205 10
                foreach ($files as $file) {
206
                    //Remove "Command.php" from filename
207 10
                    $command      = $this->sanitizeCommand(substr($file->getFilename(), 0, -11));
208 10
                    $command_name = strtolower($command);
209
210 10
                    if (array_key_exists($command_name, $commands)) {
211
                        continue;
212
                    }
213
214 10
                    require_once $file->getPathname();
215
216 10
                    $command_obj = $this->getCommandObject($command);
217 10
                    if ($command_obj instanceof Command) {
218 10
                        $commands[$command_name] = $command_obj;
219
                    }
220
                }
221
            } catch (Exception $e) {
222 10
                throw new TelegramException('Error getting commands from path: ' . $path);
223
            }
224
        }
225
226 10
        return $commands;
227
    }
228
229
    /**
230
     * Get an object instance of the passed command
231
     *
232
     * @param string $command
233
     *
234
     * @return \Longman\TelegramBot\Commands\Command|null
235
     */
236 11
    public function getCommandObject($command)
237
    {
238 11
        $which = ['System'];
239 11
        ($this->isAdmin()) && $which[] = 'Admin';
240 11
        $which[] = 'User';
241
242 11
        foreach ($which as $auth) {
243 11
            $command_namespace = __NAMESPACE__ . '\\Commands\\' . $auth . 'Commands\\' . $this->ucfirstUnicode($command) . 'Command';
244 11
            if (class_exists($command_namespace)) {
245 11
                return new $command_namespace($this, $this->update);
246
            }
247
        }
248
249
        return null;
250
    }
251
252
    /**
253
     * Set custom input string for debug purposes
254
     *
255
     * @param string $input (json format)
256
     *
257
     * @return \Longman\TelegramBot\Telegram
258
     */
259
    public function setCustomInput($input)
260
    {
261
        $this->input = $input;
262
        return $this;
263
    }
264
265
    /**
266
     * Get custom input string for debug purposes
267
     *
268
     * @return string
269
     */
270
    public function getCustomInput()
271
    {
272
        return $this->input;
273
    }
274
275
    /**
276
     * Get the ServerResponse of the last Command execution
277
     *
278
     * @return \Longman\TelegramBot\Entities\ServerResponse
279
     */
280
    public function getLastCommandResponse()
281
    {
282
        return $this->last_command_response;
283
    }
284
285
    /**
286
     * Handle getUpdates method
287
     *
288
     * @param int|null $limit
289
     * @param int|null $timeout
290
     *
291
     * @return \Longman\TelegramBot\Entities\ServerResponse
292
     */
293
    public function handleGetUpdates($limit = null, $timeout = null)
294
    {
295
        if (!DB::isDbConnected()) {
296
            return new ServerResponse(
297
                [
298
                    'ok'          => false,
299
                    'description' => 'getUpdates needs MySQL connection!',
300
                ],
301
                $this->bot_name
302
            );
303
        }
304
305
        //DB Query
306
        $last_update = DB::selectTelegramUpdate(1);
307
308
        //As explained in the telegram bot api documentation
309
        $offset = (isset($last_update[0]['id'])) ? $last_update[0]['id'] + 1 : null;
310
311
        $response = Request::getUpdates([
312
                                            'offset'  => $offset,
313
                                            'limit'   => $limit,
314
                                            'timeout' => $timeout,
315
                                        ]);
316
317
        if ($response->isOk()) {
318
            //Process all updates
319
            foreach ((array) $response->getResult() as $result) {
320
                $this->processUpdate($result);
321
            }
322
        }
323
324
        return $response;
325
    }
326
327
    /**
328
     * Handle bot request from webhook
329
     *
330
     * @return bool
331
     *
332
     * @throws \Longman\TelegramBot\Exception\TelegramException
333
     */
334
    public function handle()
335
    {
336
        $this->input = Request::getInput();
337
338
        if (empty($this->input)) {
339
            throw new TelegramException('Input is empty!');
340
        }
341
        $post = json_decode($this->input, true);
342
        if (empty($post)) {
343
            throw new TelegramException('Invalid JSON!');
344
        }
345
346
        if ($response = $this->processUpdate(new Update($post, $this->bot_name))) {
347
            return $response->isOk();
348
        }
349
350
        return false;
351
    }
352
353
    /**
354
     * Get the command name from the command type
355
     *
356
     * @param string $type
357
     *
358
     * @return string
359
     */
360
    private function getCommandFromType($type)
361
    {
362
        return $this->ucfirstUnicode(str_replace('_', '', $type));
363
    }
364
365
    /**
366
     * Process bot Update request
367
     *
368
     * @param \Longman\TelegramBot\Entities\Update $update
369
     *
370
     * @return \Longman\TelegramBot\Entities\ServerResponse
371
     */
372
    public function processUpdate(Update $update)
373
    {
374
        $this->update = $update;
375
376
        //If all else fails, it's a generic message.
377
        $command = 'genericmessage';
378
379
        $update_type = $this->update->getUpdateType();
380
        if (in_array($update_type, ['inline_query', 'chosen_inline_result', 'callback_query', 'edited_message'])) {
381
            $command = $this->getCommandFromType($update_type);
382
        } elseif ($update_type === 'message') {
383
            $message = $this->update->getMessage();
384
385
            //Load admin commands
386
            if ($this->isAdmin()) {
387
                $this->addCommandsPath(BASE_COMMANDS_PATH . '/AdminCommands', false);
388
            }
389
390
            $this->addCommandsPath(BASE_COMMANDS_PATH . '/UserCommands', false);
391
392
            $type = $message->getType();
393
            if ($type === 'command') {
394
                $command = $message->getCommand();
395
            } elseif (in_array($type, [
396
                'channel_chat_created',
397
                'delete_chat_photo',
398
                'group_chat_created',
399
                'left_chat_member',
400
                'migrate_from_chat_id',
401
                'migrate_to_chat_id',
402
                'new_chat_member',
403
                'new_chat_photo',
404
                'new_chat_title',
405
                'supergroup_chat_created',
406
            ])) {
407
                $command = $this->getCommandFromType($type);
408
            }
409
        }
410
411
        //Make sure we have an up-to-date command list
412
        //This is necessary to "require" all the necessary command files!
413
        $this->getCommandsList();
414
415
        DB::insertRequest($this->update);
416
417
        return $this->executeCommand($command);
0 ignored issues
show
Bug introduced by
It seems like $command defined by $message->getCommand() on line 394 can also be of type boolean; however, Longman\TelegramBot\Telegram::executeCommand() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
418
    }
419
420
    /**
421
     * Execute /command
422
     *
423
     * @param string $command
424
     *
425
     * @return mixed
426
     * @throws \Longman\TelegramBot\Exception\TelegramException
427
     */
428
    public function executeCommand($command)
429
    {
430
        $command_obj = $this->getCommandObject($command);
431
432
        if (!$command_obj || !$command_obj->isEnabled()) {
433
            //Failsafe in case the Generic command can't be found
434
            if ($command === 'Generic') {
435
                throw new TelegramException('Generic command missing!');
436
            }
437
438
            //Handle a generic command or non existing one
439
            $this->last_command_response = $this->executeCommand('Generic');
440
        } else {
441
            //Botan.io integration, make sure only the command user executed is reported
442
            if ($this->botan_enabled) {
443
                Botan::lock($command);
444
            }
445
446
            //execute() method is executed after preExecute()
447
            //This is to prevent executing a DB query without a valid connection
448
            $this->last_command_response = $command_obj->preExecute();
449
450
            //Botan.io integration, send report after executing the command
451
            if ($this->botan_enabled) {
452
                Botan::track($this->update, $command);
453
            }
454
        }
455
456
        return $this->last_command_response;
457
    }
458
459
    /**
460
     * Sanitize Command
461
     *
462
     * @param string $command
463
     *
464
     * @return string
465
     */
466 10
    protected function sanitizeCommand($command)
467
    {
468 10
        return str_replace(' ', '', $this->ucwordsUnicode(str_replace('_', ' ', $command)));
469
    }
470
471
    /**
472
     * Enable a single Admin account
473
     *
474
     * @param integer $admin_id Single admin id
475
     *
476
     * @return \Longman\TelegramBot\Telegram
477
     */
478 1
    public function enableAdmin($admin_id)
479
    {
480 1
        if (is_int($admin_id) && $admin_id > 0 && !in_array($admin_id, $this->admins_list)) {
481 1
            $this->admins_list[] = $admin_id;
482
        } else {
483 1
            TelegramLog::error('Invalid value "' . $admin_id . '" for admin.');
484
        }
485
486 1
        return $this;
487
    }
488
489
    /**
490
     * Enable a list of Admin Accounts
491
     *
492
     * @param array $admin_ids List of admin ids
493
     *
494
     * @return \Longman\TelegramBot\Telegram
495
     */
496 1
    public function enableAdmins(array $admin_ids)
497
    {
498 1
        foreach ($admin_ids as $admin_id) {
499 1
            $this->enableAdmin($admin_id);
500
        }
501
502 1
        return $this;
503
    }
504
505
    /**
506
     * Get list of admins
507
     *
508
     * @return array
509
     */
510 1
    public function getAdminList()
511
    {
512 1
        return $this->admins_list;
513
    }
514
515
    /**
516
     * Check if the passed user is an admin
517
     *
518
     * If no user id is passed, the current update is checked for a valid message sender.
519
     *
520
     * @param int|null $user_id
521
     *
522
     * @return bool
523
     */
524 11
    public function isAdmin($user_id = null)
525
    {
526 11
        if ($user_id === null && $this->update !== null) {
527
            if (($message = $this->update->getMessage()) && ($from = $message->getFrom())) {
528
                $user_id = $from->getId();
529
            } elseif (($inline_query = $this->update->getInlineQuery()) && ($from = $inline_query->getFrom())) {
530
                $user_id = $from->getId();
531
            } elseif (($chosen_inline_result = $this->update->getChosenInlineResult()) && ($from = $chosen_inline_result->getFrom())) {
532
                $user_id = $from->getId();
533
            } elseif (($callback_query = $this->update->getCallbackQuery()) && ($from = $callback_query->getFrom())) {
534
                $user_id = $from->getId();
535
            } elseif (($edited_message = $this->update->getEditedMessage()) && ($from = $edited_message->getFrom())) {
536
                $user_id = $from->getId();
537
            }
538
        }
539
540 11
        return ($user_id === null) ? false : in_array($user_id, $this->admins_list);
541
    }
542
543
    /**
544
     * Check if user required the db connection
545
     *
546
     * @return bool
547
     */
548
    public function isDbEnabled()
549
    {
550
        if ($this->mysql_enabled) {
551
            return true;
552
        } else {
553
            return false;
554
        }
555
    }
556
557
    /**
558
     * Add a single custom commands path
559
     *
560
     * @param string $path   Custom commands path to add
561
     * @param bool   $before If the path should be prepended or appended to the list
562
     *
563
     * @return \Longman\TelegramBot\Telegram
564
     */
565 39
    public function addCommandsPath($path, $before = true)
566
    {
567 39
        if (!is_dir($path)) {
568 1
            TelegramLog::error('Commands path "' . $path . '" does not exist.');
569 39
        } elseif (!in_array($path, $this->commands_paths)) {
570 39
            if ($before) {
571 39
                array_unshift($this->commands_paths, $path);
572
            } else {
573
                array_push($this->commands_paths, $path);
574
            }
575
        }
576
577 39
        return $this;
578
    }
579
580
    /**
581
     * Add multiple custom commands paths
582
     *
583
     * @param array $paths  Custom commands paths to add
584
     * @param bool  $before If the paths should be prepended or appended to the list
585
     *
586
     * @return \Longman\TelegramBot\Telegram
587
     */
588 1
    public function addCommandsPaths(array $paths, $before = true)
0 ignored issues
show
Unused Code introduced by
The parameter $before is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
589
    {
590 1
        foreach ($paths as $path) {
591 1
            $this->addCommandsPath($path);
592
        }
593
594 1
        return $this;
595
    }
596
597
    /**
598
     * Set custom upload path
599
     *
600
     * @param string $path Custom upload path
601
     *
602
     * @return \Longman\TelegramBot\Telegram
603
     */
604 39
    public function setUploadPath($path)
605
    {
606 39
        $this->upload_path = $path;
607 39
        return $this;
608
    }
609
610
    /**
611
     * Get custom upload path
612
     *
613
     * @return string
614
     */
615
    public function getUploadPath()
616
    {
617
        return $this->upload_path;
618
    }
619
620
    /**
621
     * Set custom download path
622
     *
623
     * @param string $path Custom download path
624
     *
625
     * @return \Longman\TelegramBot\Telegram
626
     */
627 39
    public function setDownloadPath($path)
628
    {
629 39
        $this->download_path = $path;
630 39
        return $this;
631
    }
632
633
    /**
634
     * Get custom download path
635
     *
636
     * @return string
637
     */
638
    public function getDownloadPath()
639
    {
640
        return $this->download_path;
641
    }
642
643
    /**
644
     * Set command config
645
     *
646
     * Provide further variables to a particular commands.
647
     * For example you can add the channel name at the command /sendtochannel
648
     * Or you can add the api key for external service.
649
     *
650
     * @param string $command
651
     * @param array  $config
652
     *
653
     * @return \Longman\TelegramBot\Telegram
654
     */
655 13
    public function setCommandConfig($command, array $config)
656
    {
657 13
        $this->commands_config[$command] = $config;
658 13
        return $this;
659
    }
660
661
    /**
662
     * Get command config
663
     *
664
     * @param string $command
665
     *
666
     * @return array
667
     */
668 24
    public function getCommandConfig($command)
669
    {
670 24
        return isset($this->commands_config[$command]) ? $this->commands_config[$command] : [];
671
    }
672
673
    /**
674
     * Get API key
675
     *
676
     * @return string
677
     */
678 1
    public function getApiKey()
679
    {
680 1
        return $this->api_key;
681
    }
682
683
    /**
684
     * Get Bot name
685
     *
686
     * @return string
687
     */
688 6
    public function getBotName()
689
    {
690 6
        return $this->bot_name;
691
    }
692
693
    /**
694
     * Get Version
695
     *
696
     * @return string
697
     */
698 1
    public function getVersion()
699
    {
700 1
        return $this->version;
701
    }
702
703
    /**
704
     * Set Webhook for bot
705
     *
706
     * @param string      $url
707
     * @param string|null $path_certificate
708
     *
709
     * @return \Longman\TelegramBot\Entities\ServerResponse
710
     * @throws \Longman\TelegramBot\Exception\TelegramException
711
     */
712
    public function setWebHook($url, $path_certificate = null)
713
    {
714
        if (empty($url)) {
715
            throw new TelegramException('Hook url is empty!');
716
        }
717
718
        $result = Request::setWebhook($url, $path_certificate);
719
720 View Code Duplication
        if (!$result->isOk()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
721
            throw new TelegramException(
722
                'Webhook was not set! Error: ' . $result->getErrorCode() . ' ' . $result->getDescription()
723
            );
724
        }
725
726
        return $result;
727
    }
728
729
    /**
730
     * Unset Webhook for bot
731
     *
732
     * @return mixed
733
     * @throws \Longman\TelegramBot\Exception\TelegramException
734
     */
735
    public function unsetWebHook()
736
    {
737
        $result = Request::setWebhook();
738
739 View Code Duplication
        if (!$result->isOk()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
740
            throw new TelegramException(
741
                'Webhook was not unset! Error: ' . $result->getErrorCode() . ' ' . $result->getDescription()
742
            );
743
        }
744
745
        return $result;
746
    }
747
748
    /**
749
     * Replace function `ucwords` for UTF-8 characters in the class definition and commands
750
     *
751
     * @param string $str
752
     * @param string $encoding (default = 'UTF-8')
753
     *
754
     * @return string
755
     */
756 10
    protected function ucwordsUnicode($str, $encoding = 'UTF-8')
757
    {
758 10
        return mb_convert_case($str, MB_CASE_TITLE, $encoding);
759
    }
760
761
    /**
762
     * Replace function `ucfirst` for UTF-8 characters in the class definition and commands
763
     *
764
     * @param string $str
765
     * @param string $encoding (default = 'UTF-8')
766
     *
767
     * @return string
768
     */
769 11
    protected function ucfirstUnicode($str, $encoding = 'UTF-8')
770
    {
771
        return
772 11
            mb_strtoupper(mb_substr($str, 0, 1, $encoding), $encoding)
773 11
            . mb_strtolower(mb_substr($str, 1, mb_strlen($str), $encoding), $encoding);
774
    }
775
776
    /**
777
     * Enable Botan.io integration
778
     *
779
     * @param  $token
780
     * @return \Longman\TelegramBot\Telegram
781
     */
782
    public function enableBotan($token)
783
    {
784
        Botan::initializeBotan($token);
785
        $this->botan_enabled = true;
786
        return $this;
787
    }
788
}
789