Completed
Push — master ( 3e7af9...f7cac5 )
by Armando
90:45 queued 54:10
created

DB::insertChat()   B

Complexity

Conditions 5
Paths 34

Size

Total Lines 46
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5.4439

Importance

Changes 0
Metric Value
dl 0
loc 46
ccs 17
cts 23
cp 0.7391
rs 8.4751
c 0
b 0
f 0
cc 5
eloc 25
nc 34
nop 3
crap 5.4439
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
    static protected $mysql_credentials = [];
35
36
    /**
37
     * PDO object
38
     *
39
     * @var PDO
40
     */
41
    static protected $pdo;
42
43
    /**
44
     * Table prefix
45
     *
46
     * @var string
47
     */
48
    static protected $table_prefix;
49
50
    /**
51
     * Telegram class object
52
     *
53
     * @var Telegram
54
     */
55
    static protected $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
        $options = [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES ' . $encoding];
80
        try {
81 9
            $pdo = new PDO($dsn, $credentials['user'], $credentials['password'], $options);
82 9
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
83
        } catch (PDOException $e) {
84
            throw new TelegramException($e->getMessage());
85
        }
86
87 9
        self::$pdo               = $pdo;
88 9
        self::$telegram          = $telegram;
89 9
        self::$mysql_credentials = $credentials;
90 9
        self::$table_prefix      = $table_prefix;
91
92 9
        self::defineTables();
93
94 9
        return self::$pdo;
95
    }
96
97
    /**
98
     * External Initialize
99
     *
100
     * Let you use the class with an external already existing Pdo Mysql connection.
101
     *
102
     * @param PDO      $external_pdo_connection PDO database object
103
     * @param Telegram $telegram                Telegram object to connect with this object
104
     * @param string   $table_prefix            Table prefix
105
     *
106
     * @return PDO PDO database object
107
     * @throws TelegramException
108
     */
109
    public static function externalInitialize(
110
        $external_pdo_connection,
111
        Telegram $telegram,
112
        $table_prefix = null
113
    ) {
114
        if ($external_pdo_connection === null) {
115
            throw new TelegramException('MySQL external connection not provided!');
116
        }
117
118
        self::$pdo               = $external_pdo_connection;
119
        self::$telegram          = $telegram;
120
        self::$mysql_credentials = [];
121
        self::$table_prefix      = $table_prefix;
122
123
        self::defineTables();
124
125
        return self::$pdo;
126
    }
127
128
    /**
129
     * Define all the tables with the proper prefix
130
     */
131 9
    protected static function defineTables()
132
    {
133
        $tables = [
134 9
            'callback_query',
135
            'chat',
136
            'chosen_inline_result',
137
            'edited_message',
138
            'inline_query',
139
            'message',
140
            'request_limiter',
141
            'telegram_update',
142
            'user',
143
            'user_chat',
144
        ];
145 9
        foreach ($tables as $table) {
146 9
            $table_name = 'TB_' . strtoupper($table);
147 9
            if (!defined($table_name)) {
148 9
                define($table_name, self::$table_prefix . $table);
149
            }
150
        }
151 9
    }
152
153
    /**
154
     * Check if database connection has been created
155
     *
156
     * @return bool
157
     */
158 9
    public static function isDbConnected()
159
    {
160 9
        return self::$pdo !== null;
161
    }
162
163
    /**
164
     * Get the PDO object of the connected database
165
     *
166
     * @return PDO
167
     */
168
    public static function getPdo()
169
    {
170
        return self::$pdo;
171
    }
172
173
    /**
174
     * Fetch update(s) from DB
175
     *
176
     * @param int    $limit Limit the number of updates to fetch
177
     * @param string $id    Check for unique update id
178
     *
179
     * @return array|bool Fetched data or false if not connected
180
     * @throws TelegramException
181
     */
182
    public static function selectTelegramUpdate($limit = null, $id = null)
183
    {
184
        if (!self::isDbConnected()) {
185
            return false;
186
        }
187
188
        try {
189
            $sql = '
190
                SELECT `id`
191
                FROM `' . TB_TELEGRAM_UPDATE . '`
192
            ';
193
194
            if ($id !== null) {
195
                $sql .= ' WHERE `id` = :id';
196
            } else {
197
                $sql .= ' ORDER BY `id` DESC';
198
            }
199
200
            if ($limit !== null) {
201
                $sql .= ' LIMIT :limit';
202
            }
203
204
            $sth = self::$pdo->prepare($sql);
205
206
            if ($limit !== null) {
207
                $sth->bindValue(':limit', $limit, PDO::PARAM_INT);
208
            }
209
            if ($id !== null) {
210
                $sth->bindValue(':id', $id);
211
            }
212
213
            $sth->execute();
214
215
            return $sth->fetchAll(PDO::FETCH_ASSOC);
216
        } catch (PDOException $e) {
217
            throw new TelegramException($e->getMessage());
218
        }
219
    }
220
221
    /**
222
     * Fetch message(s) from DB
223
     *
224
     * @param int $limit Limit the number of messages to fetch
225
     *
226
     * @return array|bool Fetched data or false if not connected
227
     * @throws TelegramException
228
     */
229
    public static function selectMessages($limit = null)
230
    {
231
        if (!self::isDbConnected()) {
232
            return false;
233
        }
234
235
        try {
236
            $sql = '
237
                SELECT *
238
                FROM `' . TB_MESSAGE . '`
239
                ORDER BY `id` DESC
240
            ';
241
242
            if ($limit !== null) {
243
                $sql .= ' LIMIT :limit';
244
            }
245
246
            $sth = self::$pdo->prepare($sql);
247
248
            if ($limit !== null) {
249
                $sth->bindValue(':limit', $limit, PDO::PARAM_INT);
250
            }
251
252
            $sth->execute();
253
254
            return $sth->fetchAll(PDO::FETCH_ASSOC);
255
        } catch (PDOException $e) {
256
            throw new TelegramException($e->getMessage());
257
        }
258
    }
259
260
    /**
261
     * Convert from unix timestamp to timestamp
262
     *
263
     * @param int $time Unix timestamp (if empty, current timestamp is used)
264
     *
265
     * @return string
266
     */
267 7
    protected static function getTimestamp($time = null)
268
    {
269 7
        return date('Y-m-d H:i:s', $time ?: time());
270
    }
271
272
    /**
273
     * Convert array of Entity items to a JSON array
274
     *
275
     * @todo Find a better way, as json_* functions are very heavy
276
     *
277
     * @param array|null $entities
278
     * @param mixed      $default
279
     *
280
     * @return mixed
281
     */
282 6
    public static function entitiesArrayToJson($entities, $default = null)
283
    {
284 6
        if (!is_array($entities)) {
285 6
            return $default;
286
        }
287
288
        // Convert each Entity item into an object based on its JSON reflection
289
        $json_entities = array_map(function ($entity) {
290
            return json_decode($entity, true);
291
        }, $entities);
292
293
        return json_encode($json_entities);
294
    }
295
296
    /**
297
     * Insert entry to telegram_update table
298
     *
299
     * @todo Add missing values! See https://core.telegram.org/bots/api#update
300
     *
301
     * @param string $id
302
     * @param string $chat_id
303
     * @param string $message_id
304
     * @param string $inline_query_id
305
     * @param string $chosen_inline_result_id
306
     * @param string $callback_query_id
307
     * @param string $edited_message_id
308
     *
309
     * @return bool If the insert was successful
310
     * @throws TelegramException
311
     */
312
    public static function insertTelegramUpdate(
313
        $id,
314
        $chat_id = null,
315
        $message_id = null,
316
        $inline_query_id = null,
317
        $chosen_inline_result_id = null,
318
        $callback_query_id = null,
319
        $edited_message_id = null
320
    ) {
321
        if ($message_id === null && $inline_query_id === null && $chosen_inline_result_id === null && $callback_query_id === null && $edited_message_id === null) {
322
            throw new TelegramException('message_id, inline_query_id, chosen_inline_result_id, callback_query_id, edited_message_id are all null');
323
        }
324
325
        if (!self::isDbConnected()) {
326
            return false;
327
        }
328
329
        try {
330
            $sth = self::$pdo->prepare('
331
                INSERT IGNORE INTO `' . TB_TELEGRAM_UPDATE . '`
332
                (`id`, `chat_id`, `message_id`, `inline_query_id`, `chosen_inline_result_id`, `callback_query_id`, `edited_message_id`)
333
                VALUES
334
                (:id, :chat_id, :message_id, :inline_query_id, :chosen_inline_result_id, :callback_query_id, :edited_message_id)
335
            ');
336
337
            $sth->bindValue(':id', $id);
338
            $sth->bindValue(':chat_id', $chat_id);
339
            $sth->bindValue(':message_id', $message_id);
340
            $sth->bindValue(':edited_message_id', $edited_message_id);
341
            $sth->bindValue(':inline_query_id', $inline_query_id);
342
            $sth->bindValue(':chosen_inline_result_id', $chosen_inline_result_id);
343
            $sth->bindValue(':callback_query_id', $callback_query_id);
344
345
            return $sth->execute();
346
        } catch (PDOException $e) {
347
            throw new TelegramException($e->getMessage());
348
        }
349
    }
350
351
    /**
352
     * Insert users and save their connection to chats
353
     *
354
     * @param User   $user
355
     * @param string $date
356
     * @param Chat   $chat
357
     *
358
     * @return bool If the insert was successful
359
     * @throws TelegramException
360
     */
361 6
    public static function insertUser(User $user, $date = null, Chat $chat = null)
362
    {
363 6
        if (!self::isDbConnected()) {
364
            return false;
365
        }
366
367
        try {
368 6
            $sth = self::$pdo->prepare('
369 6
                INSERT INTO `' . TB_USER . '`
370
                (`id`, `is_bot`, `username`, `first_name`, `last_name`, `language_code`, `created_at`, `updated_at`)
371
                VALUES
372
                (:id, :is_bot, :username, :first_name, :last_name, :language_code, :created_at, :updated_at)
373
                ON DUPLICATE KEY UPDATE
374
                    `is_bot`         = VALUES(`is_bot`),
375
                    `username`       = VALUES(`username`),
376
                    `first_name`     = VALUES(`first_name`),
377
                    `last_name`      = VALUES(`last_name`),
378
                    `language_code`  = VALUES(`language_code`),
379
                    `updated_at`     = VALUES(`updated_at`)
380
            ');
381
382 6
            $sth->bindValue(':id', $user->getId());
383 6
            $sth->bindValue(':is_bot', $user->getIsBot(), PDO::PARAM_INT);
384 6
            $sth->bindValue(':username', $user->getUsername());
385 6
            $sth->bindValue(':first_name', $user->getFirstName());
386 6
            $sth->bindValue(':last_name', $user->getLastName());
387 6
            $sth->bindValue(':language_code', $user->getLanguageCode());
388 6
            $date = $date ?: self::getTimestamp();
389 6
            $sth->bindValue(':created_at', $date);
390 6
            $sth->bindValue(':updated_at', $date);
391
392 6
            $status = $sth->execute();
393
        } catch (PDOException $e) {
394
            throw new TelegramException($e->getMessage());
395
        }
396
397
        // Also insert the relationship to the chat into the user_chat table
398 6
        if ($chat instanceof Chat) {
399
            try {
400 6
                $sth = self::$pdo->prepare('
401 6
                    INSERT IGNORE INTO `' . TB_USER_CHAT . '`
402
                    (`user_id`, `chat_id`)
403
                    VALUES
404
                    (:user_id, :chat_id)
405
                ');
406
407 6
                $sth->bindValue(':user_id', $user->getId());
408 6
                $sth->bindValue(':chat_id', $chat->getId());
409
410 6
                $status = $sth->execute();
411
            } catch (PDOException $e) {
412
                throw new TelegramException($e->getMessage());
413
            }
414
        }
415
416 6
        return $status;
417
    }
418
419
    /**
420
     * Insert chat
421
     *
422
     * @param Chat   $chat
423
     * @param string $date
424
     * @param string $migrate_to_chat_id
425
     *
426
     * @return bool If the insert was successful
427
     * @throws TelegramException
428
     */
429 6
    public static function insertChat(Chat $chat, $date = null, $migrate_to_chat_id = null)
430
    {
431 6
        if (!self::isDbConnected()) {
432
            return false;
433
        }
434
435
        try {
436 6
            $sth = self::$pdo->prepare('
437 6
                INSERT IGNORE INTO `' . TB_CHAT . '`
438
                (`id`, `type`, `title`, `username`, `all_members_are_administrators`, `created_at` ,`updated_at`, `old_id`)
439
                VALUES
440
                (:id, :type, :title, :username, :all_members_are_administrators, :created_at, :updated_at, :old_id)
441
                ON DUPLICATE KEY UPDATE
442
                    `type`                           = VALUES(`type`),
443
                    `title`                          = VALUES(`title`),
444
                    `username`                       = VALUES(`username`),
445
                    `all_members_are_administrators` = VALUES(`all_members_are_administrators`),
446
                    `updated_at`                     = VALUES(`updated_at`)
447
            ');
448
449 6
            $chat_id   = $chat->getId();
450 6
            $chat_type = $chat->getType();
451
452 6
            if ($migrate_to_chat_id !== null) {
453
                $chat_type = 'supergroup';
454
455
                $sth->bindValue(':id', $migrate_to_chat_id);
456
                $sth->bindValue(':old_id', $chat_id);
457
            } else {
458 6
                $sth->bindValue(':id', $chat_id);
459 6
                $sth->bindValue(':old_id', $migrate_to_chat_id);
460
            }
461
462 6
            $sth->bindValue(':type', $chat_type);
463 6
            $sth->bindValue(':title', $chat->getTitle());
464 6
            $sth->bindValue(':username', $chat->getUsername());
465 6
            $sth->bindValue(':all_members_are_administrators', $chat->getAllMembersAreAdministrators(), PDO::PARAM_INT);
466 6
            $date = $date ?: self::getTimestamp();
467 6
            $sth->bindValue(':created_at', $date);
468 6
            $sth->bindValue(':updated_at', $date);
469
470 6
            return $sth->execute();
471
        } catch (PDOException $e) {
472
            throw new TelegramException($e->getMessage());
473
        }
474
    }
475
476
    /**
477
     * Insert request into database
478
     *
479
     * @todo self::$pdo->lastInsertId() - unsafe usage if expected previous insert fails?
480
     *
481
     * @param Update $update
482
     *
483
     * @return bool
484
     * @throws TelegramException
485
     */
486
    public static function insertRequest(Update $update)
487
    {
488
        if (!self::isDbConnected()) {
489
            return false;
490
        }
491
492
        $update_id   = $update->getUpdateId();
493
        $update_type = $update->getUpdateType();
494
495
        if (count(self::selectTelegramUpdate(1, $update_id)) === 1) {
496
            throw new TelegramException('Duplicate update received!');
497
        }
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
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...
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
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...
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
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...
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
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...
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)
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...
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)
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...
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
                    `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`
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
                    :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
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(':photo', $t = self::entitiesArrayToJson($message->getPhoto(), null));
885 6
            $sth->bindValue(':sticker', $message->getSticker());
886 6
            $sth->bindValue(':video', $message->getVideo());
887 6
            $sth->bindValue(':voice', $message->getVoice());
888 6
            $sth->bindValue(':video_note', $message->getVideoNote());
889 6
            $sth->bindValue(':caption', $message->getCaption());
890 6
            $sth->bindValue(':contact', $message->getContact());
891 6
            $sth->bindValue(':location', $message->getLocation());
892 6
            $sth->bindValue(':venue', $message->getVenue());
893 6
            $sth->bindValue(':new_chat_members', $new_chat_members_ids);
894 6
            $sth->bindValue(':left_chat_member', $left_chat_member_id);
895 6
            $sth->bindValue(':new_chat_title', $message->getNewChatTitle());
896 6
            $sth->bindValue(':new_chat_photo', $t = self::entitiesArrayToJson($message->getNewChatPhoto(), null));
897 6
            $sth->bindValue(':delete_chat_photo', $message->getDeleteChatPhoto());
898 6
            $sth->bindValue(':group_chat_created', $message->getGroupChatCreated());
899 6
            $sth->bindValue(':supergroup_chat_created', $message->getSupergroupChatCreated());
900 6
            $sth->bindValue(':channel_chat_created', $message->getChannelChatCreated());
901 6
            $sth->bindValue(':migrate_from_chat_id', $message->getMigrateFromChatId());
902 6
            $sth->bindValue(':migrate_to_chat_id', $message->getMigrateToChatId());
903 6
            $sth->bindValue(':pinned_message', $message->getPinnedMessage());
904
905 6
            return $sth->execute();
906
        } catch (PDOException $e) {
907
            throw new TelegramException($e->getMessage());
908
        }
909
    }
910
911
    /**
912
     * Insert Edited Message request in db
913
     *
914
     * @param Message $edited_message
915
     *
916
     * @return bool If the insert was successful
917
     * @throws TelegramException
918
     */
919
    public static function insertEditedMessageRequest(Message $edited_message)
920
    {
921
        if (!self::isDbConnected()) {
922
            return false;
923
        }
924
925
        try {
926
            $edit_date = self::getTimestamp($edited_message->getEditDate());
927
928
            // Insert chat
929
            $chat = $edited_message->getChat();
930
            self::insertChat($chat, $edit_date);
931
932
            // Insert user and the relation with the chat
933
            $user = $edited_message->getFrom();
934
            if ($user instanceof User) {
935
                self::insertUser($user, $edit_date, $chat);
936
            }
937
938
            $sth = self::$pdo->prepare('
939
                INSERT IGNORE INTO `' . TB_EDITED_MESSAGE . '`
940
                (`chat_id`, `message_id`, `user_id`, `edit_date`, `text`, `entities`, `caption`)
941
                VALUES
942
                (:chat_id, :message_id, :user_id, :edit_date, :text, :entities, :caption)
943
            ');
944
945
            $user_id = null;
946
            if ($user instanceof User) {
947
                $user_id = $user->getId();
948
            }
949
950
            $sth->bindValue(':chat_id', $chat->getId());
951
            $sth->bindValue(':message_id', $edited_message->getMessageId());
952
            $sth->bindValue(':user_id', $user_id);
953
            $sth->bindValue(':edit_date', $edit_date);
954
            $sth->bindValue(':text', $edited_message->getText());
955
            $sth->bindValue(':entities', self::entitiesArrayToJson($edited_message->getEntities(), null));
956
            $sth->bindValue(':caption', $edited_message->getCaption());
957
958
            return $sth->execute();
959
        } catch (PDOException $e) {
960
            throw new TelegramException($e->getMessage());
961
        }
962
    }
963
964
    /**
965
     * Select Groups, Supergroups, Channels and/or single user Chats (also by ID or text)
966
     *
967
     * @param $select_chats_params
968
     *
969
     * @return array|bool
970
     * @throws TelegramException
971
     */
972
    public static function selectChats($select_chats_params)
973
    {
974
        if (!self::isDbConnected()) {
975
            return false;
976
        }
977
978
        // Set defaults for omitted values.
979
        $select = array_merge([
980
            'groups'      => true,
981
            'supergroups' => true,
982
            'channels'    => true,
983
            'users'       => true,
984
            'date_from'   => null,
985
            'date_to'     => null,
986
            'chat_id'     => null,
987
            'text'        => null,
988
        ], $select_chats_params);
989
990
        if (!$select['groups'] && !$select['users'] && !$select['supergroups'] && !$select['channels']) {
991
            return false;
992
        }
993
994
        try {
995
            $query = '
996
                SELECT * ,
997
                ' . TB_CHAT . '.`id` AS `chat_id`,
998
                ' . TB_CHAT . '.`username` AS `chat_username`,
999
                ' . TB_CHAT . '.`created_at` AS `chat_created_at`,
1000
                ' . TB_CHAT . '.`updated_at` AS `chat_updated_at`
1001
            ';
1002
            if ($select['users']) {
1003
                $query .= '
1004
                    , ' . TB_USER . '.`id` AS `user_id`
1005
                    FROM `' . TB_CHAT . '`
1006
                    LEFT JOIN `' . TB_USER . '`
1007
                    ON ' . TB_CHAT . '.`id`=' . TB_USER . '.`id`
1008
                ';
1009
            } else {
1010
                $query .= 'FROM `' . TB_CHAT . '`';
1011
            }
1012
1013
            // Building parts of query
1014
            $where  = [];
1015
            $tokens = [];
1016
1017
            if (!$select['groups'] || !$select['users'] || !$select['supergroups'] || !$select['channels']) {
1018
                $chat_or_user = [];
1019
1020
                $select['groups'] && $chat_or_user[] = TB_CHAT . '.`type` = "group"';
1021
                $select['supergroups'] && $chat_or_user[] = TB_CHAT . '.`type` = "supergroup"';
1022
                $select['channels'] && $chat_or_user[] = TB_CHAT . '.`type` = "channel"';
1023
                $select['users'] && $chat_or_user[] = TB_CHAT . '.`type` = "private"';
1024
1025
                $where[] = '(' . implode(' OR ', $chat_or_user) . ')';
1026
            }
1027
1028
            if (null !== $select['date_from']) {
1029
                $where[]              = TB_CHAT . '.`updated_at` >= :date_from';
1030
                $tokens[':date_from'] = $select['date_from'];
1031
            }
1032
1033
            if (null !== $select['date_to']) {
1034
                $where[]            = TB_CHAT . '.`updated_at` <= :date_to';
1035
                $tokens[':date_to'] = $select['date_to'];
1036
            }
1037
1038
            if (null !== $select['chat_id']) {
1039
                $where[]            = TB_CHAT . '.`id` = :chat_id';
1040
                $tokens[':chat_id'] = $select['chat_id'];
1041
            }
1042
1043
            if (null !== $select['text']) {
1044
                $text_like = '%' . strtolower($select['text']) . '%';
1045
                if ($select['users']) {
1046
                    $where[]          = '(
1047
                        LOWER(' . TB_CHAT . '.`title`) LIKE :text1
1048
                        OR LOWER(' . TB_USER . '.`first_name`) LIKE :text2
1049
                        OR LOWER(' . TB_USER . '.`last_name`) LIKE :text3
1050
                        OR LOWER(' . TB_USER . '.`username`) LIKE :text4
1051
                    )';
1052
                    $tokens[':text1'] = $text_like;
1053
                    $tokens[':text2'] = $text_like;
1054
                    $tokens[':text3'] = $text_like;
1055
                    $tokens[':text4'] = $text_like;
1056
                } else {
1057
                    $where[]         = 'LOWER(' . TB_CHAT . '.`title`) LIKE :text';
1058
                    $tokens[':text'] = $text_like;
1059
                }
1060
            }
1061
1062
            if (!empty($where)) {
1063
                $query .= ' WHERE ' . implode(' AND ', $where);
1064
            }
1065
1066
            $query .= ' ORDER BY ' . TB_CHAT . '.`updated_at` ASC';
1067
1068
            $sth = self::$pdo->prepare($query);
1069
            $sth->execute($tokens);
1070
1071
            return $sth->fetchAll(PDO::FETCH_ASSOC);
1072
        } catch (PDOException $e) {
1073
            throw new TelegramException($e->getMessage());
1074
        }
1075
    }
1076
1077
    /**
1078
     * Get Telegram API request count for current chat / message
1079
     *
1080
     * @param integer $chat_id
1081
     * @param string  $inline_message_id
1082
     *
1083
     * @return array|bool Array containing TOTAL and CURRENT fields or false on invalid arguments
1084
     * @throws TelegramException
1085
     */
1086
    public static function getTelegramRequestCount($chat_id = null, $inline_message_id = null)
1087
    {
1088
        if (!self::isDbConnected()) {
1089
            return false;
1090
        }
1091
1092
        try {
1093
            $sth = self::$pdo->prepare('SELECT 
1094
                (SELECT COUNT(DISTINCT `chat_id`) FROM `' . TB_REQUEST_LIMITER . '` WHERE `created_at` >= :created_at_1) AS LIMIT_PER_SEC_ALL,
1095
                (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,
1096
                (SELECT COUNT(*) FROM `' . TB_REQUEST_LIMITER . '` WHERE `created_at` >= :created_at_minute AND `chat_id` = :chat_id_2) AS LIMIT_PER_MINUTE
1097
            ');
1098
1099
            $date        = self::getTimestamp();
1100
            $date_minute = self::getTimestamp(strtotime('-1 minute'));
1101
1102
            $sth->bindValue(':chat_id_1', $chat_id);
1103
            $sth->bindValue(':chat_id_2', $chat_id);
1104
            $sth->bindValue(':inline_message_id', $inline_message_id);
1105
            $sth->bindValue(':created_at_1', $date);
1106
            $sth->bindValue(':created_at_2', $date);
1107
            $sth->bindValue(':created_at_minute', $date_minute);
1108
1109
            $sth->execute();
1110
1111
            return $sth->fetch();
1112
        } catch (Exception $e) {
1113
            throw new TelegramException($e->getMessage());
1114
        }
1115
    }
1116
1117
    /**
1118
     * Insert Telegram API request in db
1119
     *
1120
     * @param string $method
1121
     * @param array  $data
1122
     *
1123
     * @return bool If the insert was successful
1124
     * @throws TelegramException
1125
     */
1126
    public static function insertTelegramRequest($method, $data)
1127
    {
1128
        if (!self::isDbConnected()) {
1129
            return false;
1130
        }
1131
1132
        try {
1133
            $sth = self::$pdo->prepare('INSERT INTO `' . TB_REQUEST_LIMITER . '`
1134
                (`method`, `chat_id`, `inline_message_id`, `created_at`)
1135
                VALUES
1136
                (:method, :chat_id, :inline_message_id, :created_at);
1137
            ');
1138
1139
            $chat_id           = isset($data['chat_id']) ? $data['chat_id'] : null;
1140
            $inline_message_id = isset($data['inline_message_id']) ? $data['inline_message_id'] : null;
1141
1142
            $sth->bindValue(':chat_id', $chat_id);
1143
            $sth->bindValue(':inline_message_id', $inline_message_id);
1144
            $sth->bindValue(':method', $method);
1145
            $sth->bindValue(':created_at', self::getTimestamp());
1146
1147
            return $sth->execute();
1148
        } catch (Exception $e) {
1149
            throw new TelegramException($e->getMessage());
1150
        }
1151
    }
1152
1153
    /**
1154
     * Bulk update the entries of any table
1155
     *
1156
     * @param string $table
1157
     * @param array  $fields_values
1158
     * @param array  $where_fields_values
1159
     *
1160
     * @return bool
1161
     * @throws TelegramException
1162
     */
1163 3
    public static function update($table, array $fields_values, array $where_fields_values)
1164
    {
1165 3
        if (empty($fields_values) || !self::isDbConnected()) {
1166
            return false;
1167
        }
1168
1169
        try {
1170
            // Building parts of query
1171 3
            $tokens = $fields = $where = [];
1172
1173
            // Fields with values to update
1174 3 View Code Duplication
            foreach ($fields_values as $field => $value) {
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...
1175 3
                $token          = ':' . count($tokens);
1176 3
                $fields[]       = "`{$field}` = {$token}";
1177 3
                $tokens[$token] = $value;
1178
            }
1179
1180
            // Where conditions
1181 3 View Code Duplication
            foreach ($where_fields_values as $field => $value) {
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...
1182 3
                $token          = ':' . count($tokens);
1183 3
                $where[]        = "`{$field}` = {$token}";
1184 3
                $tokens[$token] = $value;
1185
            }
1186
1187 3
            $sql = 'UPDATE `' . $table . '` SET ' . implode(', ', $fields);
1188 3
            $sql .= count($where) > 0 ? ' WHERE ' . implode(' AND ', $where) : '';
1189
1190 3
            return self::$pdo->prepare($sql)->execute($tokens);
1191
        } catch (Exception $e) {
1192
            throw new TelegramException($e->getMessage());
1193
        }
1194
    }
1195
}
1196