Completed
Push — 857-mysql_port ( 52ff92...6ae024 )
by Armando
03:05
created

DB::getTelegramRequestCount()   A

Complexity

Conditions 3
Paths 13

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 30
ccs 0
cts 19
cp 0
rs 9.44
c 0
b 0
f 0
cc 3
nc 13
nop 2
crap 12
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`, `all_members_are_administrators`, `created_at` ,`updated_at`, `old_id`)
443
                VALUES
444
                (:id, :type, :title, :username, :all_members_are_administrators, :created_at, :updated_at, :old_id)
445
                ON DUPLICATE KEY UPDATE
446
                    `type`                           = VALUES(`type`),
447
                    `title`                          = VALUES(`title`),
448
                    `username`                       = VALUES(`username`),
449
                    `all_members_are_administrators` = VALUES(`all_members_are_administrators`),
450
                    `updated_at`                     = VALUES(`updated_at`)
451
            ');
452
453 6
            $chat_id   = $chat->getId();
454 6
            $chat_type = $chat->getType();
455
456 6
            if ($migrate_to_chat_id !== null) {
457
                $chat_type = 'supergroup';
458
459
                $sth->bindValue(':id', $migrate_to_chat_id);
460
                $sth->bindValue(':old_id', $chat_id);
461
            } else {
462 6
                $sth->bindValue(':id', $chat_id);
463 6
                $sth->bindValue(':old_id', $migrate_to_chat_id);
464
            }
465
466 6
            $sth->bindValue(':type', $chat_type);
467 6
            $sth->bindValue(':title', $chat->getTitle());
468 6
            $sth->bindValue(':username', $chat->getUsername());
469 6
            $sth->bindValue(':all_members_are_administrators', $chat->getAllMembersAreAdministrators(), PDO::PARAM_INT);
470 6
            $date = $date ?: self::getTimestamp();
471 6
            $sth->bindValue(':created_at', $date);
472 6
            $sth->bindValue(':updated_at', $date);
473
474 6
            return $sth->execute();
475
        } catch (PDOException $e) {
476
            throw new TelegramException($e->getMessage());
477
        }
478
    }
479
480
    /**
481
     * Insert request into database
482
     *
483
     * @todo self::$pdo->lastInsertId() - unsafe usage if expected previous insert fails?
484
     *
485
     * @param Update $update
486
     *
487
     * @return bool
488
     * @throws TelegramException
489
     */
490
    public static function insertRequest(Update $update)
491
    {
492
        if (!self::isDbConnected()) {
493
            return false;
494
        }
495
496
        $update_id   = $update->getUpdateId();
497
        $update_type = $update->getUpdateType();
498
499
        // @todo Make this simpler: if ($message = $update->getMessage()) ...
500
        if ($update_type === 'message') {
501
            $message = $update->getMessage();
502
503 View Code Duplication
            if (self::insertMessageRequest($message)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
504
                $message_id = $message->getMessageId();
505
                $chat_id    = $message->getChat()->getId();
506
507
                return self::insertTelegramUpdate(
508
                    $update_id,
509
                    $chat_id,
510
                    $message_id
511
                );
512
            }
513 View Code Duplication
        } elseif ($update_type === 'edited_message') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
514
            $edited_message = $update->getEditedMessage();
515
516
            if (self::insertEditedMessageRequest($edited_message)) {
517
                $edited_message_local_id = self::$pdo->lastInsertId();
518
                $chat_id                 = $edited_message->getChat()->getId();
519
520
                return self::insertTelegramUpdate(
521
                    $update_id,
522
                    $chat_id,
523
                    null,
524
                    null,
525
                    null,
526
                    null,
527
                    $edited_message_local_id
528
                );
529
            }
530
        } elseif ($update_type === 'channel_post') {
531
            $channel_post = $update->getChannelPost();
532
533 View Code Duplication
            if (self::insertMessageRequest($channel_post)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
534
                $message_id = $channel_post->getMessageId();
535
                $chat_id    = $channel_post->getChat()->getId();
536
537
                return self::insertTelegramUpdate(
538
                    $update_id,
539
                    $chat_id,
540
                    $message_id
541
                );
542
            }
543 View Code Duplication
        } elseif ($update_type === 'edited_channel_post') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
544
            $edited_channel_post = $update->getEditedChannelPost();
545
546
            if (self::insertEditedMessageRequest($edited_channel_post)) {
547
                $edited_channel_post_local_id = self::$pdo->lastInsertId();
548
                $chat_id                      = $edited_channel_post->getChat()->getId();
549
550
                return self::insertTelegramUpdate(
551
                    $update_id,
552
                    $chat_id,
553
                    null,
554
                    null,
555
                    null,
556
                    null,
557
                    $edited_channel_post_local_id
558
                );
559
            }
560
        } elseif ($update_type === 'inline_query') {
561
            $inline_query = $update->getInlineQuery();
562
563
            if (self::insertInlineQueryRequest($inline_query)) {
564
                $inline_query_id = $inline_query->getId();
565
566
                return self::insertTelegramUpdate(
567
                    $update_id,
568
                    null,
569
                    null,
570
                    $inline_query_id
571
                );
572
            }
573
        } elseif ($update_type === 'chosen_inline_result') {
574
            $chosen_inline_result = $update->getChosenInlineResult();
575
576
            if (self::insertChosenInlineResultRequest($chosen_inline_result)) {
577
                $chosen_inline_result_local_id = self::$pdo->lastInsertId();
578
579
                return self::insertTelegramUpdate(
580
                    $update_id,
581
                    null,
582
                    null,
583
                    null,
584
                    $chosen_inline_result_local_id
585
                );
586
            }
587
        } elseif ($update_type === 'callback_query') {
588
            $callback_query = $update->getCallbackQuery();
589
590
            if (self::insertCallbackQueryRequest($callback_query)) {
591
                $callback_query_id = $callback_query->getId();
592
593
                return self::insertTelegramUpdate(
594
                    $update_id,
595
                    null,
596
                    null,
597
                    null,
598
                    null,
599
                    $callback_query_id
600
                );
601
            }
602
        }
603
604
        return false;
605
    }
606
607
    /**
608
     * Insert inline query request into database
609
     *
610
     * @param InlineQuery $inline_query
611
     *
612
     * @return bool If the insert was successful
613
     * @throws TelegramException
614
     */
615 View Code Duplication
    public static function insertInlineQueryRequest(InlineQuery $inline_query)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
616
    {
617
        if (!self::isDbConnected()) {
618
            return false;
619
        }
620
621
        try {
622
            $sth = self::$pdo->prepare('
623
                INSERT IGNORE INTO `' . TB_INLINE_QUERY . '`
624
                (`id`, `user_id`, `location`, `query`, `offset`, `created_at`)
625
                VALUES
626
                (:id, :user_id, :location, :query, :offset, :created_at)
627
            ');
628
629
            $date    = self::getTimestamp();
630
            $user_id = null;
631
632
            $user = $inline_query->getFrom();
633
            if ($user instanceof User) {
634
                $user_id = $user->getId();
635
                self::insertUser($user, $date);
636
            }
637
638
            $sth->bindValue(':id', $inline_query->getId());
639
            $sth->bindValue(':user_id', $user_id);
640
            $sth->bindValue(':location', $inline_query->getLocation());
641
            $sth->bindValue(':query', $inline_query->getQuery());
642
            $sth->bindValue(':offset', $inline_query->getOffset());
643
            $sth->bindValue(':created_at', $date);
644
645
            return $sth->execute();
646
        } catch (PDOException $e) {
647
            throw new TelegramException($e->getMessage());
648
        }
649
    }
650
651
    /**
652
     * Insert chosen inline result request into database
653
     *
654
     * @param ChosenInlineResult $chosen_inline_result
655
     *
656
     * @return bool If the insert was successful
657
     * @throws TelegramException
658
     */
659 View Code Duplication
    public static function insertChosenInlineResultRequest(ChosenInlineResult $chosen_inline_result)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
660
    {
661
        if (!self::isDbConnected()) {
662
            return false;
663
        }
664
665
        try {
666
            $sth = self::$pdo->prepare('
667
                INSERT INTO `' . TB_CHOSEN_INLINE_RESULT . '`
668
                (`result_id`, `user_id`, `location`, `inline_message_id`, `query`, `created_at`)
669
                VALUES
670
                (:result_id, :user_id, :location, :inline_message_id, :query, :created_at)
671
            ');
672
673
            $date    = self::getTimestamp();
674
            $user_id = null;
675
676
            $user = $chosen_inline_result->getFrom();
677
            if ($user instanceof User) {
678
                $user_id = $user->getId();
679
                self::insertUser($user, $date);
680
            }
681
682
            $sth->bindValue(':result_id', $chosen_inline_result->getResultId());
683
            $sth->bindValue(':user_id', $user_id);
684
            $sth->bindValue(':location', $chosen_inline_result->getLocation());
685
            $sth->bindValue(':inline_message_id', $chosen_inline_result->getInlineMessageId());
686
            $sth->bindValue(':query', $chosen_inline_result->getQuery());
687
            $sth->bindValue(':created_at', $date);
688
689
            return $sth->execute();
690
        } catch (PDOException $e) {
691
            throw new TelegramException($e->getMessage());
692
        }
693
    }
694
695
    /**
696
     * Insert callback query request into database
697
     *
698
     * @param CallbackQuery $callback_query
699
     *
700
     * @return bool If the insert was successful
701
     * @throws TelegramException
702
     */
703
    public static function insertCallbackQueryRequest(CallbackQuery $callback_query)
704
    {
705
        if (!self::isDbConnected()) {
706
            return false;
707
        }
708
709
        try {
710
            $sth = self::$pdo->prepare('
711
                INSERT IGNORE INTO `' . TB_CALLBACK_QUERY . '`
712
                (`id`, `user_id`, `chat_id`, `message_id`, `inline_message_id`, `data`, `created_at`)
713
                VALUES
714
                (:id, :user_id, :chat_id, :message_id, :inline_message_id, :data, :created_at)
715
            ');
716
717
            $date    = self::getTimestamp();
718
            $user_id = null;
719
720
            $user = $callback_query->getFrom();
721
            if ($user instanceof User) {
722
                $user_id = $user->getId();
723
                self::insertUser($user, $date);
724
            }
725
726
            $message    = $callback_query->getMessage();
727
            $chat_id    = null;
728
            $message_id = null;
729
            if ($message instanceof Message) {
730
                $chat_id    = $message->getChat()->getId();
731
                $message_id = $message->getMessageId();
732
733
                $is_message = self::$pdo->query('
734
                    SELECT *
735
                    FROM `' . TB_MESSAGE . '`
736
                    WHERE `id` = ' . $message_id . '
737
                      AND `chat_id` = ' . $chat_id . '
738
                    LIMIT 1
739
                ')->rowCount();
740
741
                if ($is_message) {
742
                    self::insertEditedMessageRequest($message);
743
                } else {
744
                    self::insertMessageRequest($message);
745
                }
746
            }
747
748
            $sth->bindValue(':id', $callback_query->getId());
749
            $sth->bindValue(':user_id', $user_id);
750
            $sth->bindValue(':chat_id', $chat_id);
751
            $sth->bindValue(':message_id', $message_id);
752
            $sth->bindValue(':inline_message_id', $callback_query->getInlineMessageId());
753
            $sth->bindValue(':data', $callback_query->getData());
754
            $sth->bindValue(':created_at', $date);
755
756
            return $sth->execute();
757
        } catch (PDOException $e) {
758
            throw new TelegramException($e->getMessage());
759
        }
760
    }
761
762
    /**
763
     * Insert Message request in db
764
     *
765
     * @todo Complete with new fields: https://core.telegram.org/bots/api#message
766
     *
767
     * @param Message $message
768
     *
769
     * @return bool If the insert was successful
770
     * @throws TelegramException
771
     */
772 6
    public static function insertMessageRequest(Message $message)
773
    {
774 6
        if (!self::isDbConnected()) {
775
            return false;
776
        }
777
778 6
        $date = self::getTimestamp($message->getDate());
779
780
        // Insert chat, update chat id in case it migrated
781 6
        $chat = $message->getChat();
782 6
        self::insertChat($chat, $date, $message->getMigrateToChatId());
783
784
        // Insert user and the relation with the chat
785 6
        $user = $message->getFrom();
786 6
        if ($user instanceof User) {
787 6
            self::insertUser($user, $date, $chat);
788
        }
789
790
        // Insert the forwarded message user in users table
791 6
        $forward_date = null;
792 6
        $forward_from = $message->getForwardFrom();
793 6
        if ($forward_from instanceof User) {
794
            self::insertUser($forward_from, $forward_date);
795
            $forward_from = $forward_from->getId();
796
            $forward_date = self::getTimestamp($message->getForwardDate());
797
        }
798 6
        $forward_from_chat = $message->getForwardFromChat();
799 6
        if ($forward_from_chat instanceof Chat) {
800
            self::insertChat($forward_from_chat, $forward_date);
801
            $forward_from_chat = $forward_from_chat->getId();
802
            $forward_date      = self::getTimestamp($message->getForwardDate());
803
        }
804
805
        // New and left chat member
806 6
        $new_chat_members_ids = null;
807 6
        $left_chat_member_id  = null;
808
809 6
        $new_chat_members = $message->getNewChatMembers();
810 6
        $left_chat_member = $message->getLeftChatMember();
811 6
        if (!empty($new_chat_members)) {
812
            foreach ($new_chat_members as $new_chat_member) {
813
                if ($new_chat_member instanceof User) {
814
                    // Insert the new chat user
815
                    self::insertUser($new_chat_member, $date, $chat);
816
                    $new_chat_members_ids[] = $new_chat_member->getId();
817
                }
818
            }
819
            $new_chat_members_ids = implode(',', $new_chat_members_ids);
820 6
        } elseif ($left_chat_member instanceof User) {
821
            // Insert the left chat user
822
            self::insertUser($left_chat_member, $date, $chat);
823
            $left_chat_member_id = $left_chat_member->getId();
824
        }
825
826
        try {
827 6
            $sth = self::$pdo->prepare('
828 6
                INSERT IGNORE INTO `' . TB_MESSAGE . '`
829
                (
830
                    `id`, `user_id`, `chat_id`, `date`, `forward_from`, `forward_from_chat`, `forward_from_message_id`,
831
                    `forward_date`, `reply_to_chat`, `reply_to_message`, `media_group_id`, `text`, `entities`, `audio`, `document`,
832
                    `game`, `photo`, `sticker`, `video`, `voice`, `video_note`, `caption`, `contact`,
833
                    `location`, `venue`, `new_chat_members`, `left_chat_member`,
834
                    `new_chat_title`,`new_chat_photo`, `delete_chat_photo`, `group_chat_created`,
835
                    `supergroup_chat_created`, `channel_chat_created`,
836
                    `migrate_from_chat_id`, `migrate_to_chat_id`, `pinned_message`, `connected_website`
837
                ) VALUES (
838
                    :message_id, :user_id, :chat_id, :date, :forward_from, :forward_from_chat, :forward_from_message_id,
839
                    :forward_date, :reply_to_chat, :reply_to_message, :media_group_id, :text, :entities, :audio, :document,
840
                    :game, :photo, :sticker, :video, :voice, :video_note, :caption, :contact,
841
                    :location, :venue, :new_chat_members, :left_chat_member,
842
                    :new_chat_title, :new_chat_photo, :delete_chat_photo, :group_chat_created,
843
                    :supergroup_chat_created, :channel_chat_created,
844
                    :migrate_from_chat_id, :migrate_to_chat_id, :pinned_message, :connected_website
845
                )
846
            ');
847
848 6
            $user_id = null;
849 6
            if ($user instanceof User) {
850 6
                $user_id = $user->getId();
851
            }
852 6
            $chat_id = $chat->getId();
853
854 6
            $reply_to_message    = $message->getReplyToMessage();
855 6
            $reply_to_message_id = null;
856 6
            if ($reply_to_message instanceof ReplyToMessage) {
857
                $reply_to_message_id = $reply_to_message->getMessageId();
858
                // please notice that, as explained in the documentation, reply_to_message don't contain other
859
                // reply_to_message field so recursion deep is 1
860
                self::insertMessageRequest($reply_to_message);
861
            }
862
863 6
            $sth->bindValue(':message_id', $message->getMessageId());
864 6
            $sth->bindValue(':chat_id', $chat_id);
865 6
            $sth->bindValue(':user_id', $user_id);
866 6
            $sth->bindValue(':date', $date);
867 6
            $sth->bindValue(':forward_from', $forward_from);
868 6
            $sth->bindValue(':forward_from_chat', $forward_from_chat);
869 6
            $sth->bindValue(':forward_from_message_id', $message->getForwardFromMessageId());
870 6
            $sth->bindValue(':forward_date', $forward_date);
871
872 6
            $reply_to_chat_id = null;
873 6
            if ($reply_to_message_id !== null) {
874
                $reply_to_chat_id = $chat_id;
875
            }
876 6
            $sth->bindValue(':reply_to_chat', $reply_to_chat_id);
877 6
            $sth->bindValue(':reply_to_message', $reply_to_message_id);
878
879 6
            $sth->bindValue(':media_group_id', $message->getMediaGroupId());
880 6
            $sth->bindValue(':text', $message->getText());
881 6
            $sth->bindValue(':entities', $t = self::entitiesArrayToJson($message->getEntities(), null));
882 6
            $sth->bindValue(':audio', $message->getAudio());
883 6
            $sth->bindValue(':document', $message->getDocument());
884 6
            $sth->bindValue(':game', $message->getGame());
885 6
            $sth->bindValue(':photo', $t = self::entitiesArrayToJson($message->getPhoto(), null));
886 6
            $sth->bindValue(':sticker', $message->getSticker());
887 6
            $sth->bindValue(':video', $message->getVideo());
888 6
            $sth->bindValue(':voice', $message->getVoice());
889 6
            $sth->bindValue(':video_note', $message->getVideoNote());
890 6
            $sth->bindValue(':caption', $message->getCaption());
891 6
            $sth->bindValue(':contact', $message->getContact());
892 6
            $sth->bindValue(':location', $message->getLocation());
893 6
            $sth->bindValue(':venue', $message->getVenue());
894 6
            $sth->bindValue(':new_chat_members', $new_chat_members_ids);
895 6
            $sth->bindValue(':left_chat_member', $left_chat_member_id);
896 6
            $sth->bindValue(':new_chat_title', $message->getNewChatTitle());
897 6
            $sth->bindValue(':new_chat_photo', $t = self::entitiesArrayToJson($message->getNewChatPhoto(), null));
898 6
            $sth->bindValue(':delete_chat_photo', $message->getDeleteChatPhoto());
899 6
            $sth->bindValue(':group_chat_created', $message->getGroupChatCreated());
900 6
            $sth->bindValue(':supergroup_chat_created', $message->getSupergroupChatCreated());
901 6
            $sth->bindValue(':channel_chat_created', $message->getChannelChatCreated());
902 6
            $sth->bindValue(':migrate_from_chat_id', $message->getMigrateFromChatId());
903 6
            $sth->bindValue(':migrate_to_chat_id', $message->getMigrateToChatId());
904 6
            $sth->bindValue(':pinned_message', $message->getPinnedMessage());
905 6
            $sth->bindValue(':connected_website', $message->getConnectedWebsite());
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