Completed
Push — master ( 5cdc2a...4c8026 )
by Danilo
01:54
created

Bot::getLanguageRedisAsCache()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 26
Code Lines 8

Duplication

Lines 7
Ratio 26.92 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 7
loc 26
rs 8.5806
cc 4
eloc 8
nc 3
nop 2
1
<?php
2
3
// Exception declration
4
use DanySpin97\PhpBotFramework\BotException;
5
6
/**
7
 * \class Bot Bot
8
 * \brief Bot class to handle updates and commandes.
9
 * \details Class Bot to handle task like api request, or more specific api function(sendMessage, editMessageText, etc).
10
 * Usage example in webhook.php
11
 *
12
 */
13
class DanySpin97\PhpBotFramework\Bot extends DanySpin97\PhpBotFramework\CoreBot {
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_NS_SEPARATOR, expecting '{'
Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
Coding Style introduced by
Expected 0 spaces between "Bot" and comma; 1 found
Loading history...
14
15
    /**
16
     * \addtogroup Bot Bot
17
     * \brief Properties and methods to handle the TelegramBot.
18
     * \details Here are listed all the properties and methods that will help the developer create the basic bot functions.
19
     * @{
20
     */
21
22
    /** \brief Text received in messages */
23
    protected $_text;
24
25
    /** \brief Data received in callback query */
26
    protected $_data;
27
28
    /** \brief Query sent by the user in the inline query */
29
    protected $_query;
30
31
    /** \brief Store the inline keyboard */
32
    public $keyboard;
33
34
    /** \brief Pdo reference */
35
    public $pdo;
36
37
    /** \brief Redis connection */
38
    public $redis;
39
40
    /** @} */
41
42
    /**
43
     * \addtogroup Core Core(internal)
44
     * @{
45
     */
46
47
    /** \brief Store the command triggered on message. */
48
    private $message_commands;
49
50
    /** \brief Does the bot has message commands? Set by initBot. */
51
    private $message_commands_set;
52
53
    /** \brief Store the command triggered on callback query. */
54
    private $callback_commands;
55
56
    /** \brief Does the bot has message commands? Set by initBot. */
57
    private $callback_commands_set;
58
59
    /** @} */
60
61
    /**
62
     * \addtogroup Multilanguage Multilanguage
63
     * \brief Methods to create a localized bot.
64
     * @{
65
     */
66
67
    /** \brief Store the language for a multi-language bot */
68
    public $language;
69
70
    /** \brief Store localization data */
71
    public $local;
72
73
    /** \brief Table contaning bot users data in the sql database. */
74
    public $user_table = '"User"';
75
76
    /** \brief Name of the column that represents the user id in the sql database */
77
    public $id_column = 'chat_id';
78
79
    /** @} */
80
81
    /** \addtogroup State
82
     * @{
83
     */
84
85
    /** \brief Status of the bot to handle data inserting and menu-like bot. */
86
    public $status;
87
88
    /** @} */
89
90
    /**
91
     * \addtogroup Bot
92
     * @{
93
     */
94
95
    /**
96
     * \brief Construct an empy bot.
97
     * \details Construct a bot with commands, multilanguage and status.
98
     */
99
    public function __construct(string $token) {
100
101
        // Parent constructor
102
        parent::__construct($token);
103
104
        // Initialize to an empty array
105
        $this->message_commands = [];
106
        $this->callback_commands = [];
107
108
        $this->keyboard = new DanySpin97\PhpBotFramework\InlineKeyboard($this);
109
110
    }
111
112
    /** \brief Descruct the class. */
113
    public function __destruct() {
114
115
        parent::__destruct();
116
117
        // Close redis connection if it is open
118
        if (isset($this->redis)) {
119
120
            $this->redis->close();
121
122
        }
123
124
    }
125
126
    /**
127
     * \brief Get the text of the message, if set (for updates of type "message").
128
     * @return Text of the message, empty string if not set.
129
     */
130
    public function getMessageText() : string {
131
132
        if (isset($this->_text)) {
133
134
            return $this->_text;
135
136
        }
137
138
        return '';
139
140
    }
141
142
    /**
143
     * \brief Get the data of callback query, if set (for updates of type "callback_query").
144
     * @return Data of the callback query, empty string if not set.
145
     */
146
    public function getCallbackData() : string {
147
148
        if (isset($this->_data)) {
149
150
            return $this->_data;
151
152
        }
153
154
        return '';
155
156
    }
157
158
    /**
159
     * \brief Get the query received from the inline query (for updates of type "inline_query").
160
     * @return The query sent by the user, throw exception if the current update is not an inline query.
161
     */
162
    public function getInlineQuery() : string {
163
164
        if (isset($this->_query)) {
165
166
            return $this->_query;
167
168
        }
169
170
        throw new BotException("Query from inline query is not set: wrong update type");
171
    }
172
173
    /**
174
     * \brief Get update and process it.
175
     * \details Call this method if you are using webhook.
176
     * It will get update from php::\input, check it and then process it using processUpdate.
177
     */
178
    public function processWebhookUpdate() {
179
180
        $this->initBot();
181
182
        $this->processUpdate(json_decode(file_get_contents('php://input'), true));
183
184
    }
185
186
    /** @} */
187
188
    /**
189
     * \addtogroup Core Core(Internal)
190
     * @{
191
     */
192
193
    /**
194
     * \brief Init variables to skip parsing commands if there aren't any.
195
     * \details Called internnaly by
196
     * - <code>getUpdatesLocal</code>
197
     * - <code>getUpdatesRedis</code>
198
     * - <code>getUpdatesDatabase</code>
199
     * - <code>processWebhookUpdate</code>
200
     */
201
    private function initBot() {
202
203
        // Are there message commands?
204
        $this->message_commands_set = !empty($this->message_commands);
205
206
        // Are there callback commands?
207
        $this->callback_commands_set = !empty($this->callback_commands);
208
209
    }
210
211
    /**
212
     * \brief Dispatch each update to the right method (processMessage, processCallbackQuery, etc).
213
     * \details Set $chat_id for each update, $text, $data and $query are set for each update that contains them.
214
     * It also calls commands for each updates, before process methods.
215
     * @param $update Reference to the update received.
216
     * @return The id of the update processed.
217
     */
218
    public function processUpdate(array $update) : int {
219
220
        if (isset($update['message'])) {
221
222
            // Set data from the message
223
            $this->_chat_id = $update['message']['chat']['id'];
224
225
            // If the message contains text
226
            if (isset($update['message']['text'])) {
227
228
                $this->_text = $update['message']['text'];
229
230
            }
231
232
            // If there are commands set by the user
233
            // and there are bot commands in the message, checking message entities
234
            if ($this->message_commands_set && isset($update['message']['entities']) && $update['message']['entities'][0]['type'] === 'bot_command') {
235
236
                // The lenght of the command
237
                $length = $update['message']['entities'][0]['length'];
238
239
                // Offset of the command
240
                $offset = $update['message']['entities'][0]['offset'];
241
242
                // For each command added by the user
243
                foreach ($this->message_commands as $trigger) {
244
245
                    // If the current command is a regex
246
                    if ($trigger['regex_active']) {
247
248
                        // Use preg_match to check if it is true
249
                        $matched = preg_match('/' . $trigger['regex_rule'] . '/', substr($update['message']['text'], $offset + 1, $length));
250
251
                        // else check if the command sent by the user is the same as the one we are expecting
252
                    } else if ($trigger['length'] == $length && mb_strpos($trigger['command'], $update['message']['text'], $offset) !== false) {
253
254
                        // We found a valid command
255
                        $matched = true;
256
257
                    } else {
258
259
                        // We did not
260
                        $matched = false;
261
262
                    }
263
264
                    // Check the results for the current command
265
                    if ($matched) {
266
267
                        // Execute script,
268
                        $trigger['script']($this, $update['message']);
269
270
                        // clear text variable
271
                        unset($this->_text);
272
273
                        // and return the id of the current update to stop processing this update
274
                        return $update['update_id'];
275
276
                    }
277
278
                }
279
280
            }
281
282
            // And process it
283
            $this->processMessage($update['message']);
284
285
            // clear text variable
286
            unset($this->_text);
287
288
            // If the update is a callback query
289
        } elseif (isset($update['callback_query'])) {
290
291
            // Set variables
292
            $this->_chat_id = $update['callback_query']['from']['id'];
293
            $this->_callback_query_id = $update['callback_query']['id'];
294
295
            // If data is set for the current callback query
296
            if (isset($update['callback_query']['data'])) {
297
298
                $this->_data = $update['callback_query']['data'];
299
300
            }
301
302
            // Check for callback commands
303
            if (isset($this->_data) && $this->callback_commands_set) {
304
305
                // Parse all commands
306
                foreach ($this->callback_commands as $trigger) {
307
308
                    // If command is found in callback data
309
                    if (strpos($trigger['data'], $this->_data) !== false) {
310
311
                        // Trigger the script
312
                        $trigger['script']($this, $update['callback_query']);
313
314
                        // Clear data
315
                        unset($this->_data);
316
                        unset($this->_callback_query_id);
317
318
                        // and return the id of the current update
319
                        return $update['update_id'];
320
321
                    }
322
323
                }
324
325
            }
326
327
            // Process the callback query through processCallbackQuery
328
            $this->processCallbackQuery($update['callback_query']);
329
330
            // Unset callback query variables
331
            unset($this->_callback_query_id);
332
            unset($this->_data);
333
334
        } elseif (isset($update['inline_query'])) {
335
336
            $this->_chat_id = $update['inline_query']['from']['id'];
337
            $this->_query = $update['inline_query']['query'];
338
            $this->_inline_query_id = $update['inline_query']['id'];
339
340
            $this->processInlineQuery($update['inline_query']);
341
342
            unset($this->_query);
343
            unset($this->_inline_query_id);
344
345
        } elseif (isset($update['channel_post'])) {
346
347
            // Set data from the post
348
            $this->_chat_id = $update['channel_post']['chat']['id'];
349
350
            $this->processChannelPost($update['channel_post']);
351
352
        } elseif (isset($update['edited_message'])) {
353
354
            $this->_chat_id = $update['edited_message']['chat']['id'];
355
356
            $this->processEditedMessage($update['edited_message']);
357
358
        } elseif (isset($update['edited_channel_post'])) {
359
360
            $this->_chat_id = $update['edited_channel_post']['chat']['id'];
361
362
            $this->processEditedChannelPost($update['edited_channel_post']);
363
364
        } elseif (isset($update['chosen_inline_result'])) {
365
366
            $this->_chat_id = $update['chosen_inline_result']['chat']['id'];
367
368
            $this->processChosenInlineResult($update['chosen_inline_result']);
369
370
        }
371
372
        return $update['update_id'];
373
374
    }
375
376
    /** @} */
377
378
    /**
379
     * \addtogroup Bot Bot
380
     * @{
381
     */
382
383
    /**
384
     * \brief Called every message received by the bot.
385
     * \details Override it to script the bot answer for each message.
386
     * <code>$chat_id</code> and <code>$text</code>, if the message contains text(use getMessageText() to access it), set inside of this function.
387
     * @param $message Reference to the message received.
388
     */
389
    protected function processMessage($message) {}
390
391
        /**
392
         * \brief Called every callback query received by the bot.
393
         * \details Override it to script the bot answer for each callback.
394
         * <code>$chat_id</code> and <code>$data</code>, if set in the callback query(use getCallbackData() to access it) set inside of this function.
395
         * @param $callback_query Reference to the callback query received.
396
         */
397
        protected function processCallbackQuery($callback_query) {
398
399
        }
400
401
    /**
402
     * \brief Called every inline query received by the bot.
403
     * \details Override it to script the bot answer for each inline query.
404
     * $chat_id and $query(use getInlineQuery() to access it) set inside of this function.
405
     * @param $inline_query Reference to the inline query received.
406
     */
407
    protected function processInlineQuery($inline_query) {
408
409
    }
410
411
    /**
412
     * \brief Called every chosen inline result received by the bot.
413
     * \details Override it to script the bot answer for each chosen inline result.
414
     * <code>$chat_id</code> set inside of this function.
415
     * @param $chosen_inline_result Reference to the chosen inline result received.
416
     */
417
    protected function processChosenInlineResult($chosen_inline_result) {
418
419
    }
420
421
    /**
422
     * \brief Called every chosen edited message received by the bot.
423
     * \details Override it to script the bot answer for each edited message.
424
     * <code>$chat_id</code> set inside of this function.
425
     * @param $edited_message The message edited by the user.
426
     */
427
    protected function processEditedMessage($edited_message) {
428
429
    }
430
431
    /**
432
     * \brief Called every new post in the channel where the bot is in.
433
     * \details Override it to script the bot answer for each post sent in a channel.
434
     * <code>$chat_id</code> set inside of this function.
435
     * @param $post The message sent in the channel.
436
     */
437
    protected function processChannelPost($post) {
438
439
    }
440
441
    /**
442
     * \brief Called every time a post get edited in the channel where the bot is in.
443
     * \details Override it to script the bot answer for each post edited  in a channel.
444
     * <code>$chat_id</code> set inside of this function.
445
     * @param $post The message edited in the channel.
446
     */
447
    protected function processEditedChannelPost($edited_post) {
448
449
    }
450
451
    /**
452
     * \brief Get updates received by the bot, using redis to save and get the last offset.
453
     * \details It check if an offset exists on redis, then get it, or call getUpdates to set it.
454
     * Then it start an infinite loop where it process updates and update the offset on redis.
455
     * Each update is surrounded by a try/catch.
456
     * @see getUpdates
457
     * @param $limit <i>Optional</i>. Limits the number of updates to be retrieved. Values between 1—100 are accepted.
458
     * @param $timeout <i>Optional</i>. Timeout in seconds for long polling.
459
     * @param $offset_key <i>Optional</i>. Name of the variable where the offset is saved on Redis
460
     */
461
    public function getUpdatesRedis(int $limit = 100, int $timeout = 60, string $offset_key = 'offset') {
462
463
        // Check redis connection
464
        if (!isset($this->redis)) {
465
466
            throw new BotException("Redis connection is not set");
467
468
        }
469
470
        // If offset is already set in redis
471
        if ($this->redis->exists($variable_name)) {
472
473
            // just set $offset as the same value
474
            $offset = $this->redis->get($variable_name);
475
476
        } else {
477
            // Else get the offset from the id from the first update received
478
479
            do {
480
481
                $update = $this->getUpdates(0, 1);
482
483
            } while (empty($update));
484
485
            $offset = $update[0]['update_id'];
486
487
            $this->redis->set($variable_name, $offset);
488
489
            $update = null;
490
491
        }
492
493
        $this->initBot();
494
495
        // Process all updates received
496
        while (true) {
497
498
            $updates = $this->getUpdates($offset, $limit, $timeout);
499
500
            // Parse all updates received
501
            foreach ($updates as $key => $update) {
502
503
                try {
504
505
                    $this->processUpdate($update);
506
507
                } catch (BotException $e) {
508
509
                    echo $e->getMessage();
510
511
                }
512
513
            }
514
515
            // Update the offset in redis
516
            $this->redis->set($variable_name, $offset + count($updates));
517
        }
518
519
    }
520
521
    /**
522
     * \brief Get updates received by the bot, and hold the offset in $offset.
523
     * \details Get the update_id of the first update to parse, set it in $offset and
524
     * then it start an infinite loop where it processes updates and keep $offset on the update_id of the last update received.
525
     * Each processUpdate() method call is surrounded by a try/catch.
526
     * @see getUpdates
527
     * @param $limit <i>Optional</i>. Limits the number of updates to be retrieved. Values between 1—100 are accepted.
528
     * @param $timeout <i>Optional</i>. Timeout in seconds for long polling.
529
     */
530
    public function getUpdatesLocal(int $limit = 100, int $timeout = 60) {
531
532
        $update = [];
533
534
        // While there aren't updates to process
535
        do {
536
537
            // Get updates from telegram
538
            $update = $this->getUpdates(0, 1);
539
540
            // While in the array received there aren't updates
541
        } while (empty($update));
542
543
        // Set the offset to the first update recevied
544
        $offset = $update[0]['update_id'];
545
546
        $update = null;
547
548
        $this->initBot();
549
550
        // Process all updates
551
        while (true) {
552
553
            // Set parameter for the url call
554
            $parameters = [
555
                'offset' => $offset,
556
                'limit' => $limit,
557
                'timeout' => $timeout
558
            ];
559
560
            $updates = $this->exec_curl_request($this->_api_url . 'getUpdates?' . http_build_query($parameters));
561
562
            // Parse all update to receive
563
            foreach ($updates as $key => $update) {
564
565
                try {
566
567
                    // Process one at a time
568
                    $this->processUpdate($update);
569
570
                } catch (BotException $e) {
571
572
                    echo $e->getMessage();
573
574
                }
575
576
            }
577
578
            // Update the offset
579
            $offset += sizeof($updates);
580
581
        }
582
583
    }
584
585
    /**
586
     * \brief Get updates received by the bot, using the sql database to store and get the last offset.
587
     * \details It check if an offset exists on redis, then get it, or call getUpdates to set it.
588
     * Then it start an infinite loop where it process updates and update the offset on redis.
589
     * Each update is surrounded by a try/catch.
590
     * @see getUpdates
591
     * @param $limit <i>Optional</i>. Limits the number of updates to be retrieved. Values between 1—100 are accepted.
592
     * @param $timeout <i>Optional</i>. Timeout in seconds for long polling.
593
     * @param $table_name <i>Optional</i>. Name of the table where offset is saved in the database
594
     * @param $column_name <i>Optional</i>. Name of the column where the offset is saved in the database
595
     */
596
    public function getUpdatesDatabase(int $limit = 100, int $timeout = 0, string $table_name = 'telegram', string $column_name = 'bot_offset') {
597
598
        if (!isset($this->_database)) {
599
600
            throw new BotException("Database connection is not set");
601
602
        }
603
604
        // Get the offset from the database
605
        $sth = $this->pdo->prepare('SELECT ' . $column_name . ' FROM ' . $table_name);
606
607
        try {
608
609
            $sth->execute();
610
611
        } catch (PDOException $e) {
612
613
            echo $e->getMessage();
614
615
        }
616
617
        $offset = $sth->fetchColumn();
618
        $sth = null;
619
620
        // Get the offset from the first update to update
621
        if ($offset === false) {
622
623
            do {
624
625
                $update = $this->getUpdates(0, 1);
626
627
            } while (empty($update));
628
629
            $offset = $update[0]['update_id'];
630
631
            $update = null;
632
633
        }
634
635
        // Prepare the query for updating the offset in the database
636
        $sth = $this->pdo->prepare('UPDATE "' . $table_name . '" SET "' . $column_name . '" = :new_offset');
637
638
        $this->initBot();
639
640
        while (true) {
641
642
            $updates = $this->getUpdates($offset, $limit, $timeout);
643
644
            foreach ($updates as $key => $update) {
645
646
                try {
647
648
                    $this->processUpdate($update);
649
650
                } catch (BotException $e) {
651
652
                    echo $e->getMessage();
653
654
                }
655
656
            }
657
658
            // Update the offset on the database
659
            $sth->bindParam(':new_offset', $offset + sizeof($updates));
660
            $sth->execute();
661
        }
662
    }
663
664
    /**
665
     * \brief Add a function that will be executed everytime a message contain the selected command
666
     * \details Use this syntax:
667
     *
668
     *     addMessageCommand("start", function($bot, $message) {
669
     *         $bot->sendMessage("Hi"); });
670
     * @param $command The command that will trigger this function (without slash). Eg: "start", "help", "about"
671
     * @param $script The function that will be triggered by a command. Must take an object(the bot) and an array(the message received).
672
     */
673
    public function addMessageCommand(string $command, callable $script) {
674
675
        $this->message_commands[] = [
676
            'script' => $script,
677
            'command' => '/' . $command,
678
            'length' => strlen($command) + 1,
679
            'regex_active' => false
680
        ];
681
682
    }
683
684
    /**
685
     * \brief Add a function that will be executed everytime a message contain a command that match the regex
686
     * \details Use this syntax:
687
     *
688
     *     addMessageCommandRegex("number\d", function($bot, $message, $result) {
689
     *         $bot->sendMessage("You sent me a number"); });
690
     * @param $regex_rule Regex rule that will called for evalueting the command received.
691
     * @param $script The function that will be triggered by a command. Must take an object(the bot) and an array(the message received).
692
     */
693
    public function addMessageCommandRegex(string $regex_rule, callable $script) {
694
695
        $this->message_commands[] = [
696
            'script' => $script,
697
            'regex_active' => true,
698
            'regex_rule' => $regex_rule
699
        ];
700
701
    }
702
703
    /**
704
     * \brief Add a function that will be executed everytime a callback query contains a string as data
705
     * \details Use this syntax:
706
     *
707
     *     addMessageCommand("menu", function($bot, $callback_query) {
708
     *         $bot->editMessageText($callback_query['message']['message_id'], "This is the menu"); });
709
     * @param $data The string that will trigger this function.
710
     * @param $script The function that will be triggered by the callback query if it contains the $data string. Must take an object(the bot) and an array(the callback query received).
711
     */
712
    public function addCallbackCommand(string $data, callable $script) {
713
714
        $this->callback_commands[] = [
715
            'data' => $data,
716
            'script' => $script,
717
        ];
718
719
    }
720
721
    /** @} */
722
723
    /**
724
     * \addtogroup Multilanguage Multilanguage
725
     * @{
726
     */
727
728
    /**
729
     * \brief Get current user language from the database, and set it in $language.
730
     * @param $default_language <i>Optional</i>. Default language to return in case of errors.
731
     * @return Language set for the current user, $default_language on errors.
732
     */
733
    public function getLanguageDatabase($default_language = 'en') {
734
735
        // If we have no database
736
        if (!isset($this->_database)) {
737
738
            // Set the language to english
739
            $this->language = $default_language;
740
741
            // Return english
742
            return $default_language;
743
744
        }
745
746
        // Get the language from the bot
747
        $sth = $this->pdo->prepare('SELECT language FROM ' . $this->user_table . ' WHERE ' . $this->id_column . ' = :chat_id');
748
        $sth->bindParam(':chat_id', $this->_chat_id);
749
750
        try {
751
752
            $sth->execute();
753
754
        } catch (PDOException $e) {
755
756
            echo $e->getMessage();
757
758
        }
759
760
        $row = $sth->fetch();
761
762
        $sth = null;
763
764
        // If we got the language
765
        if (isset($row['language'])) {
766
767
            // Set the language in the bot
768
            $this->language = $row['language'];
769
770
            // And return it
771
            return $row['language'];
772
773
        }
774
775
        // If we couldn't get it, set the language to english
776
        $this->language = $default_language;
777
778
        // and return english
779
        return $this->language;
780
781
    }
782
783
    /**
784
     * \brief Get current user language from redis, and set it in language.
785
     * \details Using redis database we get language stored and the value does not expires.
786
     * @param $default_language <i>Optional</i>. Default language to return in case of errors.
787
     * @return Language for the current user, $default_language on errors.
788
     */
789
    public function getLanguageRedis($default_language = 'en') : string {
790
791
        // If redis or pdo connection are not set
792
        if (!isset($this->redis)) {
793
794
            // return default language
795
            return $default_language;
796
797
        }
798
799
        // Does it exists on redis?
800
        if ($this->redis->exists($this->_chat_id . ':language')) {
801
802
            // Get the value
803
            $this->language = $this->redis->get($this->_chat_id . ':language');
804
            return $this->language;
805
806
        }
807
808
        // If it doens't exist, set $language to $default_language
809
        $this->language = $default_language;
810
811
        // and return it
812
        return $this->language;
813
814
    }
815
816
    /**
817
     * \brief Get current user language from redis, as a cache, and set it in language.
818
     * \details Using redis database as cache, seeks the language in it, if there isn't
819
     * then get the language from the sql database and store it (with default expiring of one day) in redis.
820
     * It also change $language parameter of the bot to the language returned.
821
     * @param $default_language <i>Optional</i>. Default language to return in case of errors.
822
     * @param $expiring_time <i>Optional</i>. Set the expiring time for the language on redis each time it is took from the sql database.
823
     * @return Language for the current user, $default_language on errors.
824
     */
825
    public function getLanguageRedisAsCache($default_language = 'en', $expiring_time = '86400') : string {
826
827
        // If redis or pdo connection are not set
828
        if (!isset($this->redis) || !isset($this->pdo)) {
829
830
            // return default language
831
            return $default_language;
832
833
        }
834
835
        // Does it exists on redis?
836
        if ($this->redis->exists($this->_chat_id . ':language')) {
837
838
            // Get the value
839
            $this->language = $this->redis->get($this->_chat_id . ':language');
840
            return $this->language;
841
842
        }
843
844
        // Set the value from the db
845
        $this->redis->setEx($this->_chat_id . ':language', $expiring_time, $this->getLanguageDatabase($default_language));
846
847
        // and return it
848
        return $this->language;
849
850
    }
851
852
    /**
853
     * \brief Set the current user language in both redis, sql database and $language.
854
     * \details Save it on database first, then create the expiring key on redis.
855
     * @param $language The new language to set.
856
     * @param $expiring_time <i>Optional</i>. Time for the language key in redis to expire.
857
     * @return On sucess, return true, throw exception otherwise.
858
     */
859
    public function setLanguageRedisAsCache($language, $expiring_time = '86400') {
860
861
        // Check database connection
862
        if (!isset($this->_database) && !isset($this->redis)) {
863
            throw new BotException('Database connection not set');
864
        }
865
866
        // Update the language in the database
867
        $sth = $this->pdo->prepare('UPDATE ' . $this->user_table . ' SET language = :language WHERE ' . $this->id_column . ' = :id');
868
        $sth->bindParam(':language', $language);
869
        $sth->bindParam(':id', $this->_chat_id);
870
871
        try {
872
873
            $sth->execute();
874
875
        } catch (PDOException $e) {
876
877
            throw new BotException($e->getMessage());
878
879
        }
880
881
        // Destroy statement
882
        $sth = null;
883
884
        // Set the language in redis with expiring
885
        $this->redis->setEx($this->_chat_id . ':language', $expiring_time, $language);
886
887
        // Set language in the bot variable
888
        $this->language = $language;
889
    }
890
891
    /**
892
     * \brief Load localization files (JSON-serialized) from a folder and set them in $local variable.
893
     * \details Save all localization files, saved as json format, from a directory and put the contents in $local variable.
894
     * Each file will be saved into $local with the first two letters of the filename as the index.
895
     * Access the english data as $this->local["en"]["Your key"].
896
     * File <code>./localization/en.json</code>:
897
     *
898
     *     {"Hello_Msg": "Hello"}
899
     *
900
     * File <code>./localization/it.json</code>:
901
     *
902
     *     {"Hello_Msg": "Ciao"}
903
     *
904
     * Usage in <code>processMessage()</code>:
905
     *
906
     *     $sendMessage($this->local[$this->language]["Hello_Msg"]);
907
     *
908
     * @param $dir Directory where the localization files are saved.
909
     */
910
    public function loadLocalization($dir = './localization') {
911
912
        // Open directory
913
        if ($handle = opendir($dir)) {
914
915
            // Iterate over all files
916
            while (false !== ($file = readdir($handle))) {
917
918
                // If the file is a JSON data file
919
                if (strlen($file) > 6 && substr($file, -5) === '.json') {
920
921
                    try {
922
923
                        // Add the contents of the file to the $local variable, after deserializng it from JSON format
924
                        // The contents will be added with the 2 letter of the file as the index
925
                        $this->local[substr($file, 0, 2)] = json_decode(file_get_contents("$dir/$file"), true);
926
927
                    } catch (BotException $e) {
928
929
                        echo $e->getMessage();
930
931
                    }
932
933
                }
934
935
            }
936
937
        }
938
939
    }
940
941
    /** @} */
942
943
    /**
944
     * \addtogroup State
945
     * \brief Create a state based bot using these methods.
946
     * \details Bot will answer in different way based on the state.
947
     * Here is an example where we use save user credential using bot states:
948
     *
949
     *     <?php
950
     *
951
     *     // Include the framework
952
     *     require './vendor/autoload.php';
953
     *
954
     *     // Define bot state
955
     *     define("SEND_USERNAME", 1);
956
     *     define("SEND_PASSWORD", 2);
957
     *
958
     *     // Create the class for the bot that will handle login
959
     *     class LoginBot extends DanySpin97\PhpBotFramework\Bot {
960
     *
961
     *         // Add the function for processing messages
962
     *         protected function processMessage($message) {
963
     *
964
     *             switch($this->getStatus()) {
965
     *
966
     *                 // If we are expecting a username from the user
967
     *                 case SEND_USERNAME:
968
     *
969
     *                     // Save the username
970
     *
971
     *                     // Say the user to insert the password
972
     *                     $this->sendMessage("Please, send your password.");
973
     *
974
     *                     // Update the bot state
975
     *                     $this->setStatus(SEND_PASSWORD);
976
     *
977
     *                     break;
978
     *
979
     *                 // Or if we are expecting a password from the user
980
     *                 case SEND_PASSWORD:
981
     *
982
     *                     // Save the password
983
     *
984
     *                     // Say the user he completed the process
985
     *                     $this->sendMessage("The registration is complete");
986
     *
987
     *                     break;
988
     *                 }
989
     *
990
     *         }
991
     *
992
     *     }
993
     *
994
     *     // Create the bot
995
     *     $bot = new LoginBot("token");
996
     *
997
     *     // Create redis object
998
     *     $bot->redis = new Redis();
999
     *
1000
     *     // Connect to redis database
1001
     *     $bot->redis->connect('127.0.0.1');
1002
     *
1003
     *     // Create the awnser to the <code>/start</code> command
1004
     *     $start_closure = function($bot, $message) {
1005
     *
1006
     *         // saying the user to enter a username
1007
     *         $bot->sendMessage("Please, send your username.");
1008
     *
1009
     *         // and update the status
1010
     *         $bot->setStatus(SEND_USERNAME);
1011
     *     };
1012
     *
1013
     *     // Add the answer
1014
     *     $bot->addMessageCommand("start", $start_closure);
1015
     *
1016
     *     $bot->getUpdatesLocal();
1017
     * @{
1018
     */
1019
1020
    /**
1021
     * \brief Get current user status from redis and set it in status variable.
1022
     * \details Throw exception if redis connection is missing.
1023
     * @param $default_status <i>Optional</i>. The default status to return in case there is no status for the current user.
1024
     * @return The status for the current user, $default_status if missing.
1025
     */
1026
    public function getStatus(int $default_status = -1) : int {
1027
1028
        if (!isset($this->redis)) {
1029
1030
            throw new BotException('Redis connection not set');
1031
1032
        }
1033
1034
        if ($this->redis->exists($this->_chat_id . ':status')) {
1035
1036
            $this->status = $this->redis->get($this->_chat_id . ':status');
1037
1038
            return $this->status;
1039
1040
        }
1041
1042
        $this->redis->set($this->_chat_id . ':status', $default_status);
1043
        $this->status = $default_status;
1044
        return $default_status;
1045
1046
    }
1047
1048
    /** \brief Set the status of the bot in both redis and $status.
1049
     * \details Throw exception if redis connection is missing.
1050
     * @param $status The new status of the bot.
1051
     */
1052
    public function setStatus(int $status) {
1053
1054
        $this->redis->set($this->_chat_id . ':status', $status);
1055
1056
        $this->status = $status;
1057
1058
    }
1059
1060
    /** @} */
1061
1062
    /**
1063
     * \addtogroup Users-handle Users handling
1064
     * \brief Handle bot users on the database.
1065
     * @{
1066
     */
1067
1068
    /** \brief Add a user to the database.
1069
     * \details Add a user to the database in Bot::$user_table table and Bot::$id_column column using Bot::$pdo connection.
1070
     * @param $chat_id chat_id of the user to add.
1071
     * @return True on success.
1072
     */
1073
    public function addUser($chat_id) : bool {
1074
1075
        // Is there database connection?
1076
        if (!isset($this->pdo)) {
1077
1078
            throw new BotException("Database connection not set");
1079
1080
        }
1081
1082
        // Create insertion query and initialize variable
1083
        $query = "INSERT INTO $this->user_table ($this->id_column) VALUES (:chat_id)";
1084
1085
        // Prepare the query
1086
        $sth = $this->pdo->prepare($query);
1087
1088
        // Add the chat_id to the query
1089
        $sth->bindParam(':chat_id', $chat_id);
1090
1091
        try {
1092
1093
            $sth->execute();
1094
            $success = true;
1095
1096
        } catch (PDOException $e) {
1097
1098
            echo $e->getMessage();
1099
1100
            $success = false;
1101
1102
        }
1103
1104
        // Close statement
1105
        $sth = null;
1106
1107
        // Return result
1108
        return $success;
1109
1110
    }
1111
1112
    /**
1113
     * \brief Broadcast a message to all user registred on the database.
1114
     * \details Send a message to all users subscribed, change Bot::$user_table and Bot::$id_column to match your database structure is.
1115
     * This method requires Bot::$pdo connection set.
1116
     * All parameters are the same as CoreBot::sendMessage.
1117
     * Because a limitation of Telegram Bot API the bot will have a delay after 20 messages sent in different chats.
1118
     * @see CoreBot::sendMessage
1119
     */
1120
    public function broadcastMessage($text, string $reply_markup = null, string $parse_mode = 'HTML', bool $disable_web_preview = true, bool $disable_notification = false) {
1121
1122
        // Is there database connection?
1123
        if (!isset($this->pdo)) {
1124
1125
            throw new BotException("Database connection not set");
1126
1127
        }
1128
1129
        // Prepare the query to get all chat_id from the database
1130
        $sth = $this->pdo->prepare("SELECT $this->id_column FROM $this->user_table");
1131
1132
        try {
1133
1134
            $sth->execute();
1135
1136
        } catch (PDOException $e) {
1137
1138
            echo $e->getMessage();
1139
1140
        }
1141
1142
        // Iterate over all the row got
1143
        while ($user = $sth->fetch()) {
1144
1145
            // Call getChat to know that this users haven't blocked the bot
1146
            $user_data = $this->getChat($user[$this->id_column]);
1147
1148
            // Did they block it?
1149
            if ($user_data !== false) {
1150
1151
                // Change the chat_id for the next API method
1152
                $this->setChatID($user[$this->id_column]);
1153
1154
                // Send the message
1155
                $this->sendMessage($text, $reply_markup, null, $parse_mode, $disable_web_preview, $disable_notification);
1156
1157
            }
1158
1159
        }
1160
1161
        // Close statement
1162
        $sth = null;
1163
1164
    }
1165
1166
    /** @} */
1167
1168
}
1169