Completed
Push — master ( 695e63...f2fe6e )
by Armando
02:21 queued 01:02
created

src/DB.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
 * Written by Marco Boretto <[email protected]>
10
 */
11
12
namespace Longman\TelegramBot;
13
14
use Exception;
15
use Longman\TelegramBot\Entities\CallbackQuery;
16
use Longman\TelegramBot\Entities\Chat;
17
use Longman\TelegramBot\Entities\ChosenInlineResult;
18
use Longman\TelegramBot\Entities\InlineQuery;
19
use Longman\TelegramBot\Entities\Message;
20
use Longman\TelegramBot\Entities\ReplyToMessage;
21
use Longman\TelegramBot\Entities\Update;
22
use Longman\TelegramBot\Entities\User;
23
use Longman\TelegramBot\Exception\TelegramException;
24
use PDO;
25
use PDOException;
26
27
class DB
28
{
29
    /**
30
     * MySQL credentials
31
     *
32
     * @var array
33
     */
34
    protected static $mysql_credentials = [];
35
36
    /**
37
     * PDO object
38
     *
39
     * @var PDO
40
     */
41
    protected static $pdo;
42
43
    /**
44
     * Table prefix
45
     *
46
     * @var string
47
     */
48
    protected static $table_prefix;
49
50
    /**
51
     * Telegram class object
52
     *
53
     * @var Telegram
54
     */
55
    protected static $telegram;
56
57
    /**
58
     * Initialize
59
     *
60
     * @param array    $credentials  Database connection details
61
     * @param Telegram $telegram     Telegram object to connect with this object
62
     * @param string   $table_prefix Table prefix
63
     * @param string   $encoding     Database character encoding
64
     *
65
     * @return PDO PDO database object
66
     * @throws TelegramException
67
     */
68 9
    public static function initialize(
69
        array $credentials,
70
        Telegram $telegram,
71
        $table_prefix = null,
72
        $encoding = 'utf8mb4'
73
    ) {
74 9
        if (empty($credentials)) {
75
            throw new TelegramException('MySQL credentials not provided!');
76
        }
77
78 9
        $dsn = 'mysql:host=' . $credentials['host'] . ';dbname=' . $credentials['database'];
79 9
        if (!empty($credentials['port'])) {
80
            $dsn .= ';port=' . $credentials['port'];
81
        }
82
83 9
        $options = [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES ' . $encoding];
84
        try {
85 9
            $pdo = new PDO($dsn, $credentials['user'], $credentials['password'], $options);
86 9
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
87
        } catch (PDOException $e) {
88
            throw new TelegramException($e->getMessage());
89
        }
90
91 9
        self::$pdo               = $pdo;
92 9
        self::$telegram          = $telegram;
93 9
        self::$mysql_credentials = $credentials;
94 9
        self::$table_prefix      = $table_prefix;
95
96 9
        self::defineTables();
97
98 9
        return self::$pdo;
99
    }
100
101
    /**
102
     * External Initialize
103
     *
104
     * Let you use the class with an external already existing Pdo Mysql connection.
105
     *
106
     * @param PDO      $external_pdo_connection PDO database object
107
     * @param Telegram $telegram                Telegram object to connect with this object
108
     * @param string   $table_prefix            Table prefix
109
     *
110
     * @return PDO PDO database object
111
     * @throws TelegramException
112
     */
113
    public static function externalInitialize(
114
        $external_pdo_connection,
115
        Telegram $telegram,
116
        $table_prefix = null
117
    ) {
118
        if ($external_pdo_connection === null) {
119
            throw new TelegramException('MySQL external connection not provided!');
120
        }
121
122
        self::$pdo               = $external_pdo_connection;
123
        self::$telegram          = $telegram;
124
        self::$mysql_credentials = [];
125
        self::$table_prefix      = $table_prefix;
126
127
        self::defineTables();
128
129
        return self::$pdo;
130
    }
131
132
    /**
133
     * Define all the tables with the proper prefix
134
     */
135 9
    protected static function defineTables()
136
    {
137
        $tables = [
138 9
            'callback_query',
139
            'chat',
140
            'chosen_inline_result',
141
            'edited_message',
142
            'inline_query',
143
            'message',
144
            'request_limiter',
145
            'telegram_update',
146
            'user',
147
            'user_chat',
148
        ];
149 9
        foreach ($tables as $table) {
150 9
            $table_name = 'TB_' . strtoupper($table);
151 9
            if (!defined($table_name)) {
152 1
                define($table_name, self::$table_prefix . $table);
153
            }
154
        }
155 9
    }
156
157
    /**
158
     * Check if database connection has been created
159
     *
160
     * @return bool
161
     */
162 9
    public static function isDbConnected()
163
    {
164 9
        return self::$pdo !== null;
165
    }
166
167
    /**
168
     * Get the PDO object of the connected database
169
     *
170
     * @return PDO
171
     */
172
    public static function getPdo()
173
    {
174
        return self::$pdo;
175
    }
176
177
    /**
178
     * Fetch update(s) from DB
179
     *
180
     * @param int    $limit Limit the number of updates to fetch
181
     * @param string $id    Check for unique update id
182
     *
183
     * @return array|bool Fetched data or false if not connected
184
     * @throws TelegramException
185
     */
186
    public static function selectTelegramUpdate($limit = null, $id = null)
187
    {
188
        if (!self::isDbConnected()) {
189
            return false;
190
        }
191
192
        try {
193
            $sql = '
194
                SELECT `id`
195
                FROM `' . TB_TELEGRAM_UPDATE . '`
196
            ';
197
198
            if ($id !== null) {
199
                $sql .= ' WHERE `id` = :id';
200
            } else {
201
                $sql .= ' ORDER BY `id` DESC';
202
            }
203
204
            if ($limit !== null) {
205
                $sql .= ' LIMIT :limit';
206
            }
207
208
            $sth = self::$pdo->prepare($sql);
209
210
            if ($limit !== null) {
211
                $sth->bindValue(':limit', $limit, PDO::PARAM_INT);
212
            }
213
            if ($id !== null) {
214
                $sth->bindValue(':id', $id);
215
            }
216
217
            $sth->execute();
218
219
            return $sth->fetchAll(PDO::FETCH_ASSOC);
220
        } catch (PDOException $e) {
221
            throw new TelegramException($e->getMessage());
222
        }
223
    }
224
225
    /**
226
     * Fetch message(s) from DB
227
     *
228
     * @param int $limit Limit the number of messages to fetch
229
     *
230
     * @return array|bool Fetched data or false if not connected
231
     * @throws TelegramException
232
     */
233
    public static function selectMessages($limit = null)
234
    {
235
        if (!self::isDbConnected()) {
236
            return false;
237
        }
238
239
        try {
240
            $sql = '
241
                SELECT *
242
                FROM `' . TB_MESSAGE . '`
243
                ORDER BY `id` DESC
244
            ';
245
246
            if ($limit !== null) {
247
                $sql .= ' LIMIT :limit';
248
            }
249
250
            $sth = self::$pdo->prepare($sql);
251
252
            if ($limit !== null) {
253
                $sth->bindValue(':limit', $limit, PDO::PARAM_INT);
254
            }
255
256
            $sth->execute();
257
258
            return $sth->fetchAll(PDO::FETCH_ASSOC);
259
        } catch (PDOException $e) {
260
            throw new TelegramException($e->getMessage());
261
        }
262
    }
263
264
    /**
265
     * Convert from unix timestamp to timestamp
266
     *
267
     * @param int $time Unix timestamp (if empty, current timestamp is used)
268
     *
269
     * @return string
270
     */
271 7
    protected static function getTimestamp($time = null)
272
    {
273 7
        return date('Y-m-d H:i:s', $time ?: time());
274
    }
275
276
    /**
277
     * Convert array of Entity items to a JSON array
278
     *
279
     * @todo Find a better way, as json_* functions are very heavy
280
     *
281
     * @param array|null $entities
282
     * @param mixed      $default
283
     *
284
     * @return mixed
285
     */
286 6
    public static function entitiesArrayToJson($entities, $default = null)
287
    {
288 6
        if (!is_array($entities)) {
289 6
            return $default;
290
        }
291
292
        // Convert each Entity item into an object based on its JSON reflection
293
        $json_entities = array_map(function ($entity) {
294
            return json_decode($entity, true);
295
        }, $entities);
296
297
        return json_encode($json_entities);
298
    }
299
300
    /**
301
     * Insert entry to telegram_update table
302
     *
303
     * @todo Add missing values! See https://core.telegram.org/bots/api#update
304
     *
305
     * @param string $id
306
     * @param string $chat_id
307
     * @param string $message_id
308
     * @param string $inline_query_id
309
     * @param string $chosen_inline_result_id
310
     * @param string $callback_query_id
311
     * @param string $edited_message_id
312
     *
313
     * @return bool If the insert was successful
314
     * @throws TelegramException
315
     */
316
    public static function insertTelegramUpdate(
317
        $id,
318
        $chat_id = null,
319
        $message_id = null,
320
        $inline_query_id = null,
321
        $chosen_inline_result_id = null,
322
        $callback_query_id = null,
323
        $edited_message_id = null
324
    ) {
325
        if ($message_id === null && $inline_query_id === null && $chosen_inline_result_id === null && $callback_query_id === null && $edited_message_id === null) {
326
            throw new TelegramException('message_id, inline_query_id, chosen_inline_result_id, callback_query_id, edited_message_id are all null');
327
        }
328
329
        if (!self::isDbConnected()) {
330
            return false;
331
        }
332
333
        try {
334
            $sth = self::$pdo->prepare('
335
                INSERT IGNORE INTO `' . TB_TELEGRAM_UPDATE . '`
336
                (`id`, `chat_id`, `message_id`, `inline_query_id`, `chosen_inline_result_id`, `callback_query_id`, `edited_message_id`)
337
                VALUES
338
                (:id, :chat_id, :message_id, :inline_query_id, :chosen_inline_result_id, :callback_query_id, :edited_message_id)
339
            ');
340
341
            $sth->bindValue(':id', $id);
342
            $sth->bindValue(':chat_id', $chat_id);
343
            $sth->bindValue(':message_id', $message_id);
344
            $sth->bindValue(':edited_message_id', $edited_message_id);
345
            $sth->bindValue(':inline_query_id', $inline_query_id);
346
            $sth->bindValue(':chosen_inline_result_id', $chosen_inline_result_id);
347
            $sth->bindValue(':callback_query_id', $callback_query_id);
348
349
            return $sth->execute();
350
        } catch (PDOException $e) {
351
            throw new TelegramException($e->getMessage());
352
        }
353
    }
354
355
    /**
356
     * Insert users and save their connection to chats
357
     *
358
     * @param User   $user
359
     * @param string $date
360
     * @param Chat   $chat
361
     *
362
     * @return bool If the insert was successful
363
     * @throws TelegramException
364
     */
365 6
    public static function insertUser(User $user, $date = null, Chat $chat = null)
366
    {
367 6
        if (!self::isDbConnected()) {
368
            return false;
369
        }
370
371
        try {
372 6
            $sth = self::$pdo->prepare('
373 6
                INSERT INTO `' . TB_USER . '`
374
                (`id`, `is_bot`, `username`, `first_name`, `last_name`, `language_code`, `created_at`, `updated_at`)
375
                VALUES
376
                (:id, :is_bot, :username, :first_name, :last_name, :language_code, :created_at, :updated_at)
377
                ON DUPLICATE KEY UPDATE
378
                    `is_bot`         = VALUES(`is_bot`),
379
                    `username`       = VALUES(`username`),
380
                    `first_name`     = VALUES(`first_name`),
381
                    `last_name`      = VALUES(`last_name`),
382
                    `language_code`  = VALUES(`language_code`),
383
                    `updated_at`     = VALUES(`updated_at`)
384
            ');
385
386 6
            $sth->bindValue(':id', $user->getId());
387 6
            $sth->bindValue(':is_bot', $user->getIsBot(), PDO::PARAM_INT);
388 6
            $sth->bindValue(':username', $user->getUsername());
389 6
            $sth->bindValue(':first_name', $user->getFirstName());
390 6
            $sth->bindValue(':last_name', $user->getLastName());
391 6
            $sth->bindValue(':language_code', $user->getLanguageCode());
392 6
            $date = $date ?: self::getTimestamp();
393 6
            $sth->bindValue(':created_at', $date);
394 6
            $sth->bindValue(':updated_at', $date);
395
396 6
            $status = $sth->execute();
397
        } catch (PDOException $e) {
398
            throw new TelegramException($e->getMessage());
399
        }
400
401
        // Also insert the relationship to the chat into the user_chat table
402 6
        if ($chat instanceof Chat) {
403
            try {
404 6
                $sth = self::$pdo->prepare('
405 6
                    INSERT IGNORE INTO `' . TB_USER_CHAT . '`
406
                    (`user_id`, `chat_id`)
407
                    VALUES
408
                    (:user_id, :chat_id)
409
                ');
410
411 6
                $sth->bindValue(':user_id', $user->getId());
412 6
                $sth->bindValue(':chat_id', $chat->getId());
413
414 6
                $status = $sth->execute();
415
            } catch (PDOException $e) {
416
                throw new TelegramException($e->getMessage());
417
            }
418
        }
419
420 6
        return $status;
421
    }
422
423
    /**
424
     * Insert chat
425
     *
426
     * @param Chat   $chat
427
     * @param string $date
428
     * @param string $migrate_to_chat_id
429
     *
430
     * @return bool If the insert was successful
431
     * @throws TelegramException
432
     */
433 6
    public static function insertChat(Chat $chat, $date = null, $migrate_to_chat_id = null)
434
    {
435 6
        if (!self::isDbConnected()) {
436
            return false;
437
        }
438
439
        try {
440 6
            $sth = self::$pdo->prepare('
441 6
                INSERT IGNORE INTO `' . TB_CHAT . '`
442
                (`id`, `type`, `title`, `username`, `all_members_are_administrators`, `created_at` ,`updated_at`, `old_id`)
443
                VALUES
444
                (:id, :type, :title, :username, :all_members_are_administrators, :created_at, :updated_at, :old_id)
445
                ON DUPLICATE KEY UPDATE
446
                    `type`                           = VALUES(`type`),
447
                    `title`                          = VALUES(`title`),
448
                    `username`                       = VALUES(`username`),
449
                    `all_members_are_administrators` = VALUES(`all_members_are_administrators`),
450
                    `updated_at`                     = VALUES(`updated_at`)
451
            ');
452
453 6
            $chat_id   = $chat->getId();
454 6
            $chat_type = $chat->getType();
455
456 6
            if ($migrate_to_chat_id !== null) {
457
                $chat_type = 'supergroup';
458
459
                $sth->bindValue(':id', $migrate_to_chat_id);
460
                $sth->bindValue(':old_id', $chat_id);
461
            } else {
462 6
                $sth->bindValue(':id', $chat_id);
463 6
                $sth->bindValue(':old_id', $migrate_to_chat_id);
464
            }
465
466 6
            $sth->bindValue(':type', $chat_type);
467 6
            $sth->bindValue(':title', $chat->getTitle());
468 6
            $sth->bindValue(':username', $chat->getUsername());
469 6
            $sth->bindValue(':all_members_are_administrators', $chat->getAllMembersAreAdministrators(), PDO::PARAM_INT);
470 6
            $date = $date ?: self::getTimestamp();
471 6
            $sth->bindValue(':created_at', $date);
472 6
            $sth->bindValue(':updated_at', $date);
473
474 6
            return $sth->execute();
475
        } catch (PDOException $e) {
476
            throw new TelegramException($e->getMessage());
477
        }
478
    }
479
480
    /**
481
     * Insert request into database
482
     *
483
     * @todo self::$pdo->lastInsertId() - unsafe usage if expected previous insert fails?
484
     *
485
     * @param Update $update
486
     *
487
     * @return bool
488
     * @throws TelegramException
489
     */
490
    public static function insertRequest(Update $update)
491
    {
492
        if (!self::isDbConnected()) {
493
            return false;
494
        }
495
496
        $update_id   = $update->getUpdateId();
497
        $update_type = $update->getUpdateType();
498
499
        // @todo Make this simpler: if ($message = $update->getMessage()) ...
500
        if ($update_type === 'message') {
501
            $message = $update->getMessage();
502
503 View Code Duplication
            if (self::insertMessageRequest($message)) {
0 ignored issues
show
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...
504
                $message_id = $message->getMessageId();
505
                $chat_id    = $message->getChat()->getId();
506
507
                return self::insertTelegramUpdate(
508
                    $update_id,
509
                    $chat_id,
510
                    $message_id
511
                );
512
            }
513 View Code Duplication
        } elseif ($update_type === 'edited_message') {
0 ignored issues
show
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...
514
            $edited_message = $update->getEditedMessage();
515
516
            if (self::insertEditedMessageRequest($edited_message)) {
517
                $edited_message_local_id = self::$pdo->lastInsertId();
518
                $chat_id                 = $edited_message->getChat()->getId();
519
520
                return self::insertTelegramUpdate(
521
                    $update_id,
522
                    $chat_id,
523
                    null,
524
                    null,
525
                    null,
526
                    null,
527
                    $edited_message_local_id
528
                );
529
            }
530
        } elseif ($update_type === 'channel_post') {
531
            $channel_post = $update->getChannelPost();
532
533 View Code Duplication
            if (self::insertMessageRequest($channel_post)) {
0 ignored issues
show
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...
534
                $message_id = $channel_post->getMessageId();
535
                $chat_id    = $channel_post->getChat()->getId();
536
537
                return self::insertTelegramUpdate(
538
                    $update_id,
539
                    $chat_id,
540
                    $message_id
541
                );
542
            }
543 View Code Duplication
        } elseif ($update_type === 'edited_channel_post') {
0 ignored issues
show
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...
544
            $edited_channel_post = $update->getEditedChannelPost();
545
546
            if (self::insertEditedMessageRequest($edited_channel_post)) {
547
                $edited_channel_post_local_id = self::$pdo->lastInsertId();
548
                $chat_id                      = $edited_channel_post->getChat()->getId();
549
550
                return self::insertTelegramUpdate(
551
                    $update_id,
552
                    $chat_id,
553
                    null,
554
                    null,
555
                    null,
556
                    null,
557
                    $edited_channel_post_local_id
558
                );
559
            }
560
        } elseif ($update_type === 'inline_query') {
561
            $inline_query = $update->getInlineQuery();
562
563
            if (self::insertInlineQueryRequest($inline_query)) {
564
                $inline_query_id = $inline_query->getId();
565
566
                return self::insertTelegramUpdate(
567
                    $update_id,
568
                    null,
569
                    null,
570
                    $inline_query_id
571
                );
572
            }
573
        } elseif ($update_type === 'chosen_inline_result') {
574
            $chosen_inline_result = $update->getChosenInlineResult();
575
576
            if (self::insertChosenInlineResultRequest($chosen_inline_result)) {
577
                $chosen_inline_result_local_id = self::$pdo->lastInsertId();
578
579
                return self::insertTelegramUpdate(
580
                    $update_id,
581
                    null,
582
                    null,
583
                    null,
584
                    $chosen_inline_result_local_id
585
                );
586
            }
587
        } elseif ($update_type === 'callback_query') {
588
            $callback_query = $update->getCallbackQuery();
589
590
            if (self::insertCallbackQueryRequest($callback_query)) {
591
                $callback_query_id = $callback_query->getId();
592
593
                return self::insertTelegramUpdate(
594
                    $update_id,
595
                    null,
596
                    null,
597
                    null,
598
                    null,
599
                    $callback_query_id
600
                );
601
            }
602
        }
603
604
        return false;
605
    }
606
607
    /**
608
     * Insert inline query request into database
609
     *
610
     * @param InlineQuery $inline_query
611
     *
612
     * @return bool If the insert was successful
613
     * @throws TelegramException
614
     */
615 View Code Duplication
    public static function insertInlineQueryRequest(InlineQuery $inline_query)
616
    {
617
        if (!self::isDbConnected()) {
618
            return false;
619
        }
620
621
        try {
622
            $sth = self::$pdo->prepare('
623
                INSERT IGNORE INTO `' . TB_INLINE_QUERY . '`
624
                (`id`, `user_id`, `location`, `query`, `offset`, `created_at`)
625
                VALUES
626
                (:id, :user_id, :location, :query, :offset, :created_at)
627
            ');
628
629
            $date    = self::getTimestamp();
630
            $user_id = null;
631
632
            $user = $inline_query->getFrom();
633
            if ($user instanceof User) {
634
                $user_id = $user->getId();
635
                self::insertUser($user, $date);
636
            }
637
638
            $sth->bindValue(':id', $inline_query->getId());
639
            $sth->bindValue(':user_id', $user_id);
640
            $sth->bindValue(':location', $inline_query->getLocation());
641
            $sth->bindValue(':query', $inline_query->getQuery());
642
            $sth->bindValue(':offset', $inline_query->getOffset());
643
            $sth->bindValue(':created_at', $date);
644
645
            return $sth->execute();
646
        } catch (PDOException $e) {
647
            throw new TelegramException($e->getMessage());
648
        }
649
    }
650
651
    /**
652
     * Insert chosen inline result request into database
653
     *
654
     * @param ChosenInlineResult $chosen_inline_result
655
     *
656
     * @return bool If the insert was successful
657
     * @throws TelegramException
658
     */
659 View Code Duplication
    public static function insertChosenInlineResultRequest(ChosenInlineResult $chosen_inline_result)
660
    {
661
        if (!self::isDbConnected()) {
662
            return false;
663
        }
664
665
        try {
666
            $sth = self::$pdo->prepare('
667
                INSERT INTO `' . TB_CHOSEN_INLINE_RESULT . '`
668
                (`result_id`, `user_id`, `location`, `inline_message_id`, `query`, `created_at`)
669
                VALUES
670
                (:result_id, :user_id, :location, :inline_message_id, :query, :created_at)
671
            ');
672
673
            $date    = self::getTimestamp();
674
            $user_id = null;
675
676
            $user = $chosen_inline_result->getFrom();
677
            if ($user instanceof User) {
678
                $user_id = $user->getId();
679
                self::insertUser($user, $date);
680
            }
681
682
            $sth->bindValue(':result_id', $chosen_inline_result->getResultId());
683
            $sth->bindValue(':user_id', $user_id);
684
            $sth->bindValue(':location', $chosen_inline_result->getLocation());
685
            $sth->bindValue(':inline_message_id', $chosen_inline_result->getInlineMessageId());
686
            $sth->bindValue(':query', $chosen_inline_result->getQuery());
687
            $sth->bindValue(':created_at', $date);
688
689
            return $sth->execute();
690
        } catch (PDOException $e) {
691
            throw new TelegramException($e->getMessage());
692
        }
693
    }
694
695
    /**
696
     * Insert callback query request into database
697
     *
698
     * @param CallbackQuery $callback_query
699
     *
700
     * @return bool If the insert was successful
701
     * @throws TelegramException
702
     */
703
    public static function insertCallbackQueryRequest(CallbackQuery $callback_query)
704
    {
705
        if (!self::isDbConnected()) {
706
            return false;
707
        }
708
709
        try {
710
            $sth = self::$pdo->prepare('
711
                INSERT IGNORE INTO `' . TB_CALLBACK_QUERY . '`
712
                (`id`, `user_id`, `chat_id`, `message_id`, `inline_message_id`, `data`, `created_at`)
713
                VALUES
714
                (:id, :user_id, :chat_id, :message_id, :inline_message_id, :data, :created_at)
715
            ');
716
717
            $date    = self::getTimestamp();
718
            $user_id = null;
719
720
            $user = $callback_query->getFrom();
721
            if ($user instanceof User) {
722
                $user_id = $user->getId();
723
                self::insertUser($user, $date);
724
            }
725
726
            $message    = $callback_query->getMessage();
727
            $chat_id    = null;
728
            $message_id = null;
729
            if ($message instanceof Message) {
730
                $chat_id    = $message->getChat()->getId();
731
                $message_id = $message->getMessageId();
732
733
                $is_message = self::$pdo->query('
734
                    SELECT *
735
                    FROM `' . TB_MESSAGE . '`
736
                    WHERE `id` = ' . $message_id . '
737
                      AND `chat_id` = ' . $chat_id . '
738
                    LIMIT 1
739
                ')->rowCount();
740
741
                if ($is_message) {
742
                    self::insertEditedMessageRequest($message);
743
                } else {
744
                    self::insertMessageRequest($message);
745
                }
746
            }
747
748
            $sth->bindValue(':id', $callback_query->getId());
749
            $sth->bindValue(':user_id', $user_id);
750
            $sth->bindValue(':chat_id', $chat_id);
751
            $sth->bindValue(':message_id', $message_id);
752
            $sth->bindValue(':inline_message_id', $callback_query->getInlineMessageId());
753
            $sth->bindValue(':data', $callback_query->getData());
754
            $sth->bindValue(':created_at', $date);
755
756
            return $sth->execute();
757
        } catch (PDOException $e) {
758
            throw new TelegramException($e->getMessage());
759
        }
760
    }
761
762
    /**
763
     * Insert Message request in db
764
     *
765
     * @todo Complete with new fields: https://core.telegram.org/bots/api#message
766
     *
767
     * @param Message $message
768
     *
769
     * @return bool If the insert was successful
770
     * @throws TelegramException
771
     */
772 6
    public static function insertMessageRequest(Message $message)
773
    {
774 6
        if (!self::isDbConnected()) {
775
            return false;
776
        }
777
778 6
        $date = self::getTimestamp($message->getDate());
779
780
        // Insert chat, update chat id in case it migrated
781 6
        $chat = $message->getChat();
782 6
        self::insertChat($chat, $date, $message->getMigrateToChatId());
783
784
        // Insert user and the relation with the chat
785 6
        $user = $message->getFrom();
786 6
        if ($user instanceof User) {
787 6
            self::insertUser($user, $date, $chat);
788
        }
789
790
        // Insert the forwarded message user in users table
791 6
        $forward_date = null;
792 6
        $forward_from = $message->getForwardFrom();
793 6
        if ($forward_from instanceof User) {
794
            self::insertUser($forward_from, $forward_date);
795
            $forward_from = $forward_from->getId();
796
            $forward_date = self::getTimestamp($message->getForwardDate());
797
        }
798 6
        $forward_from_chat = $message->getForwardFromChat();
799 6
        if ($forward_from_chat instanceof Chat) {
800
            self::insertChat($forward_from_chat, $forward_date);
801
            $forward_from_chat = $forward_from_chat->getId();
802
            $forward_date      = self::getTimestamp($message->getForwardDate());
803
        }
804
805
        // New and left chat member
806 6
        $new_chat_members_ids = null;
807 6
        $left_chat_member_id  = null;
808
809 6
        $new_chat_members = $message->getNewChatMembers();
810 6
        $left_chat_member = $message->getLeftChatMember();
811 6
        if (!empty($new_chat_members)) {
812
            foreach ($new_chat_members as $new_chat_member) {
813
                if ($new_chat_member instanceof User) {
814
                    // Insert the new chat user
815
                    self::insertUser($new_chat_member, $date, $chat);
816
                    $new_chat_members_ids[] = $new_chat_member->getId();
817
                }
818
            }
819
            $new_chat_members_ids = implode(',', $new_chat_members_ids);
820 6
        } elseif ($left_chat_member instanceof User) {
821
            // Insert the left chat user
822
            self::insertUser($left_chat_member, $date, $chat);
823
            $left_chat_member_id = $left_chat_member->getId();
824
        }
825
826
        try {
827 6
            $sth = self::$pdo->prepare('
828 6
                INSERT IGNORE INTO `' . TB_MESSAGE . '`
829
                (
830
                    `id`, `user_id`, `chat_id`, `date`, `forward_from`, `forward_from_chat`, `forward_from_message_id`,
831
                    `forward_date`, `reply_to_chat`, `reply_to_message`, `media_group_id`, `text`, `entities`, `audio`, `document`,
832
                    `animation`, `game`, `photo`, `sticker`, `video`, `voice`, `video_note`, `caption`, `contact`,
833
                    `location`, `venue`, `new_chat_members`, `left_chat_member`,
834
                    `new_chat_title`,`new_chat_photo`, `delete_chat_photo`, `group_chat_created`,
835
                    `supergroup_chat_created`, `channel_chat_created`,
836
                    `migrate_from_chat_id`, `migrate_to_chat_id`, `pinned_message`, `connected_website`, `passport_data`
837
                ) VALUES (
838
                    :message_id, :user_id, :chat_id, :date, :forward_from, :forward_from_chat, :forward_from_message_id,
839
                    :forward_date, :reply_to_chat, :reply_to_message, :media_group_id, :text, :entities, :audio, :document,
840
                    :animation, :game, :photo, :sticker, :video, :voice, :video_note, :caption, :contact,
841
                    :location, :venue, :new_chat_members, :left_chat_member,
842
                    :new_chat_title, :new_chat_photo, :delete_chat_photo, :group_chat_created,
843
                    :supergroup_chat_created, :channel_chat_created,
844
                    :migrate_from_chat_id, :migrate_to_chat_id, :pinned_message, :connected_website, :passport_data
845
                )
846
            ');
847
848 6
            $user_id = null;
849 6
            if ($user instanceof User) {
850 6
                $user_id = $user->getId();
851
            }
852 6
            $chat_id = $chat->getId();
853
854 6
            $reply_to_message    = $message->getReplyToMessage();
855 6
            $reply_to_message_id = null;
856 6
            if ($reply_to_message instanceof ReplyToMessage) {
857
                $reply_to_message_id = $reply_to_message->getMessageId();
858
                // please notice that, as explained in the documentation, reply_to_message don't contain other
859
                // reply_to_message field so recursion deep is 1
860
                self::insertMessageRequest($reply_to_message);
861
            }
862
863 6
            $sth->bindValue(':message_id', $message->getMessageId());
864 6
            $sth->bindValue(':chat_id', $chat_id);
865 6
            $sth->bindValue(':user_id', $user_id);
866 6
            $sth->bindValue(':date', $date);
867 6
            $sth->bindValue(':forward_from', $forward_from);
868 6
            $sth->bindValue(':forward_from_chat', $forward_from_chat);
869 6
            $sth->bindValue(':forward_from_message_id', $message->getForwardFromMessageId());
870 6
            $sth->bindValue(':forward_date', $forward_date);
871
872 6
            $reply_to_chat_id = null;
873 6
            if ($reply_to_message_id !== null) {
874
                $reply_to_chat_id = $chat_id;
875
            }
876 6
            $sth->bindValue(':reply_to_chat', $reply_to_chat_id);
877 6
            $sth->bindValue(':reply_to_message', $reply_to_message_id);
878
879 6
            $sth->bindValue(':media_group_id', $message->getMediaGroupId());
880 6
            $sth->bindValue(':text', $message->getText());
881 6
            $sth->bindValue(':entities', $t = self::entitiesArrayToJson($message->getEntities(), null));
882 6
            $sth->bindValue(':audio', $message->getAudio());
883 6
            $sth->bindValue(':document', $message->getDocument());
884 6
            $sth->bindValue(':animation', $message->getAnimation());
885 6
            $sth->bindValue(':game', $message->getGame());
886 6
            $sth->bindValue(':photo', $t = self::entitiesArrayToJson($message->getPhoto(), null));
887 6
            $sth->bindValue(':sticker', $message->getSticker());
888 6
            $sth->bindValue(':video', $message->getVideo());
889 6
            $sth->bindValue(':voice', $message->getVoice());
890 6
            $sth->bindValue(':video_note', $message->getVideoNote());
891 6
            $sth->bindValue(':caption', $message->getCaption());
892 6
            $sth->bindValue(':contact', $message->getContact());
893 6
            $sth->bindValue(':location', $message->getLocation());
894 6
            $sth->bindValue(':venue', $message->getVenue());
895 6
            $sth->bindValue(':new_chat_members', $new_chat_members_ids);
896 6
            $sth->bindValue(':left_chat_member', $left_chat_member_id);
897 6
            $sth->bindValue(':new_chat_title', $message->getNewChatTitle());
898 6
            $sth->bindValue(':new_chat_photo', $t = self::entitiesArrayToJson($message->getNewChatPhoto(), null));
899 6
            $sth->bindValue(':delete_chat_photo', $message->getDeleteChatPhoto());
900 6
            $sth->bindValue(':group_chat_created', $message->getGroupChatCreated());
901 6
            $sth->bindValue(':supergroup_chat_created', $message->getSupergroupChatCreated());
902 6
            $sth->bindValue(':channel_chat_created', $message->getChannelChatCreated());
903 6
            $sth->bindValue(':migrate_from_chat_id', $message->getMigrateFromChatId());
904 6
            $sth->bindValue(':migrate_to_chat_id', $message->getMigrateToChatId());
905 6
            $sth->bindValue(':pinned_message', $message->getPinnedMessage());
906 6
            $sth->bindValue(':connected_website', $message->getConnectedWebsite());
907 6
            $sth->bindValue(':passport_data', $message->getPassportData());
908
909 6
            return $sth->execute();
910
        } catch (PDOException $e) {
911
            throw new TelegramException($e->getMessage());
912
        }
913
    }
914
915
    /**
916
     * Insert Edited Message request in db
917
     *
918
     * @param Message $edited_message
919
     *
920
     * @return bool If the insert was successful
921
     * @throws TelegramException
922
     */
923
    public static function insertEditedMessageRequest(Message $edited_message)
924
    {
925
        if (!self::isDbConnected()) {
926
            return false;
927
        }
928
929
        try {
930
            $edit_date = self::getTimestamp($edited_message->getEditDate());
931
932
            // Insert chat
933
            $chat = $edited_message->getChat();
934
            self::insertChat($chat, $edit_date);
935
936
            // Insert user and the relation with the chat
937
            $user = $edited_message->getFrom();
938
            if ($user instanceof User) {
939
                self::insertUser($user, $edit_date, $chat);
940
            }
941
942
            $sth = self::$pdo->prepare('
943
                INSERT IGNORE INTO `' . TB_EDITED_MESSAGE . '`
944
                (`chat_id`, `message_id`, `user_id`, `edit_date`, `text`, `entities`, `caption`)
945
                VALUES
946
                (:chat_id, :message_id, :user_id, :edit_date, :text, :entities, :caption)
947
            ');
948
949
            $user_id = null;
950
            if ($user instanceof User) {
951
                $user_id = $user->getId();
952
            }
953
954
            $sth->bindValue(':chat_id', $chat->getId());
955
            $sth->bindValue(':message_id', $edited_message->getMessageId());
956
            $sth->bindValue(':user_id', $user_id);
957
            $sth->bindValue(':edit_date', $edit_date);
958
            $sth->bindValue(':text', $edited_message->getText());
959
            $sth->bindValue(':entities', self::entitiesArrayToJson($edited_message->getEntities(), null));
960
            $sth->bindValue(':caption', $edited_message->getCaption());
961
962
            return $sth->execute();
963
        } catch (PDOException $e) {
964
            throw new TelegramException($e->getMessage());
965
        }
966
    }
967
968
    /**
969
     * Select Groups, Supergroups, Channels and/or single user Chats (also by ID or text)
970
     *
971
     * @param $select_chats_params
972
     *
973
     * @return array|bool
974
     * @throws TelegramException
975
     */
976
    public static function selectChats($select_chats_params)
977
    {
978
        if (!self::isDbConnected()) {
979
            return false;
980
        }
981
982
        // Set defaults for omitted values.
983
        $select = array_merge([
984
            'groups'      => true,
985
            'supergroups' => true,
986
            'channels'    => true,
987
            'users'       => true,
988
            'date_from'   => null,
989
            'date_to'     => null,
990
            'chat_id'     => null,
991
            'text'        => null,
992
        ], $select_chats_params);
993
994
        if (!$select['groups'] && !$select['users'] && !$select['supergroups'] && !$select['channels']) {
995
            return false;
996
        }
997
998
        try {
999
            $query = '
1000
                SELECT * ,
1001
                ' . TB_CHAT . '.`id` AS `chat_id`,
1002
                ' . TB_CHAT . '.`username` AS `chat_username`,
1003
                ' . TB_CHAT . '.`created_at` AS `chat_created_at`,
1004
                ' . TB_CHAT . '.`updated_at` AS `chat_updated_at`
1005
            ';
1006
            if ($select['users']) {
1007
                $query .= '
1008
                    , ' . TB_USER . '.`id` AS `user_id`
1009
                    FROM `' . TB_CHAT . '`
1010
                    LEFT JOIN `' . TB_USER . '`
1011
                    ON ' . TB_CHAT . '.`id`=' . TB_USER . '.`id`
1012
                ';
1013
            } else {
1014
                $query .= 'FROM `' . TB_CHAT . '`';
1015
            }
1016
1017
            // Building parts of query
1018
            $where  = [];
1019
            $tokens = [];
1020
1021
            if (!$select['groups'] || !$select['users'] || !$select['supergroups'] || !$select['channels']) {
1022
                $chat_or_user = [];
1023
1024
                $select['groups'] && $chat_or_user[] = TB_CHAT . '.`type` = "group"';
1025
                $select['supergroups'] && $chat_or_user[] = TB_CHAT . '.`type` = "supergroup"';
1026
                $select['channels'] && $chat_or_user[] = TB_CHAT . '.`type` = "channel"';
1027
                $select['users'] && $chat_or_user[] = TB_CHAT . '.`type` = "private"';
1028
1029
                $where[] = '(' . implode(' OR ', $chat_or_user) . ')';
1030
            }
1031
1032
            if (null !== $select['date_from']) {
1033
                $where[]              = TB_CHAT . '.`updated_at` >= :date_from';
1034
                $tokens[':date_from'] = $select['date_from'];
1035
            }
1036
1037
            if (null !== $select['date_to']) {
1038
                $where[]            = TB_CHAT . '.`updated_at` <= :date_to';
1039
                $tokens[':date_to'] = $select['date_to'];
1040
            }
1041
1042
            if (null !== $select['chat_id']) {
1043
                $where[]            = TB_CHAT . '.`id` = :chat_id';
1044
                $tokens[':chat_id'] = $select['chat_id'];
1045
            }
1046
1047
            if (null !== $select['text']) {
1048
                $text_like = '%' . strtolower($select['text']) . '%';
1049
                if ($select['users']) {
1050
                    $where[]          = '(
1051
                        LOWER(' . TB_CHAT . '.`title`) LIKE :text1
1052
                        OR LOWER(' . TB_USER . '.`first_name`) LIKE :text2
1053
                        OR LOWER(' . TB_USER . '.`last_name`) LIKE :text3
1054
                        OR LOWER(' . TB_USER . '.`username`) LIKE :text4
1055
                    )';
1056
                    $tokens[':text1'] = $text_like;
1057
                    $tokens[':text2'] = $text_like;
1058
                    $tokens[':text3'] = $text_like;
1059
                    $tokens[':text4'] = $text_like;
1060
                } else {
1061
                    $where[]         = 'LOWER(' . TB_CHAT . '.`title`) LIKE :text';
1062
                    $tokens[':text'] = $text_like;
1063
                }
1064
            }
1065
1066
            if (!empty($where)) {
1067
                $query .= ' WHERE ' . implode(' AND ', $where);
1068
            }
1069
1070
            $query .= ' ORDER BY ' . TB_CHAT . '.`updated_at` ASC';
1071
1072
            $sth = self::$pdo->prepare($query);
1073
            $sth->execute($tokens);
1074
1075
            return $sth->fetchAll(PDO::FETCH_ASSOC);
1076
        } catch (PDOException $e) {
1077
            throw new TelegramException($e->getMessage());
1078
        }
1079
    }
1080
1081
    /**
1082
     * Get Telegram API request count for current chat / message
1083
     *
1084
     * @param integer $chat_id
1085
     * @param string  $inline_message_id
1086
     *
1087
     * @return array|bool Array containing TOTAL and CURRENT fields or false on invalid arguments
1088
     * @throws TelegramException
1089
     */
1090
    public static function getTelegramRequestCount($chat_id = null, $inline_message_id = null)
1091
    {
1092
        if (!self::isDbConnected()) {
1093
            return false;
1094
        }
1095
1096
        try {
1097
            $sth = self::$pdo->prepare('SELECT 
1098
                (SELECT COUNT(DISTINCT `chat_id`) FROM `' . TB_REQUEST_LIMITER . '` WHERE `created_at` >= :created_at_1) AS LIMIT_PER_SEC_ALL,
1099
                (SELECT COUNT(*) FROM `' . TB_REQUEST_LIMITER . '` WHERE `created_at` >= :created_at_2 AND ((`chat_id` = :chat_id_1 AND `inline_message_id` IS NULL) OR (`inline_message_id` = :inline_message_id AND `chat_id` IS NULL))) AS LIMIT_PER_SEC,
1100
                (SELECT COUNT(*) FROM `' . TB_REQUEST_LIMITER . '` WHERE `created_at` >= :created_at_minute AND `chat_id` = :chat_id_2) AS LIMIT_PER_MINUTE
1101
            ');
1102
1103
            $date        = self::getTimestamp();
1104
            $date_minute = self::getTimestamp(strtotime('-1 minute'));
1105
1106
            $sth->bindValue(':chat_id_1', $chat_id);
1107
            $sth->bindValue(':chat_id_2', $chat_id);
1108
            $sth->bindValue(':inline_message_id', $inline_message_id);
1109
            $sth->bindValue(':created_at_1', $date);
1110
            $sth->bindValue(':created_at_2', $date);
1111
            $sth->bindValue(':created_at_minute', $date_minute);
1112
1113
            $sth->execute();
1114
1115
            return $sth->fetch();
1116
        } catch (Exception $e) {
1117
            throw new TelegramException($e->getMessage());
1118
        }
1119
    }
1120
1121
    /**
1122
     * Insert Telegram API request in db
1123
     *
1124
     * @param string $method
1125
     * @param array  $data
1126
     *
1127
     * @return bool If the insert was successful
1128
     * @throws TelegramException
1129
     */
1130
    public static function insertTelegramRequest($method, $data)
1131
    {
1132
        if (!self::isDbConnected()) {
1133
            return false;
1134
        }
1135
1136
        try {
1137
            $sth = self::$pdo->prepare('INSERT INTO `' . TB_REQUEST_LIMITER . '`
1138
                (`method`, `chat_id`, `inline_message_id`, `created_at`)
1139
                VALUES
1140
                (:method, :chat_id, :inline_message_id, :created_at);
1141
            ');
1142
1143
            $chat_id           = isset($data['chat_id']) ? $data['chat_id'] : null;
1144
            $inline_message_id = isset($data['inline_message_id']) ? $data['inline_message_id'] : null;
1145
1146
            $sth->bindValue(':chat_id', $chat_id);
1147
            $sth->bindValue(':inline_message_id', $inline_message_id);
1148
            $sth->bindValue(':method', $method);
1149
            $sth->bindValue(':created_at', self::getTimestamp());
1150
1151
            return $sth->execute();
1152
        } catch (Exception $e) {
1153
            throw new TelegramException($e->getMessage());
1154
        }
1155
    }
1156
1157
    /**
1158
     * Bulk update the entries of any table
1159
     *
1160
     * @param string $table
1161
     * @param array  $fields_values
1162
     * @param array  $where_fields_values
1163
     *
1164
     * @return bool
1165
     * @throws TelegramException
1166
     */
1167 3
    public static function update($table, array $fields_values, array $where_fields_values)
1168
    {
1169 3
        if (empty($fields_values) || !self::isDbConnected()) {
1170
            return false;
1171
        }
1172
1173
        try {
1174
            // Building parts of query
1175 3
            $tokens = $fields = $where = [];
1176
1177
            // Fields with values to update
1178 3 View Code Duplication
            foreach ($fields_values as $field => $value) {
1179 3
                $token          = ':' . count($tokens);
1180 3
                $fields[]       = "`{$field}` = {$token}";
1181 3
                $tokens[$token] = $value;
1182
            }
1183
1184
            // Where conditions
1185 3 View Code Duplication
            foreach ($where_fields_values as $field => $value) {
1186 3
                $token          = ':' . count($tokens);
1187 3
                $where[]        = "`{$field}` = {$token}";
1188 3
                $tokens[$token] = $value;
1189
            }
1190
1191 3
            $sql = 'UPDATE `' . $table . '` SET ' . implode(', ', $fields);
1192 3
            $sql .= count($where) > 0 ? ' WHERE ' . implode(' AND ', $where) : '';
1193
1194 3
            return self::$pdo->prepare($sql)->execute($tokens);
1195
        } catch (Exception $e) {
1196
            throw new TelegramException($e->getMessage());
1197
        }
1198
    }
1199
}
1200