Completed
Push — develop ( 9a59c0...ec4905 )
by Armando
12s
created

DB   F

Complexity

Total Complexity 131

Size/Duplication

Total Lines 1172
Duplicated Lines 11.6 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 28.63%

Importance

Changes 0
Metric Value
wmc 131
lcom 1
cbo 9
dl 136
loc 1172
ccs 148
cts 517
cp 0.2863
rs 0.912
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A initialize() 0 32 4
A externalInitialize() 0 18 2
A defineTables() 0 21 3
A isDbConnected() 0 4 1
A getPdo() 0 4 1
B selectTelegramUpdate() 0 38 7
A selectMessages() 0 30 5
A getTimestamp() 0 4 2
A entitiesArrayToJson() 0 13 2
B insertTelegramUpdate() 0 38 8
B insertUser() 0 57 6
B insertChat() 0 46 5
D insertRequest() 56 116 16
A insertInlineQueryRequest() 35 35 4
A insertChosenInlineResultRequest() 35 35 4
B insertCallbackQueryRequest() 0 58 6
F insertMessageRequest() 0 141 13
B insertEditedMessageRequest() 0 44 5
F selectChats() 0 104 22
A getTelegramRequestCount() 0 30 3
A insertTelegramRequest() 0 26 5
B update() 10 32 7

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like DB often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DB, and based on these observations, apply Extract Interface, too.

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
                    `animation`, `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
                    :animation, :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(':animation', $message->getAnimation());
885 6
            $sth->bindValue(':game', $message->getGame());
886 6
            $sth->bindValue(':photo', $t = self::entitiesArrayToJson($message->getPhoto(), null));
887 6
            $sth->bindValue(':sticker', $message->getSticker());
888 6
            $sth->bindValue(':video', $message->getVideo());
889 6
            $sth->bindValue(':voice', $message->getVoice());
890 6
            $sth->bindValue(':video_note', $message->getVideoNote());
891 6
            $sth->bindValue(':caption', $message->getCaption());
892 6
            $sth->bindValue(':contact', $message->getContact());
893 6
            $sth->bindValue(':location', $message->getLocation());
894 6
            $sth->bindValue(':venue', $message->getVenue());
895 6
            $sth->bindValue(':new_chat_members', $new_chat_members_ids);
896 6
            $sth->bindValue(':left_chat_member', $left_chat_member_id);
897 6
            $sth->bindValue(':new_chat_title', $message->getNewChatTitle());
898 6
            $sth->bindValue(':new_chat_photo', $t = self::entitiesArrayToJson($message->getNewChatPhoto(), null));
899 6
            $sth->bindValue(':delete_chat_photo', $message->getDeleteChatPhoto());
900 6
            $sth->bindValue(':group_chat_created', $message->getGroupChatCreated());
901 6
            $sth->bindValue(':supergroup_chat_created', $message->getSupergroupChatCreated());
902 6
            $sth->bindValue(':channel_chat_created', $message->getChannelChatCreated());
903 6
            $sth->bindValue(':migrate_from_chat_id', $message->getMigrateFromChatId());
904 6
            $sth->bindValue(':migrate_to_chat_id', $message->getMigrateToChatId());
905 6
            $sth->bindValue(':pinned_message', $message->getPinnedMessage());
906 6
            $sth->bindValue(':connected_website', $message->getConnectedWebsite());
907
908 6
            return $sth->execute();
909
        } catch (PDOException $e) {
910
            throw new TelegramException($e->getMessage());
911
        }
912
    }
913
914
    /**
915
     * Insert Edited Message request in db
916
     *
917
     * @param Message $edited_message
918
     *
919
     * @return bool If the insert was successful
920
     * @throws TelegramException
921
     */
922
    public static function insertEditedMessageRequest(Message $edited_message)
923
    {
924
        if (!self::isDbConnected()) {
925
            return false;
926
        }
927
928
        try {
929
            $edit_date = self::getTimestamp($edited_message->getEditDate());
930
931
            // Insert chat
932
            $chat = $edited_message->getChat();
933
            self::insertChat($chat, $edit_date);
934
935
            // Insert user and the relation with the chat
936
            $user = $edited_message->getFrom();
937
            if ($user instanceof User) {
938
                self::insertUser($user, $edit_date, $chat);
939
            }
940
941
            $sth = self::$pdo->prepare('
942
                INSERT IGNORE INTO `' . TB_EDITED_MESSAGE . '`
943
                (`chat_id`, `message_id`, `user_id`, `edit_date`, `text`, `entities`, `caption`)
944
                VALUES
945
                (:chat_id, :message_id, :user_id, :edit_date, :text, :entities, :caption)
946
            ');
947
948
            $user_id = null;
949
            if ($user instanceof User) {
950
                $user_id = $user->getId();
951
            }
952
953
            $sth->bindValue(':chat_id', $chat->getId());
954
            $sth->bindValue(':message_id', $edited_message->getMessageId());
955
            $sth->bindValue(':user_id', $user_id);
956
            $sth->bindValue(':edit_date', $edit_date);
957
            $sth->bindValue(':text', $edited_message->getText());
958
            $sth->bindValue(':entities', self::entitiesArrayToJson($edited_message->getEntities(), null));
959
            $sth->bindValue(':caption', $edited_message->getCaption());
960
961
            return $sth->execute();
962
        } catch (PDOException $e) {
963
            throw new TelegramException($e->getMessage());
964
        }
965
    }
966
967
    /**
968
     * Select Groups, Supergroups, Channels and/or single user Chats (also by ID or text)
969
     *
970
     * @param $select_chats_params
971
     *
972
     * @return array|bool
973
     * @throws TelegramException
974
     */
975
    public static function selectChats($select_chats_params)
976
    {
977
        if (!self::isDbConnected()) {
978
            return false;
979
        }
980
981
        // Set defaults for omitted values.
982
        $select = array_merge([
983
            'groups'      => true,
984
            'supergroups' => true,
985
            'channels'    => true,
986
            'users'       => true,
987
            'date_from'   => null,
988
            'date_to'     => null,
989
            'chat_id'     => null,
990
            'text'        => null,
991
        ], $select_chats_params);
992
993
        if (!$select['groups'] && !$select['users'] && !$select['supergroups'] && !$select['channels']) {
994
            return false;
995
        }
996
997
        try {
998
            $query = '
999
                SELECT * ,
1000
                ' . TB_CHAT . '.`id` AS `chat_id`,
1001
                ' . TB_CHAT . '.`username` AS `chat_username`,
1002
                ' . TB_CHAT . '.`created_at` AS `chat_created_at`,
1003
                ' . TB_CHAT . '.`updated_at` AS `chat_updated_at`
1004
            ';
1005
            if ($select['users']) {
1006
                $query .= '
1007
                    , ' . TB_USER . '.`id` AS `user_id`
1008
                    FROM `' . TB_CHAT . '`
1009
                    LEFT JOIN `' . TB_USER . '`
1010
                    ON ' . TB_CHAT . '.`id`=' . TB_USER . '.`id`
1011
                ';
1012
            } else {
1013
                $query .= 'FROM `' . TB_CHAT . '`';
1014
            }
1015
1016
            // Building parts of query
1017
            $where  = [];
1018
            $tokens = [];
1019
1020
            if (!$select['groups'] || !$select['users'] || !$select['supergroups'] || !$select['channels']) {
1021
                $chat_or_user = [];
1022
1023
                $select['groups'] && $chat_or_user[] = TB_CHAT . '.`type` = "group"';
1024
                $select['supergroups'] && $chat_or_user[] = TB_CHAT . '.`type` = "supergroup"';
1025
                $select['channels'] && $chat_or_user[] = TB_CHAT . '.`type` = "channel"';
1026
                $select['users'] && $chat_or_user[] = TB_CHAT . '.`type` = "private"';
1027
1028
                $where[] = '(' . implode(' OR ', $chat_or_user) . ')';
1029
            }
1030
1031
            if (null !== $select['date_from']) {
1032
                $where[]              = TB_CHAT . '.`updated_at` >= :date_from';
1033
                $tokens[':date_from'] = $select['date_from'];
1034
            }
1035
1036
            if (null !== $select['date_to']) {
1037
                $where[]            = TB_CHAT . '.`updated_at` <= :date_to';
1038
                $tokens[':date_to'] = $select['date_to'];
1039
            }
1040
1041
            if (null !== $select['chat_id']) {
1042
                $where[]            = TB_CHAT . '.`id` = :chat_id';
1043
                $tokens[':chat_id'] = $select['chat_id'];
1044
            }
1045
1046
            if (null !== $select['text']) {
1047
                $text_like = '%' . strtolower($select['text']) . '%';
1048
                if ($select['users']) {
1049
                    $where[]          = '(
1050
                        LOWER(' . TB_CHAT . '.`title`) LIKE :text1
1051
                        OR LOWER(' . TB_USER . '.`first_name`) LIKE :text2
1052
                        OR LOWER(' . TB_USER . '.`last_name`) LIKE :text3
1053
                        OR LOWER(' . TB_USER . '.`username`) LIKE :text4
1054
                    )';
1055
                    $tokens[':text1'] = $text_like;
1056
                    $tokens[':text2'] = $text_like;
1057
                    $tokens[':text3'] = $text_like;
1058
                    $tokens[':text4'] = $text_like;
1059
                } else {
1060
                    $where[]         = 'LOWER(' . TB_CHAT . '.`title`) LIKE :text';
1061
                    $tokens[':text'] = $text_like;
1062
                }
1063
            }
1064
1065
            if (!empty($where)) {
1066
                $query .= ' WHERE ' . implode(' AND ', $where);
1067
            }
1068
1069
            $query .= ' ORDER BY ' . TB_CHAT . '.`updated_at` ASC';
1070
1071
            $sth = self::$pdo->prepare($query);
1072
            $sth->execute($tokens);
1073
1074
            return $sth->fetchAll(PDO::FETCH_ASSOC);
1075
        } catch (PDOException $e) {
1076
            throw new TelegramException($e->getMessage());
1077
        }
1078
    }
1079
1080
    /**
1081
     * Get Telegram API request count for current chat / message
1082
     *
1083
     * @param integer $chat_id
1084
     * @param string  $inline_message_id
1085
     *
1086
     * @return array|bool Array containing TOTAL and CURRENT fields or false on invalid arguments
1087
     * @throws TelegramException
1088
     */
1089
    public static function getTelegramRequestCount($chat_id = null, $inline_message_id = null)
1090
    {
1091
        if (!self::isDbConnected()) {
1092
            return false;
1093
        }
1094
1095
        try {
1096
            $sth = self::$pdo->prepare('SELECT 
1097
                (SELECT COUNT(DISTINCT `chat_id`) FROM `' . TB_REQUEST_LIMITER . '` WHERE `created_at` >= :created_at_1) AS LIMIT_PER_SEC_ALL,
1098
                (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,
1099
                (SELECT COUNT(*) FROM `' . TB_REQUEST_LIMITER . '` WHERE `created_at` >= :created_at_minute AND `chat_id` = :chat_id_2) AS LIMIT_PER_MINUTE
1100
            ');
1101
1102
            $date        = self::getTimestamp();
1103
            $date_minute = self::getTimestamp(strtotime('-1 minute'));
1104
1105
            $sth->bindValue(':chat_id_1', $chat_id);
1106
            $sth->bindValue(':chat_id_2', $chat_id);
1107
            $sth->bindValue(':inline_message_id', $inline_message_id);
1108
            $sth->bindValue(':created_at_1', $date);
1109
            $sth->bindValue(':created_at_2', $date);
1110
            $sth->bindValue(':created_at_minute', $date_minute);
1111
1112
            $sth->execute();
1113
1114
            return $sth->fetch();
1115
        } catch (Exception $e) {
1116
            throw new TelegramException($e->getMessage());
1117
        }
1118
    }
1119
1120
    /**
1121
     * Insert Telegram API request in db
1122
     *
1123
     * @param string $method
1124
     * @param array  $data
1125
     *
1126
     * @return bool If the insert was successful
1127
     * @throws TelegramException
1128
     */
1129
    public static function insertTelegramRequest($method, $data)
1130
    {
1131
        if (!self::isDbConnected()) {
1132
            return false;
1133
        }
1134
1135
        try {
1136
            $sth = self::$pdo->prepare('INSERT INTO `' . TB_REQUEST_LIMITER . '`
1137
                (`method`, `chat_id`, `inline_message_id`, `created_at`)
1138
                VALUES
1139
                (:method, :chat_id, :inline_message_id, :created_at);
1140
            ');
1141
1142
            $chat_id           = isset($data['chat_id']) ? $data['chat_id'] : null;
1143
            $inline_message_id = isset($data['inline_message_id']) ? $data['inline_message_id'] : null;
1144
1145
            $sth->bindValue(':chat_id', $chat_id);
1146
            $sth->bindValue(':inline_message_id', $inline_message_id);
1147
            $sth->bindValue(':method', $method);
1148
            $sth->bindValue(':created_at', self::getTimestamp());
1149
1150
            return $sth->execute();
1151
        } catch (Exception $e) {
1152
            throw new TelegramException($e->getMessage());
1153
        }
1154
    }
1155
1156
    /**
1157
     * Bulk update the entries of any table
1158
     *
1159
     * @param string $table
1160
     * @param array  $fields_values
1161
     * @param array  $where_fields_values
1162
     *
1163
     * @return bool
1164
     * @throws TelegramException
1165
     */
1166 3
    public static function update($table, array $fields_values, array $where_fields_values)
1167
    {
1168 3
        if (empty($fields_values) || !self::isDbConnected()) {
1169
            return false;
1170
        }
1171
1172
        try {
1173
            // Building parts of query
1174 3
            $tokens = $fields = $where = [];
1175
1176
            // Fields with values to update
1177 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...
1178 3
                $token          = ':' . count($tokens);
1179 3
                $fields[]       = "`{$field}` = {$token}";
1180 3
                $tokens[$token] = $value;
1181
            }
1182
1183
            // Where conditions
1184 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...
1185 3
                $token          = ':' . count($tokens);
1186 3
                $where[]        = "`{$field}` = {$token}";
1187 3
                $tokens[$token] = $value;
1188
            }
1189
1190 3
            $sql = 'UPDATE `' . $table . '` SET ' . implode(', ', $fields);
1191 3
            $sql .= count($where) > 0 ? ' WHERE ' . implode(' AND ', $where) : '';
1192
1193 3
            return self::$pdo->prepare($sql)->execute($tokens);
1194
        } catch (Exception $e) {
1195
            throw new TelegramException($e->getMessage());
1196
        }
1197
    }
1198
}
1199