Completed
Push — feature/refactor-app-design ( b18d21...84977e )
by Avtandil
02:45
created

DB::insertUser()   B

Complexity

Conditions 6
Paths 22

Size

Total Lines 57
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 6.256

Importance

Changes 0
Metric Value
dl 0
loc 57
ccs 21
cts 26
cp 0.8077
rs 8.7433
c 0
b 0
f 0
cc 6
eloc 28
nc 22
nop 3
crap 6.256

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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