Completed
Push — feature/refactor-app-design ( d959a3 )
by Avtandil
03:51
created

DB::getTelegramRequestCount()   B

Complexity

Conditions 3
Paths 13

Size

Total Lines 30
Code Lines 20

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