Completed
Push — master ( 54823d...3e7af9 )
by Armando
04:43 queued 02:31
created

DB::insertInlineQueryRequest()   B

Complexity

Conditions 4
Paths 22

Size

Total Lines 35
Code Lines 21

Duplication

Lines 35
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 35
loc 35
ccs 0
cts 20
cp 0
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 21
nc 22
nop 1
crap 20
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 null, current timestamp is used)
264
     *
265
     * @return string
266
     */
267 7
    protected static function getTimestamp($time = null)
268
    {
269 7
        if ($time === null) {
270 6
            $time = time();
271
        }
272
273 7
        return date('Y-m-d H:i:s', $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, 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
            $sth->bindValue(':created_at', $date);
393 6
            $sth->bindValue(':updated_at', $date);
394
395 6
            $status = $sth->execute();
396
        } catch (PDOException $e) {
397
            throw new TelegramException($e->getMessage());
398
        }
399
400
        // Also insert the relationship to the chat into the user_chat table
401 6
        if ($chat instanceof Chat) {
402
            try {
403 6
                $sth = self::$pdo->prepare('
404 6
                    INSERT IGNORE INTO `' . TB_USER_CHAT . '`
405
                    (`user_id`, `chat_id`)
406
                    VALUES
407
                    (:user_id, :chat_id)
408
                ');
409
410 6
                $sth->bindValue(':user_id', $user->getId());
411 6
                $sth->bindValue(':chat_id', $chat->getId());
412
413 6
                $status = $sth->execute();
414
            } catch (PDOException $e) {
415
                throw new TelegramException($e->getMessage());
416
            }
417
        }
418
419 6
        return $status;
420
    }
421
422
    /**
423
     * Insert chat
424
     *
425
     * @param Chat   $chat
426
     * @param string $date
427
     * @param string $migrate_to_chat_id
428
     *
429
     * @return bool If the insert was successful
430
     * @throws TelegramException
431
     */
432 6
    public static function insertChat(Chat $chat, $date, $migrate_to_chat_id = null)
433
    {
434 6
        if (!self::isDbConnected()) {
435
            return false;
436
        }
437
438
        try {
439 6
            $sth = self::$pdo->prepare('
440 6
                INSERT IGNORE INTO `' . TB_CHAT . '`
441
                (`id`, `type`, `title`, `username`, `all_members_are_administrators`, `created_at` ,`updated_at`, `old_id`)
442
                VALUES
443
                (:id, :type, :title, :username, :all_members_are_administrators, :created_at, :updated_at, :old_id)
444
                ON DUPLICATE KEY UPDATE
445
                    `type`                           = VALUES(`type`),
446
                    `title`                          = VALUES(`title`),
447
                    `username`                       = VALUES(`username`),
448
                    `all_members_are_administrators` = VALUES(`all_members_are_administrators`),
449
                    `updated_at`                     = VALUES(`updated_at`)
450
            ');
451
452 6
            $chat_id   = $chat->getId();
453 6
            $chat_type = $chat->getType();
454
455 6
            if ($migrate_to_chat_id !== null) {
456
                $chat_type = 'supergroup';
457
458
                $sth->bindValue(':id', $migrate_to_chat_id);
459
                $sth->bindValue(':old_id', $chat_id);
460
            } else {
461 6
                $sth->bindValue(':id', $chat_id);
462 6
                $sth->bindValue(':old_id', $migrate_to_chat_id);
463
            }
464
465 6
            $sth->bindValue(':type', $chat_type);
466 6
            $sth->bindValue(':title', $chat->getTitle());
467 6
            $sth->bindValue(':username', $chat->getUsername());
468 6
            $sth->bindValue(':all_members_are_administrators', $chat->getAllMembersAreAdministrators(), PDO::PARAM_INT);
469 6
            $sth->bindValue(':created_at', $date);
470 6
            $sth->bindValue(':updated_at', $date);
471
472 6
            return $sth->execute();
473
        } catch (PDOException $e) {
474
            throw new TelegramException($e->getMessage());
475
        }
476
    }
477
478
    /**
479
     * Insert request into database
480
     *
481
     * @todo self::$pdo->lastInsertId() - unsafe usage if expected previous insert fails?
482
     *
483
     * @param Update $update
484
     *
485
     * @return bool
486
     * @throws TelegramException
487
     */
488
    public static function insertRequest(Update $update)
489
    {
490
        if (!self::isDbConnected()) {
491
            return false;
492
        }
493
494
        $update_id   = $update->getUpdateId();
495
        $update_type = $update->getUpdateType();
496
497
        if (count(self::selectTelegramUpdate(1, $update_id)) === 1) {
498
            throw new TelegramException('Duplicate update received!');
499
        }
500
501
        // @todo Make this simpler: if ($message = $update->getMessage()) ...
502
        if ($update_type === 'message') {
503
            $message = $update->getMessage();
504
505 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...
506
                $message_id = $message->getMessageId();
507
                $chat_id    = $message->getChat()->getId();
508
509
                return self::insertTelegramUpdate(
510
                    $update_id,
511
                    $chat_id,
512
                    $message_id
513
                );
514
            }
515 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...
516
            $edited_message = $update->getEditedMessage();
517
518
            if (self::insertEditedMessageRequest($edited_message)) {
519
                $edited_message_local_id = self::$pdo->lastInsertId();
520
                $chat_id                 = $edited_message->getChat()->getId();
521
522
                return self::insertTelegramUpdate(
523
                    $update_id,
524
                    $chat_id,
525
                    null,
526
                    null,
527
                    null,
528
                    null,
529
                    $edited_message_local_id
530
                );
531
            }
532
        } elseif ($update_type === 'channel_post') {
533
            $channel_post = $update->getChannelPost();
534
535 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...
536
                $message_id = $channel_post->getMessageId();
537
                $chat_id    = $channel_post->getChat()->getId();
538
539
                return self::insertTelegramUpdate(
540
                    $update_id,
541
                    $chat_id,
542
                    $message_id
543
                );
544
            }
545 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...
546
            $edited_channel_post = $update->getEditedChannelPost();
547
548
            if (self::insertEditedMessageRequest($edited_channel_post)) {
549
                $edited_channel_post_local_id = self::$pdo->lastInsertId();
550
                $chat_id                      = $edited_channel_post->getChat()->getId();
551
552
                return self::insertTelegramUpdate(
553
                    $update_id,
554
                    $chat_id,
555
                    null,
556
                    null,
557
                    null,
558
                    null,
559
                    $edited_channel_post_local_id
560
                );
561
            }
562
        } elseif ($update_type === 'inline_query') {
563
            $inline_query = $update->getInlineQuery();
564
565
            if (self::insertInlineQueryRequest($inline_query)) {
566
                $inline_query_id = $inline_query->getId();
567
568
                return self::insertTelegramUpdate(
569
                    $update_id,
570
                    null,
571
                    null,
572
                    $inline_query_id
573
                );
574
            }
575
        } elseif ($update_type === 'chosen_inline_result') {
576
            $chosen_inline_result = $update->getChosenInlineResult();
577
578
            if (self::insertChosenInlineResultRequest($chosen_inline_result)) {
579
                $chosen_inline_result_local_id = self::$pdo->lastInsertId();
580
581
                return self::insertTelegramUpdate(
582
                    $update_id,
583
                    null,
584
                    null,
585
                    null,
586
                    $chosen_inline_result_local_id
587
                );
588
            }
589
        } elseif ($update_type === 'callback_query') {
590
            $callback_query = $update->getCallbackQuery();
591
592
            if (self::insertCallbackQueryRequest($callback_query)) {
593
                $callback_query_id = $callback_query->getId();
594
595
                return self::insertTelegramUpdate(
596
                    $update_id,
597
                    null,
598
                    null,
599
                    null,
600
                    null,
601
                    $callback_query_id
602
                );
603
            }
604
        }
605
606
        return false;
607
    }
608
609
    /**
610
     * Insert inline query request into database
611
     *
612
     * @param InlineQuery $inline_query
613
     *
614
     * @return bool If the insert was successful
615
     * @throws TelegramException
616
     */
617 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...
618
    {
619
        if (!self::isDbConnected()) {
620
            return false;
621
        }
622
623
        try {
624
            $sth = self::$pdo->prepare('
625
                INSERT IGNORE INTO `' . TB_INLINE_QUERY . '`
626
                (`id`, `user_id`, `location`, `query`, `offset`, `created_at`)
627
                VALUES
628
                (:id, :user_id, :location, :query, :offset, :created_at)
629
            ');
630
631
            $date    = self::getTimestamp();
632
            $user_id = null;
633
634
            $user = $inline_query->getFrom();
635
            if ($user instanceof User) {
636
                $user_id = $user->getId();
637
                self::insertUser($user, $date);
638
            }
639
640
            $sth->bindValue(':id', $inline_query->getId());
641
            $sth->bindValue(':user_id', $user_id);
642
            $sth->bindValue(':location', $inline_query->getLocation());
643
            $sth->bindValue(':query', $inline_query->getQuery());
644
            $sth->bindValue(':offset', $inline_query->getOffset());
645
            $sth->bindValue(':created_at', $date);
646
647
            return $sth->execute();
648
        } catch (PDOException $e) {
649
            throw new TelegramException($e->getMessage());
650
        }
651
    }
652
653
    /**
654
     * Insert chosen inline result request into database
655
     *
656
     * @param ChosenInlineResult $chosen_inline_result
657
     *
658
     * @return bool If the insert was successful
659
     * @throws TelegramException
660
     */
661 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...
662
    {
663
        if (!self::isDbConnected()) {
664
            return false;
665
        }
666
667
        try {
668
            $sth = self::$pdo->prepare('
669
                INSERT INTO `' . TB_CHOSEN_INLINE_RESULT . '`
670
                (`result_id`, `user_id`, `location`, `inline_message_id`, `query`, `created_at`)
671
                VALUES
672
                (:result_id, :user_id, :location, :inline_message_id, :query, :created_at)
673
            ');
674
675
            $date    = self::getTimestamp();
676
            $user_id = null;
677
678
            $user = $chosen_inline_result->getFrom();
679
            if ($user instanceof User) {
680
                $user_id = $user->getId();
681
                self::insertUser($user, $date);
682
            }
683
684
            $sth->bindValue(':result_id', $chosen_inline_result->getResultId());
685
            $sth->bindValue(':user_id', $user_id);
686
            $sth->bindValue(':location', $chosen_inline_result->getLocation());
687
            $sth->bindValue(':inline_message_id', $chosen_inline_result->getInlineMessageId());
688
            $sth->bindValue(':query', $chosen_inline_result->getQuery());
689
            $sth->bindValue(':created_at', $date);
690
691
            return $sth->execute();
692
        } catch (PDOException $e) {
693
            throw new TelegramException($e->getMessage());
694
        }
695
    }
696
697
    /**
698
     * Insert callback query request into database
699
     *
700
     * @param CallbackQuery $callback_query
701
     *
702
     * @return bool If the insert was successful
703
     * @throws TelegramException
704
     */
705
    public static function insertCallbackQueryRequest(CallbackQuery $callback_query)
706
    {
707
        if (!self::isDbConnected()) {
708
            return false;
709
        }
710
711
        try {
712
            $sth = self::$pdo->prepare('
713
                INSERT IGNORE INTO `' . TB_CALLBACK_QUERY . '`
714
                (`id`, `user_id`, `chat_id`, `message_id`, `inline_message_id`, `data`, `created_at`)
715
                VALUES
716
                (:id, :user_id, :chat_id, :message_id, :inline_message_id, :data, :created_at)
717
            ');
718
719
            $date    = self::getTimestamp();
720
            $user_id = null;
721
722
            $user = $callback_query->getFrom();
723
            if ($user instanceof User) {
724
                $user_id = $user->getId();
725
                self::insertUser($user, $date);
726
            }
727
728
            $message    = $callback_query->getMessage();
729
            $chat_id    = null;
730
            $message_id = null;
731
            if ($message instanceof Message) {
732
                $chat_id    = $message->getChat()->getId();
733
                $message_id = $message->getMessageId();
734
735
                $is_message = self::$pdo->query('
736
                    SELECT *
737
                    FROM `' . TB_MESSAGE . '`
738
                    WHERE `id` = ' . $message_id . '
739
                      AND `chat_id` = ' . $chat_id . '
740
                    LIMIT 1
741
                ')->rowCount();
742
743
                if ($is_message) {
744
                    self::insertEditedMessageRequest($message);
745
                } else {
746
                    self::insertMessageRequest($message);
747
                }
748
            }
749
750
            $sth->bindValue(':id', $callback_query->getId());
751
            $sth->bindValue(':user_id', $user_id);
752
            $sth->bindValue(':chat_id', $chat_id);
753
            $sth->bindValue(':message_id', $message_id);
754
            $sth->bindValue(':inline_message_id', $callback_query->getInlineMessageId());
755
            $sth->bindValue(':data', $callback_query->getData());
756
            $sth->bindValue(':created_at', $date);
757
758
            return $sth->execute();
759
        } catch (PDOException $e) {
760
            throw new TelegramException($e->getMessage());
761
        }
762
    }
763
764
    /**
765
     * Insert Message request in db
766
     *
767
     * @todo Complete with new fields: https://core.telegram.org/bots/api#message
768
     *
769
     * @param Message $message
770
     *
771
     * @return bool If the insert was successful
772
     * @throws TelegramException
773
     */
774 6
    public static function insertMessageRequest(Message $message)
775
    {
776 6
        if (!self::isDbConnected()) {
777
            return false;
778
        }
779
780 6
        $date = self::getTimestamp($message->getDate());
781
782
        // Insert chat, update chat id in case it migrated
783 6
        $chat = $message->getChat();
784 6
        self::insertChat($chat, $date, $message->getMigrateToChatId());
785
786
        // Insert user and the relation with the chat
787 6
        $user = $message->getFrom();
788 6
        if ($user instanceof User) {
789 6
            self::insertUser($user, $date, $chat);
790
        }
791
792
        // Insert the forwarded message user in users table
793 6
        $forward_date = null;
794 6
        $forward_from = $message->getForwardFrom();
795 6
        if ($forward_from instanceof User) {
796
            self::insertUser($forward_from, $forward_date);
797
            $forward_from = $forward_from->getId();
798
            $forward_date = self::getTimestamp($message->getForwardDate());
799
        }
800 6
        $forward_from_chat = $message->getForwardFromChat();
801 6
        if ($forward_from_chat instanceof Chat) {
802
            self::insertChat($forward_from_chat, $forward_date);
803
            $forward_from_chat = $forward_from_chat->getId();
804
            $forward_date      = self::getTimestamp($message->getForwardDate());
805
        }
806
807
        // New and left chat member
808 6
        $new_chat_members_ids = null;
809 6
        $left_chat_member_id  = null;
810
811 6
        $new_chat_members = $message->getNewChatMembers();
812 6
        $left_chat_member = $message->getLeftChatMember();
813 6
        if (!empty($new_chat_members)) {
814
            foreach ($new_chat_members as $new_chat_member) {
815
                if ($new_chat_member instanceof User) {
816
                    // Insert the new chat user
817
                    self::insertUser($new_chat_member, $date, $chat);
818
                    $new_chat_members_ids[] = $new_chat_member->getId();
819
                }
820
            }
821
            $new_chat_members_ids = implode(',', $new_chat_members_ids);
822 6
        } elseif ($left_chat_member instanceof User) {
823
            // Insert the left chat user
824
            self::insertUser($left_chat_member, $date, $chat);
825
            $left_chat_member_id = $left_chat_member->getId();
826
        }
827
828
        try {
829 6
            $sth = self::$pdo->prepare('
830 6
                INSERT IGNORE INTO `' . TB_MESSAGE . '`
831
                (
832
                    `id`, `user_id`, `chat_id`, `date`, `forward_from`, `forward_from_chat`, `forward_from_message_id`,
833
                    `forward_date`, `reply_to_chat`, `reply_to_message`, `media_group_id`, `text`, `entities`, `audio`, `document`,
834
                    `photo`, `sticker`, `video`, `voice`, `video_note`, `caption`, `contact`,
835
                    `location`, `venue`, `new_chat_members`, `left_chat_member`,
836
                    `new_chat_title`,`new_chat_photo`, `delete_chat_photo`, `group_chat_created`,
837
                    `supergroup_chat_created`, `channel_chat_created`,
838
                    `migrate_from_chat_id`, `migrate_to_chat_id`, `pinned_message`
839
                ) VALUES (
840
                    :message_id, :user_id, :chat_id, :date, :forward_from, :forward_from_chat, :forward_from_message_id,
841
                    :forward_date, :reply_to_chat, :reply_to_message, :media_group_id, :text, :entities, :audio, :document,
842
                    :photo, :sticker, :video, :voice, :video_note, :caption, :contact,
843
                    :location, :venue, :new_chat_members, :left_chat_member,
844
                    :new_chat_title, :new_chat_photo, :delete_chat_photo, :group_chat_created,
845
                    :supergroup_chat_created, :channel_chat_created,
846
                    :migrate_from_chat_id, :migrate_to_chat_id, :pinned_message
847
                )
848
            ');
849
850 6
            $user_id = null;
851 6
            if ($user instanceof User) {
852 6
                $user_id = $user->getId();
853
            }
854 6
            $chat_id = $chat->getId();
855
856 6
            $reply_to_message    = $message->getReplyToMessage();
857 6
            $reply_to_message_id = null;
858 6
            if ($reply_to_message instanceof ReplyToMessage) {
859
                $reply_to_message_id = $reply_to_message->getMessageId();
860
                // please notice that, as explained in the documentation, reply_to_message don't contain other
861
                // reply_to_message field so recursion deep is 1
862
                self::insertMessageRequest($reply_to_message);
863
            }
864
865 6
            $sth->bindValue(':message_id', $message->getMessageId());
866 6
            $sth->bindValue(':chat_id', $chat_id);
867 6
            $sth->bindValue(':user_id', $user_id);
868 6
            $sth->bindValue(':date', $date);
869 6
            $sth->bindValue(':forward_from', $forward_from);
870 6
            $sth->bindValue(':forward_from_chat', $forward_from_chat);
871 6
            $sth->bindValue(':forward_from_message_id', $message->getForwardFromMessageId());
872 6
            $sth->bindValue(':forward_date', $forward_date);
873
874 6
            $reply_to_chat_id = null;
875 6
            if ($reply_to_message_id !== null) {
876
                $reply_to_chat_id = $chat_id;
877
            }
878 6
            $sth->bindValue(':reply_to_chat', $reply_to_chat_id);
879 6
            $sth->bindValue(':reply_to_message', $reply_to_message_id);
880
881 6
            $sth->bindValue(':media_group_id', $message->getMediaGroupId());
882 6
            $sth->bindValue(':text', $message->getText());
883 6
            $sth->bindValue(':entities', $t = self::entitiesArrayToJson($message->getEntities(), null));
884 6
            $sth->bindValue(':audio', $message->getAudio());
885 6
            $sth->bindValue(':document', $message->getDocument());
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
907 6
            return $sth->execute();
908
        } catch (PDOException $e) {
909
            throw new TelegramException($e->getMessage());
910
        }
911
    }
912
913
    /**
914
     * Insert Edited Message request in db
915
     *
916
     * @param Message $edited_message
917
     *
918
     * @return bool If the insert was successful
919
     * @throws TelegramException
920
     */
921
    public static function insertEditedMessageRequest(Message $edited_message)
922
    {
923
        if (!self::isDbConnected()) {
924
            return false;
925
        }
926
927
        try {
928
            $edit_date = self::getTimestamp($edited_message->getEditDate());
929
930
            // Insert chat
931
            $chat = $edited_message->getChat();
932
            self::insertChat($chat, $edit_date);
933
934
            // Insert user and the relation with the chat
935
            $user = $edited_message->getFrom();
936
            if ($user instanceof User) {
937
                self::insertUser($user, $edit_date, $chat);
938
            }
939
940
            $sth = self::$pdo->prepare('
941
                INSERT IGNORE INTO `' . TB_EDITED_MESSAGE . '`
942
                (`chat_id`, `message_id`, `user_id`, `edit_date`, `text`, `entities`, `caption`)
943
                VALUES
944
                (:chat_id, :message_id, :user_id, :edit_date, :text, :entities, :caption)
945
            ');
946
947
            $user_id = null;
948
            if ($user instanceof User) {
949
                $user_id = $user->getId();
950
            }
951
952
            $sth->bindValue(':chat_id', $chat->getId());
953
            $sth->bindValue(':message_id', $edited_message->getMessageId());
954
            $sth->bindValue(':user_id', $user_id);
955
            $sth->bindValue(':edit_date', $edit_date);
956
            $sth->bindValue(':text', $edited_message->getText());
957
            $sth->bindValue(':entities', self::entitiesArrayToJson($edited_message->getEntities(), null));
958
            $sth->bindValue(':caption', $edited_message->getCaption());
959
960
            return $sth->execute();
961
        } catch (PDOException $e) {
962
            throw new TelegramException($e->getMessage());
963
        }
964
    }
965
966
    /**
967
     * Select Groups, Supergroups, Channels and/or single user Chats (also by ID or text)
968
     *
969
     * @param $select_chats_params
970
     *
971
     * @return array|bool
972
     * @throws TelegramException
973
     */
974
    public static function selectChats($select_chats_params)
975
    {
976
        if (!self::isDbConnected()) {
977
            return false;
978
        }
979
980
        // Set defaults for omitted values.
981
        $select = array_merge([
982
            'groups'      => true,
983
            'supergroups' => true,
984
            'channels'    => true,
985
            'users'       => true,
986
            'date_from'   => null,
987
            'date_to'     => null,
988
            'chat_id'     => null,
989
            'text'        => null,
990
        ], $select_chats_params);
991
992
        if (!$select['groups'] && !$select['users'] && !$select['supergroups'] && !$select['channels']) {
993
            return false;
994
        }
995
996
        try {
997
            $query = '
998
                SELECT * ,
999
                ' . TB_CHAT . '.`id` AS `chat_id`,
1000
                ' . TB_CHAT . '.`username` AS `chat_username`,
1001
                ' . TB_CHAT . '.`created_at` AS `chat_created_at`,
1002
                ' . TB_CHAT . '.`updated_at` AS `chat_updated_at`
1003
            ';
1004
            if ($select['users']) {
1005
                $query .= '
1006
                    , ' . TB_USER . '.`id` AS `user_id`
1007
                    FROM `' . TB_CHAT . '`
1008
                    LEFT JOIN `' . TB_USER . '`
1009
                    ON ' . TB_CHAT . '.`id`=' . TB_USER . '.`id`
1010
                ';
1011
            } else {
1012
                $query .= 'FROM `' . TB_CHAT . '`';
1013
            }
1014
1015
            // Building parts of query
1016
            $where  = [];
1017
            $tokens = [];
1018
1019
            if (!$select['groups'] || !$select['users'] || !$select['supergroups'] || !$select['channels']) {
1020
                $chat_or_user = [];
1021
1022
                $select['groups'] && $chat_or_user[] = TB_CHAT . '.`type` = "group"';
1023
                $select['supergroups'] && $chat_or_user[] = TB_CHAT . '.`type` = "supergroup"';
1024
                $select['channels'] && $chat_or_user[] = TB_CHAT . '.`type` = "channel"';
1025
                $select['users'] && $chat_or_user[] = TB_CHAT . '.`type` = "private"';
1026
1027
                $where[] = '(' . implode(' OR ', $chat_or_user) . ')';
1028
            }
1029
1030
            if (null !== $select['date_from']) {
1031
                $where[]              = TB_CHAT . '.`updated_at` >= :date_from';
1032
                $tokens[':date_from'] = $select['date_from'];
1033
            }
1034
1035
            if (null !== $select['date_to']) {
1036
                $where[]            = TB_CHAT . '.`updated_at` <= :date_to';
1037
                $tokens[':date_to'] = $select['date_to'];
1038
            }
1039
1040
            if (null !== $select['chat_id']) {
1041
                $where[]            = TB_CHAT . '.`id` = :chat_id';
1042
                $tokens[':chat_id'] = $select['chat_id'];
1043
            }
1044
1045
            if (null !== $select['text']) {
1046
                $text_like = '%' . strtolower($select['text']) . '%';
1047
                if ($select['users']) {
1048
                    $where[]          = '(
1049
                        LOWER(' . TB_CHAT . '.`title`) LIKE :text1
1050
                        OR LOWER(' . TB_USER . '.`first_name`) LIKE :text2
1051
                        OR LOWER(' . TB_USER . '.`last_name`) LIKE :text3
1052
                        OR LOWER(' . TB_USER . '.`username`) LIKE :text4
1053
                    )';
1054
                    $tokens[':text1'] = $text_like;
1055
                    $tokens[':text2'] = $text_like;
1056
                    $tokens[':text3'] = $text_like;
1057
                    $tokens[':text4'] = $text_like;
1058
                } else {
1059
                    $where[]         = 'LOWER(' . TB_CHAT . '.`title`) LIKE :text';
1060
                    $tokens[':text'] = $text_like;
1061
                }
1062
            }
1063
1064
            if (!empty($where)) {
1065
                $query .= ' WHERE ' . implode(' AND ', $where);
1066
            }
1067
1068
            $query .= ' ORDER BY ' . TB_CHAT . '.`updated_at` ASC';
1069
1070
            $sth = self::$pdo->prepare($query);
1071
            $sth->execute($tokens);
1072
1073
            return $sth->fetchAll(PDO::FETCH_ASSOC);
1074
        } catch (PDOException $e) {
1075
            throw new TelegramException($e->getMessage());
1076
        }
1077
    }
1078
1079
    /**
1080
     * Get Telegram API request count for current chat / message
1081
     *
1082
     * @param integer $chat_id
1083
     * @param string  $inline_message_id
1084
     *
1085
     * @return array|bool Array containing TOTAL and CURRENT fields or false on invalid arguments
1086
     * @throws TelegramException
1087
     */
1088
    public static function getTelegramRequestCount($chat_id = null, $inline_message_id = null)
1089
    {
1090
        if (!self::isDbConnected()) {
1091
            return false;
1092
        }
1093
1094
        try {
1095
            $sth = self::$pdo->prepare('SELECT 
1096
                (SELECT COUNT(DISTINCT `chat_id`) FROM `' . TB_REQUEST_LIMITER . '` WHERE `created_at` >= :created_at_1) AS LIMIT_PER_SEC_ALL,
1097
                (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,
1098
                (SELECT COUNT(*) FROM `' . TB_REQUEST_LIMITER . '` WHERE `created_at` >= :created_at_minute AND `chat_id` = :chat_id_2) AS LIMIT_PER_MINUTE
1099
            ');
1100
1101
            $date        = self::getTimestamp();
1102
            $date_minute = self::getTimestamp(strtotime('-1 minute'));
1103
1104
            $sth->bindValue(':chat_id_1', $chat_id);
1105
            $sth->bindValue(':chat_id_2', $chat_id);
1106
            $sth->bindValue(':inline_message_id', $inline_message_id);
1107
            $sth->bindValue(':created_at_1', $date);
1108
            $sth->bindValue(':created_at_2', $date);
1109
            $sth->bindValue(':created_at_minute', $date_minute);
1110
1111
            $sth->execute();
1112
1113
            return $sth->fetch();
1114
        } catch (Exception $e) {
1115
            throw new TelegramException($e->getMessage());
1116
        }
1117
    }
1118
1119
    /**
1120
     * Insert Telegram API request in db
1121
     *
1122
     * @param string $method
1123
     * @param array  $data
1124
     *
1125
     * @return bool If the insert was successful
1126
     * @throws TelegramException
1127
     */
1128
    public static function insertTelegramRequest($method, $data)
1129
    {
1130
        if (!self::isDbConnected()) {
1131
            return false;
1132
        }
1133
1134
        try {
1135
            $sth = self::$pdo->prepare('INSERT INTO `' . TB_REQUEST_LIMITER . '`
1136
                (`method`, `chat_id`, `inline_message_id`, `created_at`)
1137
                VALUES
1138
                (:method, :chat_id, :inline_message_id, :created_at);
1139
            ');
1140
1141
            $chat_id           = isset($data['chat_id']) ? $data['chat_id'] : null;
1142
            $inline_message_id = isset($data['inline_message_id']) ? $data['inline_message_id'] : null;
1143
1144
            $sth->bindValue(':chat_id', $chat_id);
1145
            $sth->bindValue(':inline_message_id', $inline_message_id);
1146
            $sth->bindValue(':method', $method);
1147
            $sth->bindValue(':created_at', self::getTimestamp());
1148
1149
            return $sth->execute();
1150
        } catch (Exception $e) {
1151
            throw new TelegramException($e->getMessage());
1152
        }
1153
    }
1154
1155
    /**
1156
     * Bulk update the entries of any table
1157
     *
1158
     * @param string $table
1159
     * @param array  $fields_values
1160
     * @param array  $where_fields_values
1161
     *
1162
     * @return bool
1163
     * @throws TelegramException
1164
     */
1165 3
    public static function update($table, array $fields_values, array $where_fields_values)
1166
    {
1167 3
        if (empty($fields_values) || !self::isDbConnected()) {
1168
            return false;
1169
        }
1170
1171
        try {
1172
            // Building parts of query
1173 3
            $tokens = $fields = $where = [];
1174
1175
            // Fields with values to update
1176 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...
1177 3
                $token          = ':' . count($tokens);
1178 3
                $fields[]       = "`{$field}` = {$token}";
1179 3
                $tokens[$token] = $value;
1180
            }
1181
1182
            // Where conditions
1183 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...
1184 3
                $token          = ':' . count($tokens);
1185 3
                $where[]        = "`{$field}` = {$token}";
1186 3
                $tokens[$token] = $value;
1187
            }
1188
1189 3
            $sql = 'UPDATE `' . $table . '` SET ' . implode(', ', $fields);
1190 3
            $sql .= count($where) > 0 ? ' WHERE ' . implode(' AND ', $where) : '';
1191
1192 3
            return self::$pdo->prepare($sql)->execute($tokens);
1193
        } catch (Exception $e) {
1194
            throw new TelegramException($e->getMessage());
1195
        }
1196
    }
1197
}
1198