Completed
Pull Request — develop (#288)
by Armando
27:44 queued 12:40
created

Telegram::enableAdmins()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 8
ccs 0
cts 0
cp 0
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
crap 6
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 PDO;
22
use RecursiveDirectoryIterator;
23
use RecursiveIteratorIterator;
24
use RegexIterator;
25
26
class Telegram
27
{
28
    /**
29
     * Version
30
     *
31
     * @var string
32
     */
33
    protected $version = '0.35.0';
34
35
    /**
36
     * Telegram API key
37
     *
38
     * @var string
39
     */
40
    protected $api_key = '';
41
42
    /**
43
     * Telegram Bot name
44
     *
45
     * @var string
46
     */
47
    protected $bot_name = '';
48
49
    /**
50
     * Raw request data (json) for webhook methods
51
     *
52
     * @var string
53
     */
54
    protected $input;
55
56
    /**
57
     * Custom commands paths
58
     *
59
     * @var array
60
     */
61
    protected $commands_paths = [];
62
63
    /**
64
     * Current Update object
65
     *
66
     * @var \Longman\TelegramBot\Entities\Update
67
     */
68
    protected $update;
69
70
    /**
71
     * Upload path
72
     *
73
     * @var string
74
     */
75
    protected $upload_path;
76
77
    /**
78
     * Download path
79
     *
80
     * @var string
81
     */
82
    protected $download_path;
83
84
    /**
85
     * MySQL integration
86
     *
87
     * @var boolean
88
     */
89
    protected $mysql_enabled = false;
90
91
    /**
92
     * PDO object
93
     *
94
     * @var \PDO
95
     */
96
    protected $pdo;
97
98
    /**
99
     * Commands config
100
     *
101
     * @var array
102
     */
103
    protected $commands_config = [];
104
105
    /**
106
     * Admins list
107
     *
108
     * @var array
109
     */
110
    protected $admins_list = [];
111
112
    /**
113
     * ServerResponse of the last Command execution
114
     *
115
     * @var \Longman\TelegramBot\Entities\ServerResponse
116
     */
117
    protected $last_command_response;
118
119
    /**
120
     * Botan.io integration
121
     *
122
     * @var boolean
123
     */
124
    protected $botan_enabled = false;
125
126
    /**
127
     * Telegram constructor.
128
     *
129
     * @param string $api_key
130
     * @param string $bot_name
131
     *
132 39
     * @throws \Longman\TelegramBot\Exception\TelegramException
133
     */
134 39
    public function __construct($api_key, $bot_name)
135 1
    {
136
        if (empty($api_key)) {
137
            throw new TelegramException('API KEY not defined!');
138 39
        }
139 1
140
        if (empty($bot_name)) {
141
            throw new TelegramException('Bot Username not defined!');
142 39
        }
143 39
144
        $this->api_key  = $api_key;
145
        $this->bot_name = $bot_name;
146 39
147 39
        //Set default download and upload path
148
        $this->setDownloadPath(BASE_PATH . '/../Download');
149
        $this->setUploadPath(BASE_PATH . '/../Upload');
150 39
151
        //Add default system commands path
152 39
        $this->addCommandsPath(BASE_COMMANDS_PATH . '/SystemCommands');
153 39
154
        Request::initialize($this);
155
    }
156
157
    /**
158
     * Initialize Database connection
159
     *
160
     * @param array  $credential
161
     * @param string $table_prefix
162
     * @param string $encoding
163
     *
164 9
     * @return \Longman\TelegramBot\Telegram
165
     * @throws \Longman\TelegramBot\Exception\TelegramException
166 9
     */
167 9 View Code Duplication
    public function enableMySql(array $credential, $table_prefix = null, $encoding = 'utf8mb4')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
168 9
    {
169 9
        $this->pdo = DB::initialize($credential, $this, $table_prefix, $encoding);
170
        ConversationDB::initializeConversation();
171
        $this->mysql_enabled = true;
172
173
        return $this;
174
    }
175
176
    /**
177
     * Initialize Database external connection
178
     *
179
     * @param PDO    $external_pdo_connection PDO database object
180
     * @param string $table_prefix
181
     *
182
     * @return \Longman\TelegramBot\Telegram
183
     * @throws \Longman\TelegramBot\Exception\TelegramException
184
     */
185 View Code Duplication
    public function enableExternalMySql($external_pdo_connection, $table_prefix = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
186
    {
187
        $this->pdo = DB::externalInitialize($external_pdo_connection, $this, $table_prefix);
188
        ConversationDB::initializeConversation();
189
        $this->mysql_enabled = true;
190
191 10
        return $this;
192
    }
193 10
194
    /**
195 10
     * Get commands list
196
     *
197
     * @return array $commands
198 10
     * @throws \Longman\TelegramBot\Exception\TelegramException
199 10
     */
200 10
    public function getCommandsList()
201
    {
202 10
        $commands = [];
203
204
        foreach ($this->commands_paths as $path) {
205 10
            try {
206
                //Get all "*Command.php" files
207 10
                $files = new RegexIterator(
208 10
                    new RecursiveIteratorIterator(
209
                        new RecursiveDirectoryIterator($path)
210 10
                    ),
211
                    '/^.+Command.php$/'
212
                );
213
214 10
                foreach ($files as $file) {
215
                    //Remove "Command.php" from filename
216 10
                    $command      = $this->sanitizeCommand(substr($file->getFilename(), 0, -11));
217 10
                    $command_name = strtolower($command);
218 10
219
                    if (array_key_exists($command_name, $commands)) {
220
                        continue;
221
                    }
222 10
223
                    require_once $file->getPathname();
224
225
                    $command_obj = $this->getCommandObject($command);
226 10
                    if ($command_obj instanceof Command) {
227
                        $commands[$command_name] = $command_obj;
228
                    }
229
                }
230
            } catch (Exception $e) {
231
                throw new TelegramException('Error getting commands from path: ' . $path);
232
            }
233
        }
234
235
        return $commands;
236 11
    }
237
238 11
    /**
239 11
     * Get an object instance of the passed command
240 11
     *
241
     * @param string $command
242 11
     *
243 11
     * @return \Longman\TelegramBot\Commands\Command|null
244 11
     */
245 11
    public function getCommandObject($command)
246
    {
247
        $which = ['System'];
248
        $this->isAdmin() && $which[] = 'Admin';
249
        $which[] = 'User';
250
251
        foreach ($which as $auth) {
252
            $command_namespace = __NAMESPACE__ . '\\Commands\\' . $auth . 'Commands\\' . $this->ucfirstUnicode($command) . 'Command';
253
            if (class_exists($command_namespace)) {
254
                return new $command_namespace($this, $this->update);
255
            }
256
        }
257
258
        return null;
259
    }
260
261
    /**
262
     * Set custom input string for debug purposes
263
     *
264
     * @param string $input (json format)
265
     *
266
     * @return \Longman\TelegramBot\Telegram
267
     */
268
    public function setCustomInput($input)
269
    {
270
        $this->input = $input;
271
272
        return $this;
273
    }
274
275
    /**
276
     * Get custom input string for debug purposes
277
     *
278
     * @return string
279
     */
280
    public function getCustomInput()
281
    {
282
        return $this->input;
283
    }
284
285
    /**
286
     * Get the ServerResponse of the last Command execution
287
     *
288
     * @return \Longman\TelegramBot\Entities\ServerResponse
289
     */
290
    public function getLastCommandResponse()
291
    {
292
        return $this->last_command_response;
293
    }
294
295
    /**
296
     * Handle getUpdates method
297
     *
298
     * @param int|null $limit
299
     * @param int|null $timeout
300
     *
301
     * @return \Longman\TelegramBot\Entities\ServerResponse
302
     * @throws \Longman\TelegramBot\Exception\TelegramException
303
     */
304
    public function handleGetUpdates($limit = null, $timeout = null)
305
    {
306
        if (!DB::isDbConnected()) {
307
            return new ServerResponse(
308
                [
309
                    'ok'          => false,
310
                    'description' => 'getUpdates needs MySQL connection!',
311
                ],
312
                $this->bot_name
313
            );
314
        }
315
316
        //DB Query
317
        $last_update = DB::selectTelegramUpdate(1);
318
        $last_update = reset($last_update);
319
320
        //As explained in the telegram bot api documentation
321
        $offset = isset($last_update['id']) ? $last_update['id'] + 1 : null;
322
323
        $response = Request::getUpdates(
324
            [
325
                'offset'  => $offset,
326
                'limit'   => $limit,
327
                'timeout' => $timeout,
328
            ]
329
        );
330
331
        if ($response->isOk()) {
332
            //Process all updates
333
            /** @var Update $result */
334
            foreach ((array)$response->getResult() as $result) {
335
                $this->processUpdate($result);
336
            }
337
        }
338
339
        return $response;
340
    }
341
342
    /**
343
     * Handle bot request from webhook
344
     *
345
     * @return bool
346
     *
347
     * @throws \Longman\TelegramBot\Exception\TelegramException
348
     */
349
    public function handle()
350
    {
351
        $this->input = Request::getInput();
352
353
        if (empty($this->input)) {
354
            throw new TelegramException('Input is empty!');
355
        }
356
357
        $post = json_decode($this->input, true);
358
        if (empty($post)) {
359
            throw new TelegramException('Invalid JSON!');
360
        }
361
362
        if ($response = $this->processUpdate(new Update($post, $this->bot_name))) {
363
            return $response->isOk();
364
        }
365
366
        return false;
367
    }
368
369
    /**
370
     * Get the command name from the command type
371
     *
372
     * @param string $type
373
     *
374
     * @return string
375
     */
376
    private function getCommandFromType($type)
377
    {
378
        return $this->ucfirstUnicode(str_replace('_', '', $type));
379
    }
380
381
    /**
382
     * Process bot Update request
383
     *
384
     * @param \Longman\TelegramBot\Entities\Update $update
385
     *
386
     * @return \Longman\TelegramBot\Entities\ServerResponse
387
     * @throws \Longman\TelegramBot\Exception\TelegramException
388
     */
389
    public function processUpdate(Update $update)
390
    {
391
        $this->update = $update;
392
393
        //If all else fails, it's a generic message.
394
        $command = 'genericmessage';
395
396
        $update_type = $this->update->getUpdateType();
397
        if (in_array($update_type, ['inline_query', 'chosen_inline_result', 'callback_query', 'edited_message'], true)) {
398
            $command = $this->getCommandFromType($update_type);
399
        } elseif ($update_type === 'message') {
400
            $message = $this->update->getMessage();
401
402
            //Load admin commands
403
            if ($this->isAdmin()) {
404
                $this->addCommandsPath(BASE_COMMANDS_PATH . '/AdminCommands', false);
405
            }
406
407
            $this->addCommandsPath(BASE_COMMANDS_PATH . '/UserCommands', false);
408
409
            $type = $message->getType();
410
            if ($type === 'command') {
411
                $command = $message->getCommand();
412
            } elseif (in_array($type, [
413
                'channel_chat_created',
414
                'delete_chat_photo',
415
                'group_chat_created',
416
                'left_chat_member',
417
                'migrate_from_chat_id',
418
                'migrate_to_chat_id',
419
                'new_chat_member',
420
                'new_chat_photo',
421
                'new_chat_title',
422
                'supergroup_chat_created',
423
            ], true)) {
424
                $command = $this->getCommandFromType($type);
425
            }
426
        }
427
428
        //Make sure we have an up-to-date command list
429
        //This is necessary to "require" all the necessary command files!
430
        $this->getCommandsList();
431
432
        DB::insertRequest($this->update);
433
434
        return $this->executeCommand($command);
0 ignored issues
show
Bug introduced by
It seems like $command defined by $message->getCommand() on line 411 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...
435
    }
436
437
    /**
438
     * Execute /command
439
     *
440
     * @param string $command
441
     *
442
     * @return mixed
443
     * @throws \Longman\TelegramBot\Exception\TelegramException
444
     */
445
    public function executeCommand($command)
446
    {
447
        $command_obj = $this->getCommandObject($command);
448
449
        if (!$command_obj || !$command_obj->isEnabled()) {
450
            //Failsafe in case the Generic command can't be found
451
            if ($command === 'Generic') {
452
                throw new TelegramException('Generic command missing!');
453
            }
454
455
            //Handle a generic command or non existing one
456
            $this->last_command_response = $this->executeCommand('Generic');
457
        } else {
458
            //Botan.io integration, make sure only the command user executed is reported
459
            if ($this->botan_enabled) {
460
                Botan::lock($command);
461
            }
462
463
            //execute() method is executed after preExecute()
464
            //This is to prevent executing a DB query without a valid connection
465
            $this->last_command_response = $command_obj->preExecute();
466 10
467
            //Botan.io integration, send report after executing the command
468 10
            if ($this->botan_enabled) {
469
                Botan::track($this->update, $command);
470
            }
471
        }
472
473
        return $this->last_command_response;
474
    }
475
476
    /**
477
     * Sanitize Command
478 1
     *
479
     * @param string $command
480 1
     *
481 1
     * @return string
482
     */
483 1
    protected function sanitizeCommand($command)
484
    {
485
        return str_replace(' ', '', $this->ucwordsUnicode(str_replace('_', ' ', $command)));
486 1
    }
487
488
    /**
489
     * Enable a single Admin account
490
     *
491
     * @param integer $admin_id Single admin id
492
     *
493
     * @return \Longman\TelegramBot\Telegram
494
     */
495
    public function enableAdmin($admin_id)
496 1
    {
497
        if (is_int($admin_id) && $admin_id > 0 && !in_array($admin_id, $this->admins_list, true)) {
498 1
            $this->admins_list[] = $admin_id;
499 1
        } else {
500
            TelegramLog::error('Invalid value "' . $admin_id . '" for admin.');
501
        }
502 1
503
        return $this;
504
    }
505
506
    /**
507
     * Enable a list of Admin Accounts
508
     *
509
     * @param array $admin_ids List of admin ids
510 1
     *
511
     * @return \Longman\TelegramBot\Telegram
512 1
     */
513
    public function enableAdmins(array $admin_ids)
514
    {
515
        foreach ($admin_ids as $admin_id) {
516
            $this->enableAdmin($admin_id);
517
        }
518
519
        return $this;
520
    }
521
522
    /**
523
     * Get list of admins
524 11
     *
525
     * @return array
526 11
     */
527
    public function getAdminList()
528
    {
529
        return $this->admins_list;
530
    }
531
532
    /**
533
     * Check if the passed user is an admin
534
     *
535
     * If no user id is passed, the current update is checked for a valid message sender.
536
     *
537
     * @param int|null $user_id
538
     *
539
     * @return bool
540 11
     */
541
    public function isAdmin($user_id = null)
542
    {
543
        if ($user_id === null && $this->update !== null) {
544
            //Try to figure out if the user is an admin
545
            $update_methods = [
546
                'getMessage',
547
                'getInlineQuery',
548
                'getChosenInlineResult',
549
                'getCallbackQuery',
550
                'getEditedMessage',
551
            ];
552
            foreach ($update_methods as $update_method) {
553
                $object = call_user_func([$this->update, $update_method]);
554
                if ($object !== null && $from = $object->getFrom()) {
555
                    $user_id = $from->getId();
556
                    break;
557
                }
558
            }
559
        }
560
561
        return ($user_id === null) ? false : in_array($user_id, $this->admins_list, true);
562
    }
563
564
    /**
565 39
     * Check if user required the db connection
566
     *
567 39
     * @return bool
568 1
     */
569 39
    public function isDbEnabled()
570 39
    {
571 39
        if ($this->mysql_enabled) {
572
            return true;
573
        } else {
574
            return false;
575
        }
576
    }
577 39
578
    /**
579
     * Add a single custom commands path
580
     *
581
     * @param string $path   Custom commands path to add
582
     * @param bool   $before If the path should be prepended or appended to the list
583
     *
584
     * @return \Longman\TelegramBot\Telegram
585
     */
586
    public function addCommandsPath($path, $before = true)
587
    {
588 1
        if (!is_dir($path)) {
589
            TelegramLog::error('Commands path "' . $path . '" does not exist.');
590 1
        } elseif (!in_array($path, $this->commands_paths, true)) {
591 1
            if ($before) {
592
                array_unshift($this->commands_paths, $path);
593
            } else {
594 1
                $this->commands_paths[] = $path;
595
            }
596
        }
597
598
        return $this;
599
    }
600
601
    /**
602
     * Add multiple custom commands paths
603
     *
604 39
     * @param array $paths  Custom commands paths to add
605
     * @param bool  $before If the paths should be prepended or appended to the list
606 39
     *
607 39
     * @return \Longman\TelegramBot\Telegram
608
     */
609
    public function addCommandsPaths(array $paths, $before = true)
610
    {
611
        foreach ($paths as $path) {
612
            $this->addCommandsPath($path, $before);
613
        }
614
615
        return $this;
616
    }
617
618
    /**
619
     * Set custom upload path
620
     *
621
     * @param string $path Custom upload path
622
     *
623
     * @return \Longman\TelegramBot\Telegram
624
     */
625
    public function setUploadPath($path)
626
    {
627 39
        $this->upload_path = $path;
628
629 39
        return $this;
630 39
    }
631
632
    /**
633
     * Get custom upload path
634
     *
635
     * @return string
636
     */
637
    public function getUploadPath()
638
    {
639
        return $this->upload_path;
640
    }
641
642
    /**
643
     * Set custom download path
644
     *
645
     * @param string $path Custom download path
646
     *
647
     * @return \Longman\TelegramBot\Telegram
648
     */
649
    public function setDownloadPath($path)
650
    {
651
        $this->download_path = $path;
652
653
        return $this;
654
    }
655 13
656
    /**
657 13
     * Get custom download path
658 13
     *
659
     * @return string
660
     */
661
    public function getDownloadPath()
662
    {
663
        return $this->download_path;
664
    }
665
666
    /**
667
     * Set command config
668 24
     *
669
     * Provide further variables to a particular commands.
670 24
     * For example you can add the channel name at the command /sendtochannel
671
     * Or you can add the api key for external service.
672
     *
673
     * @param string $command
674
     * @param array  $config
675
     *
676
     * @return \Longman\TelegramBot\Telegram
677
     */
678 1
    public function setCommandConfig($command, array $config)
679
    {
680 1
        $this->commands_config[$command] = $config;
681
682
        return $this;
683
    }
684
685
    /**
686
     * Get command config
687
     *
688 6
     * @param string $command
689
     *
690 6
     * @return array
691
     */
692
    public function getCommandConfig($command)
693
    {
694
        return isset($this->commands_config[$command]) ? $this->commands_config[$command] : [];
695
    }
696
697
    /**
698 1
     * Get API key
699
     *
700 1
     * @return string
701
     */
702
    public function getApiKey()
703
    {
704
        return $this->api_key;
705
    }
706
707
    /**
708
     * Get Bot name
709
     *
710
     * @return string
711
     */
712
    public function getBotName()
713
    {
714
        return $this->bot_name;
715
    }
716
717
    /**
718
     * Get Version
719
     *
720
     * @return string
721
     */
722
    public function getVersion()
723
    {
724
        return $this->version;
725
    }
726
727
    /**
728
     * Set Webhook for bot
729
     *
730
     * @param string      $url
731
     * @param string|null $path_certificate
732
     *
733
     * @return \Longman\TelegramBot\Entities\ServerResponse
734
     * @throws \Longman\TelegramBot\Exception\TelegramException
735
     */
736
    public function setWebHook($url, $path_certificate = null)
737
    {
738
        if (empty($url)) {
739
            throw new TelegramException('Hook url is empty!');
740
        }
741
742
        $result = Request::setWebhook($url, $path_certificate);
743
744 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...
745
            throw new TelegramException(
746
                'Webhook was not set! Error: ' . $result->getErrorCode() . ' ' . $result->getDescription()
747
            );
748
        }
749
750
        return $result;
751
    }
752
753
    /**
754
     * Unset Webhook for bot
755
     *
756 10
     * @return mixed
757
     * @throws \Longman\TelegramBot\Exception\TelegramException
758 10
     */
759
    public function unsetWebHook()
760
    {
761
        $result = Request::setWebhook();
762
763 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...
764
            throw new TelegramException(
765
                'Webhook was not unset! Error: ' . $result->getErrorCode() . ' ' . $result->getDescription()
766
            );
767
        }
768
769 11
        return $result;
770
    }
771
772 11
    /**
773 11
     * Replace function `ucwords` for UTF-8 characters in the class definition and commands
774
     *
775
     * @param string $str
776
     * @param string $encoding (default = 'UTF-8')
777
     *
778
     * @return string
779
     */
780
    protected function ucwordsUnicode($str, $encoding = 'UTF-8')
781
    {
782
        return mb_convert_case($str, MB_CASE_TITLE, $encoding);
783
    }
784
785
    /**
786
     * Replace function `ucfirst` for UTF-8 characters in the class definition and commands
787
     *
788
     * @param string $str
789
     * @param string $encoding (default = 'UTF-8')
790
     *
791
     * @return string
792
     */
793
    protected function ucfirstUnicode($str, $encoding = 'UTF-8')
794
    {
795
        return
796
            mb_strtoupper(mb_substr($str, 0, 1, $encoding), $encoding)
797
            . mb_strtolower(mb_substr($str, 1, mb_strlen($str), $encoding), $encoding);
798
    }
799
800
    /**
801
     * Enable Botan.io integration
802
     *
803
     * @param  $token
804
     *
805
     * @return \Longman\TelegramBot\Telegram
806
     * @throws \Longman\TelegramBot\Exception\TelegramException
807
     */
808
    public function enableBotan($token)
809
    {
810
        Botan::initializeBotan($token);
811
        $this->botan_enabled = true;
812
813
        return $this;
814
    }
815
}
816