Completed
Push — add_missing_db_fields ( 46a6ca )
by Armando
02:46
created

DB::insertEditedMessageRequest()   B

Complexity

Conditions 5
Paths 46

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

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