Completed
Push — develop ( f7cac5...56298a )
by Armando
02:39
created

DB::getTimestamp()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 2
eloc 2
nc 1
nop 1
crap 2
1
<?php
2
/**
3
 * This file is part of the TelegramBot package.
4
 *
5
 * (c) Avtandil Kikabidze aka LONGMAN <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 * Written by Marco Boretto <[email protected]>
10
 */
11
12
namespace Longman\TelegramBot;
13
14
use Exception;
15
use Longman\TelegramBot\Entities\CallbackQuery;
16
use Longman\TelegramBot\Entities\Chat;
17
use Longman\TelegramBot\Entities\ChosenInlineResult;
18
use Longman\TelegramBot\Entities\InlineQuery;
19
use Longman\TelegramBot\Entities\Message;
20
use Longman\TelegramBot\Entities\ReplyToMessage;
21
use Longman\TelegramBot\Entities\Update;
22
use Longman\TelegramBot\Entities\User;
23
use Longman\TelegramBot\Exception\TelegramException;
24
use PDO;
25
use PDOException;
26
27
class DB
28
{
29
    /**
30
     * MySQL credentials
31
     *
32
     * @var array
33
     */
34
    static protected $mysql_credentials = [];
35
36
    /**
37
     * PDO object
38
     *
39
     * @var PDO
40
     */
41
    static protected $pdo;
42
43
    /**
44
     * Table prefix
45
     *
46
     * @var string
47
     */
48
    static protected $table_prefix;
49
50
    /**
51
     * Telegram class object
52
     *
53
     * @var Telegram
54
     */
55
    static protected $telegram;
56
57
    /**
58
     * Initialize
59
     *
60
     * @param array    $credentials  Database connection details
61
     * @param Telegram $telegram     Telegram object to connect with this object
62
     * @param string   $table_prefix Table prefix
63
     * @param string   $encoding     Database character encoding
64
     *
65
     * @return PDO PDO database object
66
     * @throws TelegramException
67
     */
68 9
    public static function initialize(
69
        array $credentials,
70
        Telegram $telegram,
71
        $table_prefix = null,
72
        $encoding = 'utf8mb4'
73
    ) {
74 9
        if (empty($credentials)) {
75
            throw new TelegramException('MySQL credentials not provided!');
76
        }
77
78 9
        $dsn     = 'mysql:host=' . $credentials['host'] . ';dbname=' . $credentials['database'];
79 9
        $options = [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES ' . $encoding];
80
        try {
81 9
            $pdo = new PDO($dsn, $credentials['user'], $credentials['password'], $options);
82 9
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
83
        } catch (PDOException $e) {
84
            throw new TelegramException($e->getMessage());
85
        }
86
87 9
        self::$pdo               = $pdo;
88 9
        self::$telegram          = $telegram;
89 9
        self::$mysql_credentials = $credentials;
90 9
        self::$table_prefix      = $table_prefix;
91
92 9
        self::defineTables();
93
94 9
        return self::$pdo;
95
    }
96
97
    /**
98
     * External Initialize
99
     *
100
     * Let you use the class with an external already existing Pdo Mysql connection.
101
     *
102
     * @param PDO      $external_pdo_connection PDO database object
103
     * @param Telegram $telegram                Telegram object to connect with this object
104
     * @param string   $table_prefix            Table prefix
105
     *
106
     * @return PDO PDO database object
107
     * @throws TelegramException
108
     */
109
    public static function externalInitialize(
110
        $external_pdo_connection,
111
        Telegram $telegram,
112
        $table_prefix = null
113
    ) {
114
        if ($external_pdo_connection === null) {
115
            throw new TelegramException('MySQL external connection not provided!');
116
        }
117
118
        self::$pdo               = $external_pdo_connection;
119
        self::$telegram          = $telegram;
120
        self::$mysql_credentials = [];
121
        self::$table_prefix      = $table_prefix;
122
123
        self::defineTables();
124
125
        return self::$pdo;
126
    }
127
128
    /**
129
     * Define all the tables with the proper prefix
130
     */
131 9
    protected static function defineTables()
132
    {
133
        $tables = [
134 9
            'callback_query',
135
            'chat',
136
            'chosen_inline_result',
137
            'edited_message',
138
            'inline_query',
139
            'message',
140
            'request_limiter',
141
            'telegram_update',
142
            'user',
143
            'user_chat',
144
        ];
145 9
        foreach ($tables as $table) {
146 9
            $table_name = 'TB_' . strtoupper($table);
147 9
            if (!defined($table_name)) {
148 9
                define($table_name, self::$table_prefix . $table);
149
            }
150
        }
151 9
    }
152
153
    /**
154
     * Check if database connection has been created
155
     *
156
     * @return bool
157
     */
158 9
    public static function isDbConnected()
159
    {
160 9
        return self::$pdo !== null;
161
    }
162
163
    /**
164
     * Get the PDO object of the connected database
165
     *
166
     * @return PDO
167
     */
168
    public static function getPdo()
169
    {
170
        return self::$pdo;
171
    }
172
173
    /**
174
     * Fetch update(s) from DB
175
     *
176
     * @param int    $limit Limit the number of updates to fetch
177
     * @param string $id    Check for unique update id
178
     *
179
     * @return array|bool Fetched data or false if not connected
180
     * @throws TelegramException
181
     */
182
    public static function selectTelegramUpdate($limit = null, $id = null)
183
    {
184
        if (!self::isDbConnected()) {
185
            return false;
186
        }
187
188
        try {
189
            $sql = '
190
                SELECT `id`
191
                FROM `' . TB_TELEGRAM_UPDATE . '`
192
            ';
193
194
            if ($id !== null) {
195
                $sql .= ' WHERE `id` = :id';
196
            } else {
197
                $sql .= ' ORDER BY `id` DESC';
198
            }
199
200
            if ($limit !== null) {
201
                $sql .= ' LIMIT :limit';
202
            }
203
204
            $sth = self::$pdo->prepare($sql);
205
206
            if ($limit !== null) {
207
                $sth->bindValue(':limit', $limit, PDO::PARAM_INT);
208
            }
209
            if ($id !== null) {
210
                $sth->bindValue(':id', $id);
211
            }
212
213
            $sth->execute();
214
215
            return $sth->fetchAll(PDO::FETCH_ASSOC);
216
        } catch (PDOException $e) {
217
            throw new TelegramException($e->getMessage());
218
        }
219
    }
220
221
    /**
222
     * Fetch message(s) from DB
223
     *
224
     * @param int $limit Limit the number of messages to fetch
225
     *
226
     * @return array|bool Fetched data or false if not connected
227
     * @throws TelegramException
228
     */
229
    public static function selectMessages($limit = null)
230
    {
231
        if (!self::isDbConnected()) {
232
            return false;
233
        }
234
235
        try {
236
            $sql = '
237
                SELECT *
238
                FROM `' . TB_MESSAGE . '`
239
                ORDER BY `id` DESC
240
            ';
241
242
            if ($limit !== null) {
243
                $sql .= ' LIMIT :limit';
244
            }
245
246
            $sth = self::$pdo->prepare($sql);
247
248
            if ($limit !== null) {
249
                $sth->bindValue(':limit', $limit, PDO::PARAM_INT);
250
            }
251
252
            $sth->execute();
253
254
            return $sth->fetchAll(PDO::FETCH_ASSOC);
255
        } catch (PDOException $e) {
256
            throw new TelegramException($e->getMessage());
257
        }
258
    }
259
260
    /**
261
     * Convert from unix timestamp to timestamp
262
     *
263
     * @param int $time Unix timestamp (if empty, current timestamp is used)
264
     *
265
     * @return string
266
     */
267 7
    protected static function getTimestamp($time = null)
268
    {
269 7
        return date('Y-m-d H:i:s', $time ?: time());
270
    }
271
272
    /**
273
     * Convert array of Entity items to a JSON array
274
     *
275
     * @todo Find a better way, as json_* functions are very heavy
276
     *
277
     * @param array|null $entities
278
     * @param mixed      $default
279
     *
280
     * @return mixed
281
     */
282 6
    public static function entitiesArrayToJson($entities, $default = null)
283
    {
284 6
        if (!is_array($entities)) {
285 6
            return $default;
286
        }
287
288
        // Convert each Entity item into an object based on its JSON reflection
289
        $json_entities = array_map(function ($entity) {
290
            return json_decode($entity, true);
291
        }, $entities);
292
293
        return json_encode($json_entities);
294
    }
295
296
    /**
297
     * Insert entry to telegram_update table
298
     *
299
     * @todo Add missing values! See https://core.telegram.org/bots/api#update
300
     *
301
     * @param string $id
302
     * @param string $chat_id
303
     * @param string $message_id
304
     * @param string $inline_query_id
305
     * @param string $chosen_inline_result_id
306
     * @param string $callback_query_id
307
     * @param string $edited_message_id
308
     *
309
     * @return bool If the insert was successful
310
     * @throws TelegramException
311
     */
312
    public static function insertTelegramUpdate(
313
        $id,
314
        $chat_id = null,
315
        $message_id = null,
316
        $inline_query_id = null,
317
        $chosen_inline_result_id = null,
318
        $callback_query_id = null,
319
        $edited_message_id = null
320
    ) {
321
        if ($message_id === null && $inline_query_id === null && $chosen_inline_result_id === null && $callback_query_id === null && $edited_message_id === null) {
322
            throw new TelegramException('message_id, inline_query_id, chosen_inline_result_id, callback_query_id, edited_message_id are all null');
323
        }
324
325
        if (!self::isDbConnected()) {
326
            return false;
327
        }
328
329
        try {
330
            $sth = self::$pdo->prepare('
331
                INSERT IGNORE INTO `' . TB_TELEGRAM_UPDATE . '`
332
                (`id`, `chat_id`, `message_id`, `inline_query_id`, `chosen_inline_result_id`, `callback_query_id`, `edited_message_id`)
333
                VALUES
334
                (:id, :chat_id, :message_id, :inline_query_id, :chosen_inline_result_id, :callback_query_id, :edited_message_id)
335
            ');
336
337
            $sth->bindValue(':id', $id);
338
            $sth->bindValue(':chat_id', $chat_id);
339
            $sth->bindValue(':message_id', $message_id);
340
            $sth->bindValue(':edited_message_id', $edited_message_id);
341
            $sth->bindValue(':inline_query_id', $inline_query_id);
342
            $sth->bindValue(':chosen_inline_result_id', $chosen_inline_result_id);
343
            $sth->bindValue(':callback_query_id', $callback_query_id);
344
345
            return $sth->execute();
346
        } catch (PDOException $e) {
347
            throw new TelegramException($e->getMessage());
348
        }
349
    }
350
351
    /**
352
     * Insert users and save their connection to chats
353
     *
354
     * @param User   $user
355
     * @param string $date
356
     * @param Chat   $chat
357
     *
358
     * @return bool If the insert was successful
359
     * @throws TelegramException
360
     */
361 6
    public static function insertUser(User $user, $date = null, Chat $chat = null)
362
    {
363 6
        if (!self::isDbConnected()) {
364
            return false;
365
        }
366
367
        try {
368 6
            $sth = self::$pdo->prepare('
369 6
                INSERT INTO `' . TB_USER . '`
370
                (`id`, `is_bot`, `username`, `first_name`, `last_name`, `language_code`, `created_at`, `updated_at`)
371
                VALUES
372
                (:id, :is_bot, :username, :first_name, :last_name, :language_code, :created_at, :updated_at)
373
                ON DUPLICATE KEY UPDATE
374
                    `is_bot`         = VALUES(`is_bot`),
375
                    `username`       = VALUES(`username`),
376
                    `first_name`     = VALUES(`first_name`),
377
                    `last_name`      = VALUES(`last_name`),
378
                    `language_code`  = VALUES(`language_code`),
379
                    `updated_at`     = VALUES(`updated_at`)
380
            ');
381
382 6
            $sth->bindValue(':id', $user->getId());
383 6
            $sth->bindValue(':is_bot', $user->getIsBot(), PDO::PARAM_INT);
384 6
            $sth->bindValue(':username', $user->getUsername());
385 6
            $sth->bindValue(':first_name', $user->getFirstName());
386 6
            $sth->bindValue(':last_name', $user->getLastName());
387 6
            $sth->bindValue(':language_code', $user->getLanguageCode());
388 6
            $date = $date ?: self::getTimestamp();
389 6
            $sth->bindValue(':created_at', $date);
390 6
            $sth->bindValue(':updated_at', $date);
391
392 6
            $status = $sth->execute();
393
        } catch (PDOException $e) {
394
            throw new TelegramException($e->getMessage());
395
        }
396
397
        // Also insert the relationship to the chat into the user_chat table
398 6
        if ($chat instanceof Chat) {
399
            try {
400 6
                $sth = self::$pdo->prepare('
401 6
                    INSERT IGNORE INTO `' . TB_USER_CHAT . '`
402
                    (`user_id`, `chat_id`)
403
                    VALUES
404
                    (:user_id, :chat_id)
405
                ');
406
407 6
                $sth->bindValue(':user_id', $user->getId());
408 6
                $sth->bindValue(':chat_id', $chat->getId());
409
410 6
                $status = $sth->execute();
411
            } catch (PDOException $e) {
412
                throw new TelegramException($e->getMessage());
413
            }
414
        }
415
416 6
        return $status;
417
    }
418
419
    /**
420
     * Insert chat
421
     *
422
     * @param Chat   $chat
423
     * @param string $date
424
     * @param string $migrate_to_chat_id
425
     *
426
     * @return bool If the insert was successful
427
     * @throws TelegramException
428
     */
429 6
    public static function insertChat(Chat $chat, $date = null, $migrate_to_chat_id = null)
430
    {
431 6
        if (!self::isDbConnected()) {
432
            return false;
433
        }
434
435
        try {
436 6
            $sth = self::$pdo->prepare('
437 6
                INSERT IGNORE INTO `' . TB_CHAT . '`
438
                (`id`, `type`, `title`, `username`, `all_members_are_administrators`, `created_at` ,`updated_at`, `old_id`)
439
                VALUES
440
                (:id, :type, :title, :username, :all_members_are_administrators, :created_at, :updated_at, :old_id)
441
                ON DUPLICATE KEY UPDATE
442
                    `type`                           = VALUES(`type`),
443
                    `title`                          = VALUES(`title`),
444
                    `username`                       = VALUES(`username`),
445
                    `all_members_are_administrators` = VALUES(`all_members_are_administrators`),
446
                    `updated_at`                     = VALUES(`updated_at`)
447
            ');
448
449 6
            $chat_id   = $chat->getId();
450 6
            $chat_type = $chat->getType();
451
452 6
            if ($migrate_to_chat_id !== null) {
453
                $chat_type = 'supergroup';
454
455
                $sth->bindValue(':id', $migrate_to_chat_id);
456
                $sth->bindValue(':old_id', $chat_id);
457
            } else {
458 6
                $sth->bindValue(':id', $chat_id);
459 6
                $sth->bindValue(':old_id', $migrate_to_chat_id);
460
            }
461
462 6
            $sth->bindValue(':type', $chat_type);
463 6
            $sth->bindValue(':title', $chat->getTitle());
464 6
            $sth->bindValue(':username', $chat->getUsername());
465 6
            $sth->bindValue(':all_members_are_administrators', $chat->getAllMembersAreAdministrators(), PDO::PARAM_INT);
466 6
            $date = $date ?: self::getTimestamp();
467 6
            $sth->bindValue(':created_at', $date);
468 6
            $sth->bindValue(':updated_at', $date);
469
470 6
            return $sth->execute();
471
        } catch (PDOException $e) {
472
            throw new TelegramException($e->getMessage());
473
        }
474
    }
475
476
    /**
477
     * Insert request into database
478
     *
479
     * @todo self::$pdo->lastInsertId() - unsafe usage if expected previous insert fails?
480
     *
481
     * @param Update $update
482
     *
483
     * @return bool
484
     * @throws TelegramException
485
     */
486
    public static function insertRequest(Update $update)
487
    {
488
        if (!self::isDbConnected()) {
489
            return false;
490
        }
491
492
        $update_id   = $update->getUpdateId();
493
        $update_type = $update->getUpdateType();
494
495
        // @todo Make this simpler: if ($message = $update->getMessage()) ...
496
        if ($update_type === 'message') {
497
            $message = $update->getMessage();
498
499 View Code Duplication
            if (self::insertMessageRequest($message)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
656
    {
657
        if (!self::isDbConnected()) {
658
            return false;
659
        }
660
661
        try {
662
            $sth = self::$pdo->prepare('
663
                INSERT INTO `' . TB_CHOSEN_INLINE_RESULT . '`
664
                (`result_id`, `user_id`, `location`, `inline_message_id`, `query`, `created_at`)
665
                VALUES
666
                (:result_id, :user_id, :location, :inline_message_id, :query, :created_at)
667
            ');
668
669
            $date    = self::getTimestamp();
670
            $user_id = null;
671
672
            $user = $chosen_inline_result->getFrom();
673
            if ($user instanceof User) {
674
                $user_id = $user->getId();
675
                self::insertUser($user, $date);
676
            }
677
678
            $sth->bindValue(':result_id', $chosen_inline_result->getResultId());
679
            $sth->bindValue(':user_id', $user_id);
680
            $sth->bindValue(':location', $chosen_inline_result->getLocation());
681
            $sth->bindValue(':inline_message_id', $chosen_inline_result->getInlineMessageId());
682
            $sth->bindValue(':query', $chosen_inline_result->getQuery());
683
            $sth->bindValue(':created_at', $date);
684
685
            return $sth->execute();
686
        } catch (PDOException $e) {
687
            throw new TelegramException($e->getMessage());
688
        }
689
    }
690
691
    /**
692
     * Insert callback query request into database
693
     *
694
     * @param CallbackQuery $callback_query
695
     *
696
     * @return bool If the insert was successful
697
     * @throws TelegramException
698
     */
699
    public static function insertCallbackQueryRequest(CallbackQuery $callback_query)
700
    {
701
        if (!self::isDbConnected()) {
702
            return false;
703
        }
704
705
        try {
706
            $sth = self::$pdo->prepare('
707
                INSERT IGNORE INTO `' . TB_CALLBACK_QUERY . '`
708
                (`id`, `user_id`, `chat_id`, `message_id`, `inline_message_id`, `data`, `created_at`)
709
                VALUES
710
                (:id, :user_id, :chat_id, :message_id, :inline_message_id, :data, :created_at)
711
            ');
712
713
            $date    = self::getTimestamp();
714
            $user_id = null;
715
716
            $user = $callback_query->getFrom();
717
            if ($user instanceof User) {
718
                $user_id = $user->getId();
719
                self::insertUser($user, $date);
720
            }
721
722
            $message    = $callback_query->getMessage();
723
            $chat_id    = null;
724
            $message_id = null;
725
            if ($message instanceof Message) {
726
                $chat_id    = $message->getChat()->getId();
727
                $message_id = $message->getMessageId();
728
729
                $is_message = self::$pdo->query('
730
                    SELECT *
731
                    FROM `' . TB_MESSAGE . '`
732
                    WHERE `id` = ' . $message_id . '
733
                      AND `chat_id` = ' . $chat_id . '
734
                    LIMIT 1
735
                ')->rowCount();
736
737
                if ($is_message) {
738
                    self::insertEditedMessageRequest($message);
739
                } else {
740
                    self::insertMessageRequest($message);
741
                }
742
            }
743
744
            $sth->bindValue(':id', $callback_query->getId());
745
            $sth->bindValue(':user_id', $user_id);
746
            $sth->bindValue(':chat_id', $chat_id);
747
            $sth->bindValue(':message_id', $message_id);
748
            $sth->bindValue(':inline_message_id', $callback_query->getInlineMessageId());
749
            $sth->bindValue(':data', $callback_query->getData());
750
            $sth->bindValue(':created_at', $date);
751
752
            return $sth->execute();
753
        } catch (PDOException $e) {
754
            throw new TelegramException($e->getMessage());
755
        }
756
    }
757
758
    /**
759
     * Insert Message request in db
760
     *
761
     * @todo Complete with new fields: https://core.telegram.org/bots/api#message
762
     *
763
     * @param Message $message
764
     *
765
     * @return bool If the insert was successful
766
     * @throws TelegramException
767
     */
768 6
    public static function insertMessageRequest(Message $message)
769
    {
770 6
        if (!self::isDbConnected()) {
771
            return false;
772
        }
773
774 6
        $date = self::getTimestamp($message->getDate());
775
776
        // Insert chat, update chat id in case it migrated
777 6
        $chat = $message->getChat();
778 6
        self::insertChat($chat, $date, $message->getMigrateToChatId());
779
780
        // Insert user and the relation with the chat
781 6
        $user = $message->getFrom();
782 6
        if ($user instanceof User) {
783 6
            self::insertUser($user, $date, $chat);
784
        }
785
786
        // Insert the forwarded message user in users table
787 6
        $forward_date = null;
788 6
        $forward_from = $message->getForwardFrom();
789 6
        if ($forward_from instanceof User) {
790
            self::insertUser($forward_from, $forward_date);
791
            $forward_from = $forward_from->getId();
792
            $forward_date = self::getTimestamp($message->getForwardDate());
793
        }
794 6
        $forward_from_chat = $message->getForwardFromChat();
795 6
        if ($forward_from_chat instanceof Chat) {
796
            self::insertChat($forward_from_chat, $forward_date);
797
            $forward_from_chat = $forward_from_chat->getId();
798
            $forward_date      = self::getTimestamp($message->getForwardDate());
799
        }
800
801
        // New and left chat member
802 6
        $new_chat_members_ids = null;
803 6
        $left_chat_member_id  = null;
804
805 6
        $new_chat_members = $message->getNewChatMembers();
806 6
        $left_chat_member = $message->getLeftChatMember();
807 6
        if (!empty($new_chat_members)) {
808
            foreach ($new_chat_members as $new_chat_member) {
809
                if ($new_chat_member instanceof User) {
810
                    // Insert the new chat user
811
                    self::insertUser($new_chat_member, $date, $chat);
812
                    $new_chat_members_ids[] = $new_chat_member->getId();
813
                }
814
            }
815
            $new_chat_members_ids = implode(',', $new_chat_members_ids);
816 6
        } elseif ($left_chat_member instanceof User) {
817
            // Insert the left chat user
818
            self::insertUser($left_chat_member, $date, $chat);
819
            $left_chat_member_id = $left_chat_member->getId();
820
        }
821
822
        try {
823 6
            $sth = self::$pdo->prepare('
824 6
                INSERT IGNORE INTO `' . TB_MESSAGE . '`
825
                (
826
                    `id`, `user_id`, `chat_id`, `date`, `forward_from`, `forward_from_chat`, `forward_from_message_id`,
827
                    `forward_date`, `reply_to_chat`, `reply_to_message`, `media_group_id`, `text`, `entities`, `audio`, `document`,
828
                    `photo`, `sticker`, `video`, `voice`, `video_note`, `caption`, `contact`,
829
                    `location`, `venue`, `new_chat_members`, `left_chat_member`,
830
                    `new_chat_title`,`new_chat_photo`, `delete_chat_photo`, `group_chat_created`,
831
                    `supergroup_chat_created`, `channel_chat_created`,
832
                    `migrate_from_chat_id`, `migrate_to_chat_id`, `pinned_message`
833
                ) VALUES (
834
                    :message_id, :user_id, :chat_id, :date, :forward_from, :forward_from_chat, :forward_from_message_id,
835
                    :forward_date, :reply_to_chat, :reply_to_message, :media_group_id, :text, :entities, :audio, :document,
836
                    :photo, :sticker, :video, :voice, :video_note, :caption, :contact,
837
                    :location, :venue, :new_chat_members, :left_chat_member,
838
                    :new_chat_title, :new_chat_photo, :delete_chat_photo, :group_chat_created,
839
                    :supergroup_chat_created, :channel_chat_created,
840
                    :migrate_from_chat_id, :migrate_to_chat_id, :pinned_message
841
                )
842
            ');
843
844 6
            $user_id = null;
845 6
            if ($user instanceof User) {
846 6
                $user_id = $user->getId();
847
            }
848 6
            $chat_id = $chat->getId();
849
850 6
            $reply_to_message    = $message->getReplyToMessage();
851 6
            $reply_to_message_id = null;
852 6
            if ($reply_to_message instanceof ReplyToMessage) {
853
                $reply_to_message_id = $reply_to_message->getMessageId();
854
                // please notice that, as explained in the documentation, reply_to_message don't contain other
855
                // reply_to_message field so recursion deep is 1
856
                self::insertMessageRequest($reply_to_message);
857
            }
858
859 6
            $sth->bindValue(':message_id', $message->getMessageId());
860 6
            $sth->bindValue(':chat_id', $chat_id);
861 6
            $sth->bindValue(':user_id', $user_id);
862 6
            $sth->bindValue(':date', $date);
863 6
            $sth->bindValue(':forward_from', $forward_from);
864 6
            $sth->bindValue(':forward_from_chat', $forward_from_chat);
865 6
            $sth->bindValue(':forward_from_message_id', $message->getForwardFromMessageId());
866 6
            $sth->bindValue(':forward_date', $forward_date);
867
868 6
            $reply_to_chat_id = null;
869 6
            if ($reply_to_message_id !== null) {
870
                $reply_to_chat_id = $chat_id;
871
            }
872 6
            $sth->bindValue(':reply_to_chat', $reply_to_chat_id);
873 6
            $sth->bindValue(':reply_to_message', $reply_to_message_id);
874
875 6
            $sth->bindValue(':media_group_id', $message->getMediaGroupId());
876 6
            $sth->bindValue(':text', $message->getText());
877 6
            $sth->bindValue(':entities', $t = self::entitiesArrayToJson($message->getEntities(), null));
878 6
            $sth->bindValue(':audio', $message->getAudio());
879 6
            $sth->bindValue(':document', $message->getDocument());
880 6
            $sth->bindValue(':photo', $t = self::entitiesArrayToJson($message->getPhoto(), null));
881 6
            $sth->bindValue(':sticker', $message->getSticker());
882 6
            $sth->bindValue(':video', $message->getVideo());
883 6
            $sth->bindValue(':voice', $message->getVoice());
884 6
            $sth->bindValue(':video_note', $message->getVideoNote());
885 6
            $sth->bindValue(':caption', $message->getCaption());
886 6
            $sth->bindValue(':contact', $message->getContact());
887 6
            $sth->bindValue(':location', $message->getLocation());
888 6
            $sth->bindValue(':venue', $message->getVenue());
889 6
            $sth->bindValue(':new_chat_members', $new_chat_members_ids);
890 6
            $sth->bindValue(':left_chat_member', $left_chat_member_id);
891 6
            $sth->bindValue(':new_chat_title', $message->getNewChatTitle());
892 6
            $sth->bindValue(':new_chat_photo', $t = self::entitiesArrayToJson($message->getNewChatPhoto(), null));
893 6
            $sth->bindValue(':delete_chat_photo', $message->getDeleteChatPhoto());
894 6
            $sth->bindValue(':group_chat_created', $message->getGroupChatCreated());
895 6
            $sth->bindValue(':supergroup_chat_created', $message->getSupergroupChatCreated());
896 6
            $sth->bindValue(':channel_chat_created', $message->getChannelChatCreated());
897 6
            $sth->bindValue(':migrate_from_chat_id', $message->getMigrateFromChatId());
898 6
            $sth->bindValue(':migrate_to_chat_id', $message->getMigrateToChatId());
899 6
            $sth->bindValue(':pinned_message', $message->getPinnedMessage());
900
901 6
            return $sth->execute();
902
        } catch (PDOException $e) {
903
            throw new TelegramException($e->getMessage());
904
        }
905
    }
906
907
    /**
908
     * Insert Edited Message request in db
909
     *
910
     * @param Message $edited_message
911
     *
912
     * @return bool If the insert was successful
913
     * @throws TelegramException
914
     */
915
    public static function insertEditedMessageRequest(Message $edited_message)
916
    {
917
        if (!self::isDbConnected()) {
918
            return false;
919
        }
920
921
        try {
922
            $edit_date = self::getTimestamp($edited_message->getEditDate());
923
924
            // Insert chat
925
            $chat = $edited_message->getChat();
926
            self::insertChat($chat, $edit_date);
927
928
            // Insert user and the relation with the chat
929
            $user = $edited_message->getFrom();
930
            if ($user instanceof User) {
931
                self::insertUser($user, $edit_date, $chat);
932
            }
933
934
            $sth = self::$pdo->prepare('
935
                INSERT IGNORE INTO `' . TB_EDITED_MESSAGE . '`
936
                (`chat_id`, `message_id`, `user_id`, `edit_date`, `text`, `entities`, `caption`)
937
                VALUES
938
                (:chat_id, :message_id, :user_id, :edit_date, :text, :entities, :caption)
939
            ');
940
941
            $user_id = null;
942
            if ($user instanceof User) {
943
                $user_id = $user->getId();
944
            }
945
946
            $sth->bindValue(':chat_id', $chat->getId());
947
            $sth->bindValue(':message_id', $edited_message->getMessageId());
948
            $sth->bindValue(':user_id', $user_id);
949
            $sth->bindValue(':edit_date', $edit_date);
950
            $sth->bindValue(':text', $edited_message->getText());
951
            $sth->bindValue(':entities', self::entitiesArrayToJson($edited_message->getEntities(), null));
952
            $sth->bindValue(':caption', $edited_message->getCaption());
953
954
            return $sth->execute();
955
        } catch (PDOException $e) {
956
            throw new TelegramException($e->getMessage());
957
        }
958
    }
959
960
    /**
961
     * Select Groups, Supergroups, Channels and/or single user Chats (also by ID or text)
962
     *
963
     * @param $select_chats_params
964
     *
965
     * @return array|bool
966
     * @throws TelegramException
967
     */
968
    public static function selectChats($select_chats_params)
969
    {
970
        if (!self::isDbConnected()) {
971
            return false;
972
        }
973
974
        // Set defaults for omitted values.
975
        $select = array_merge([
976
            'groups'      => true,
977
            'supergroups' => true,
978
            'channels'    => true,
979
            'users'       => true,
980
            'date_from'   => null,
981
            'date_to'     => null,
982
            'chat_id'     => null,
983
            'text'        => null,
984
        ], $select_chats_params);
985
986
        if (!$select['groups'] && !$select['users'] && !$select['supergroups'] && !$select['channels']) {
987
            return false;
988
        }
989
990
        try {
991
            $query = '
992
                SELECT * ,
993
                ' . TB_CHAT . '.`id` AS `chat_id`,
994
                ' . TB_CHAT . '.`username` AS `chat_username`,
995
                ' . TB_CHAT . '.`created_at` AS `chat_created_at`,
996
                ' . TB_CHAT . '.`updated_at` AS `chat_updated_at`
997
            ';
998
            if ($select['users']) {
999
                $query .= '
1000
                    , ' . TB_USER . '.`id` AS `user_id`
1001
                    FROM `' . TB_CHAT . '`
1002
                    LEFT JOIN `' . TB_USER . '`
1003
                    ON ' . TB_CHAT . '.`id`=' . TB_USER . '.`id`
1004
                ';
1005
            } else {
1006
                $query .= 'FROM `' . TB_CHAT . '`';
1007
            }
1008
1009
            // Building parts of query
1010
            $where  = [];
1011
            $tokens = [];
1012
1013
            if (!$select['groups'] || !$select['users'] || !$select['supergroups'] || !$select['channels']) {
1014
                $chat_or_user = [];
1015
1016
                $select['groups'] && $chat_or_user[] = TB_CHAT . '.`type` = "group"';
1017
                $select['supergroups'] && $chat_or_user[] = TB_CHAT . '.`type` = "supergroup"';
1018
                $select['channels'] && $chat_or_user[] = TB_CHAT . '.`type` = "channel"';
1019
                $select['users'] && $chat_or_user[] = TB_CHAT . '.`type` = "private"';
1020
1021
                $where[] = '(' . implode(' OR ', $chat_or_user) . ')';
1022
            }
1023
1024
            if (null !== $select['date_from']) {
1025
                $where[]              = TB_CHAT . '.`updated_at` >= :date_from';
1026
                $tokens[':date_from'] = $select['date_from'];
1027
            }
1028
1029
            if (null !== $select['date_to']) {
1030
                $where[]            = TB_CHAT . '.`updated_at` <= :date_to';
1031
                $tokens[':date_to'] = $select['date_to'];
1032
            }
1033
1034
            if (null !== $select['chat_id']) {
1035
                $where[]            = TB_CHAT . '.`id` = :chat_id';
1036
                $tokens[':chat_id'] = $select['chat_id'];
1037
            }
1038
1039
            if (null !== $select['text']) {
1040
                $text_like = '%' . strtolower($select['text']) . '%';
1041
                if ($select['users']) {
1042
                    $where[]          = '(
1043
                        LOWER(' . TB_CHAT . '.`title`) LIKE :text1
1044
                        OR LOWER(' . TB_USER . '.`first_name`) LIKE :text2
1045
                        OR LOWER(' . TB_USER . '.`last_name`) LIKE :text3
1046
                        OR LOWER(' . TB_USER . '.`username`) LIKE :text4
1047
                    )';
1048
                    $tokens[':text1'] = $text_like;
1049
                    $tokens[':text2'] = $text_like;
1050
                    $tokens[':text3'] = $text_like;
1051
                    $tokens[':text4'] = $text_like;
1052
                } else {
1053
                    $where[]         = 'LOWER(' . TB_CHAT . '.`title`) LIKE :text';
1054
                    $tokens[':text'] = $text_like;
1055
                }
1056
            }
1057
1058
            if (!empty($where)) {
1059
                $query .= ' WHERE ' . implode(' AND ', $where);
1060
            }
1061
1062
            $query .= ' ORDER BY ' . TB_CHAT . '.`updated_at` ASC';
1063
1064
            $sth = self::$pdo->prepare($query);
1065
            $sth->execute($tokens);
1066
1067
            return $sth->fetchAll(PDO::FETCH_ASSOC);
1068
        } catch (PDOException $e) {
1069
            throw new TelegramException($e->getMessage());
1070
        }
1071
    }
1072
1073
    /**
1074
     * Get Telegram API request count for current chat / message
1075
     *
1076
     * @param integer $chat_id
1077
     * @param string  $inline_message_id
1078
     *
1079
     * @return array|bool Array containing TOTAL and CURRENT fields or false on invalid arguments
1080
     * @throws TelegramException
1081
     */
1082
    public static function getTelegramRequestCount($chat_id = null, $inline_message_id = null)
1083
    {
1084
        if (!self::isDbConnected()) {
1085
            return false;
1086
        }
1087
1088
        try {
1089
            $sth = self::$pdo->prepare('SELECT 
1090
                (SELECT COUNT(DISTINCT `chat_id`) FROM `' . TB_REQUEST_LIMITER . '` WHERE `created_at` >= :created_at_1) AS LIMIT_PER_SEC_ALL,
1091
                (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,
1092
                (SELECT COUNT(*) FROM `' . TB_REQUEST_LIMITER . '` WHERE `created_at` >= :created_at_minute AND `chat_id` = :chat_id_2) AS LIMIT_PER_MINUTE
1093
            ');
1094
1095
            $date        = self::getTimestamp();
1096
            $date_minute = self::getTimestamp(strtotime('-1 minute'));
1097
1098
            $sth->bindValue(':chat_id_1', $chat_id);
1099
            $sth->bindValue(':chat_id_2', $chat_id);
1100
            $sth->bindValue(':inline_message_id', $inline_message_id);
1101
            $sth->bindValue(':created_at_1', $date);
1102
            $sth->bindValue(':created_at_2', $date);
1103
            $sth->bindValue(':created_at_minute', $date_minute);
1104
1105
            $sth->execute();
1106
1107
            return $sth->fetch();
1108
        } catch (Exception $e) {
1109
            throw new TelegramException($e->getMessage());
1110
        }
1111
    }
1112
1113
    /**
1114
     * Insert Telegram API request in db
1115
     *
1116
     * @param string $method
1117
     * @param array  $data
1118
     *
1119
     * @return bool If the insert was successful
1120
     * @throws TelegramException
1121
     */
1122
    public static function insertTelegramRequest($method, $data)
1123
    {
1124
        if (!self::isDbConnected()) {
1125
            return false;
1126
        }
1127
1128
        try {
1129
            $sth = self::$pdo->prepare('INSERT INTO `' . TB_REQUEST_LIMITER . '`
1130
                (`method`, `chat_id`, `inline_message_id`, `created_at`)
1131
                VALUES
1132
                (:method, :chat_id, :inline_message_id, :created_at);
1133
            ');
1134
1135
            $chat_id           = isset($data['chat_id']) ? $data['chat_id'] : null;
1136
            $inline_message_id = isset($data['inline_message_id']) ? $data['inline_message_id'] : null;
1137
1138
            $sth->bindValue(':chat_id', $chat_id);
1139
            $sth->bindValue(':inline_message_id', $inline_message_id);
1140
            $sth->bindValue(':method', $method);
1141
            $sth->bindValue(':created_at', self::getTimestamp());
1142
1143
            return $sth->execute();
1144
        } catch (Exception $e) {
1145
            throw new TelegramException($e->getMessage());
1146
        }
1147
    }
1148
1149
    /**
1150
     * Bulk update the entries of any table
1151
     *
1152
     * @param string $table
1153
     * @param array  $fields_values
1154
     * @param array  $where_fields_values
1155
     *
1156
     * @return bool
1157
     * @throws TelegramException
1158
     */
1159 3
    public static function update($table, array $fields_values, array $where_fields_values)
1160
    {
1161 3
        if (empty($fields_values) || !self::isDbConnected()) {
1162
            return false;
1163
        }
1164
1165
        try {
1166
            // Building parts of query
1167 3
            $tokens = $fields = $where = [];
1168
1169
            // Fields with values to update
1170 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...
1171 3
                $token          = ':' . count($tokens);
1172 3
                $fields[]       = "`{$field}` = {$token}";
1173 3
                $tokens[$token] = $value;
1174
            }
1175
1176
            // Where conditions
1177 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...
1178 3
                $token          = ':' . count($tokens);
1179 3
                $where[]        = "`{$field}` = {$token}";
1180 3
                $tokens[$token] = $value;
1181
            }
1182
1183 3
            $sql = 'UPDATE `' . $table . '` SET ' . implode(', ', $fields);
1184 3
            $sql .= count($where) > 0 ? ' WHERE ' . implode(' AND ', $where) : '';
1185
1186 3
            return self::$pdo->prepare($sql)->execute($tokens);
1187
        } catch (Exception $e) {
1188
            throw new TelegramException($e->getMessage());
1189
        }
1190
    }
1191
}
1192