Completed
Push — scrutinizer_fixes ( 91717c...889978 )
by Armando
03:37
created

DB   D

Complexity

Total Complexity 94

Size/Duplication

Total Lines 986
Duplicated Lines 15.11 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 33.33%

Importance

Changes 28
Bugs 3 Features 1
Metric Value
wmc 94
c 28
b 3
f 1
lcom 1
cbo 9
dl 149
loc 986
rs 4.4444
ccs 147
cts 441
cp 0.3333

18 Methods

Rating   Name   Duplication   Size   Complexity  
B initialize() 0 28 3
A externalInitialize() 0 18 2
A defineTables() 0 20 3
A isDbConnected() 0 4 1
B selectTelegramUpdate() 26 26 4
B selectMessages() 27 27 4
A getTimestamp() 0 8 2
A entitiesArrayToJson() 0 13 2
C insertTelegramUpdate() 0 38 8
B insertUser() 0 57 5
B insertChat() 0 41 4
C insertRequest() 18 67 11
B insertInlineQueryRequest() 39 39 4
B insertChosenInlineResultRequest() 39 39 4
B insertCallbackQueryRequest() 0 61 6
F insertMessageRequest() 0 149 9
A insertEditedMessageRequest() 0 48 3
F selectChats() 0 92 19

How to fix   Duplicated Code    Complexity   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

Complex Class

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

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

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

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

1
<?php
2
/**
3
 * This file is part of the TelegramBot package.
4
 *
5
 * (c) Avtandil Kikabidze aka LONGMAN <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 * Written by Marco Boretto <[email protected]>
10
 */
11
12
namespace Longman\TelegramBot;
13
14
use Longman\TelegramBot\Entities\CallbackQuery;
15
use Longman\TelegramBot\Entities\Chat;
16
use Longman\TelegramBot\Entities\ChosenInlineResult;
17
use Longman\TelegramBot\Entities\InlineQuery;
18
use Longman\TelegramBot\Entities\Message;
19
use Longman\TelegramBot\Entities\ReplyToMessage;
20
use Longman\TelegramBot\Entities\Update;
21
use Longman\TelegramBot\Entities\User;
22
use Longman\TelegramBot\Exception\TelegramException;
23
use PDO;
24
use PDOException;
25
26
class DB
27
{
28
    /**
29
     * MySQL credentials
30
     *
31
     * @var array
32
     */
33
    static protected $mysql_credentials = [];
34
35
    /**
36
     * PDO object
37
     *
38
     * @var PDO
39
     */
40
    static protected $pdo;
41
42
    /**
43
     * Table prefix
44
     *
45
     * @var string
46
     */
47
    static protected $table_prefix;
48
49
    /**
50
     * Telegram class object
51
     *
52
     * @var \Longman\TelegramBot\Telegram
53
     */
54
    static protected $telegram;
55
56
    /**
57
     * Initialize
58
     *
59
     * @param array                         $credentials  Database connection details
60
     * @param \Longman\TelegramBot\Telegram $telegram     Telegram object to connect with this object
61
     * @param string                        $table_prefix Table prefix
62
     * @param string                        $encoding     Database character encoding
63
     *
64
     * @return PDO PDO database object
65
     * @throws \Longman\TelegramBot\Exception\TelegramException
66
     */
67 9
    public static function initialize(
68
        array $credentials,
69
        Telegram $telegram,
70
        $table_prefix = null,
71
        $encoding = 'utf8mb4'
72
    ) {
73 9
        if (empty($credentials)) {
74
            throw new TelegramException('MySQL credentials not provided!');
75
        }
76
77 9
        $dsn     = 'mysql:host=' . $credentials['host'] . ';dbname=' . $credentials['database'];
78 9
        $options = [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES ' . $encoding];
79
        try {
80 9
            $pdo = new PDO($dsn, $credentials['user'], $credentials['password'], $options);
81 9
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
82
        } catch (PDOException $e) {
83
            throw new TelegramException($e->getMessage());
84
        }
85
86 9
        self::$pdo               = $pdo;
87 9
        self::$telegram          = $telegram;
88 9
        self::$mysql_credentials = $credentials;
89 9
        self::$table_prefix      = $table_prefix;
90
91 9
        self::defineTables();
92
93 9
        return self::$pdo;
94
    }
95
96
    /**
97
     * External Initialize
98
     *
99
     * Let you use the class with an external already existing Pdo Mysql connection.
100
     *
101
     * @param PDO                           $external_pdo_connection PDO database object
102
     * @param \Longman\TelegramBot\Telegram $telegram                Telegram object to connect with this object
103
     * @param string                        $table_prefix            Table prefix
104
     *
105
     * @return PDO PDO database object
106
     * @throws \Longman\TelegramBot\Exception\TelegramException
107
     */
108
    public static function externalInitialize(
109
        $external_pdo_connection,
110
        Telegram $telegram,
111
        $table_prefix = null
112
    ) {
113
        if ($external_pdo_connection === null) {
114
            throw new TelegramException('MySQL external connection not provided!');
115
        }
116
117
        self::$pdo               = $external_pdo_connection;
118
        self::$telegram          = $telegram;
119
        self::$mysql_credentials = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $mysql_credentials.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
120
        self::$table_prefix      = $table_prefix;
121
122
        self::defineTables();
123
124
        return self::$pdo;
125
    }
126
127
    /**
128
     * Define all the tables with the proper prefix
129
     */
130 9
    protected static function defineTables()
131
    {
132
        $tables = [
133 9
            'callback_query',
134
            'chat',
135
            'chosen_inline_result',
136
            'edited_message',
137
            'inline_query',
138
            'message',
139
            'telegram_update',
140
            'user',
141
            'user_chat',
142
        ];
143 9
        foreach ($tables as $table) {
144 9
            $table_name = 'TB_' . strtoupper($table);
145 9
            if (!defined($table_name)) {
146 9
                define($table_name, self::$table_prefix . $table);
147
            }
148
        }
149 9
    }
150
151
    /**
152
     * Check if database connection has been created
153
     *
154
     * @return bool
155
     */
156 9
    public static function isDbConnected()
157
    {
158 9
        return self::$pdo !== null;
159
    }
160
161
    /**
162
     * Fetch update(s) from DB
163
     *
164
     * @param int $limit Limit the number of updates to fetch
165
     *
166
     * @return array|bool Fetched data or false if not connected
167
     * @throws \Longman\TelegramBot\Exception\TelegramException
168
     */
169 View Code Duplication
    public static function selectTelegramUpdate($limit = null)
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...
170
    {
171
        if (!self::isDbConnected()) {
172
            return false;
173
        }
174
175
        try {
176
            $sql = '
177
                SELECT `id`
178
                FROM `' . TB_TELEGRAM_UPDATE . '`
179
                ORDER BY `id` DESC
180
            ';
181
182
            if ($limit !== null) {
183
                $sql .= 'LIMIT :limit';
184
            }
185
186
            $sth = self::$pdo->prepare($sql);
187
            $sth->bindParam(':limit', $limit, PDO::PARAM_INT);
188
            $sth->execute();
189
190
            return $sth->fetchAll(PDO::FETCH_ASSOC);
191
        } catch (PDOException $e) {
192
            throw new TelegramException($e->getMessage());
193
        }
194
    }
195
196
    /**
197
     * Fetch message(s) from DB
198
     *
199
     * @param int $limit Limit the number of messages to fetch
200
     *
201
     * @return array|bool Fetched data or false if not connected
202
     * @throws \Longman\TelegramBot\Exception\TelegramException
203
     */
204 View Code Duplication
    public static function selectMessages($limit = null)
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...
205
    {
206
        if (!self::isDbConnected()) {
207
            return false;
208
        }
209
210
        try {
211
            $sql = '
212
                SELECT *
213
                FROM `' . TB_MESSAGE . '`
214
                WHERE `update_id` != 0
215
                ORDER BY `message_id` DESC
216
            ';
217
218
            if ($limit !== null) {
219
                $sql .= 'LIMIT :limit';
220
            }
221
222
            $sth = self::$pdo->prepare($sql);
223
            $sth->bindParam(':limit', $limit, PDO::PARAM_INT);
224
            $sth->execute();
225
226
            return $sth->fetchAll(PDO::FETCH_ASSOC);
227
        } catch (PDOException $e) {
228
            throw new TelegramException($e->getMessage());
229
        }
230
    }
231
232
    /**
233
     * Convert from unix timestamp to timestamp
234
     *
235
     * @param int $time Unix timestamp (if null, current timestamp is used)
236
     *
237
     * @return string
238
     */
239 7
    protected static function getTimestamp($time = null)
240
    {
241 7
        if ($time === null) {
242 6
            $time = time();
243
        }
244
245 7
        return date('Y-m-d H:i:s', $time);
246
    }
247
248
    /**
249
     * Convert array of Entity items to a JSON array
250
     *
251
     * @todo Find a better way, as json_* functions are very heavy
252
     *
253
     * @param array $entities
254
     * @param mixed $default
255
     *
256
     * @return mixed
257
     */
258 6
    public static function entitiesArrayToJson($entities, $default = null)
259
    {
260 6
        if (!is_array($entities)) {
261 6
            return $default;
262
        }
263
264
        //Convert each Entity item into an object based on its JSON reflection
265
        $json_entities = array_map(function ($entity) {
266
            return json_decode($entity, true);
267
        }, $entities);
268
269
        return json_encode($json_entities);
270
    }
271
272
273
    /**
274
     * Insert entry to telegram_update table
275
     *
276
     * @param int $id
277
     * @param int $chat_id
278
     * @param int $message_id
279
     * @param int $inline_query_id
280
     * @param int $chosen_inline_result_id
281
     * @param int $callback_query_id
282
     * @param int $edited_message_id
283
     *
284
     * @return bool If the insert was successful
285
     * @throws \Longman\TelegramBot\Exception\TelegramException
286
     */
287
    public static function insertTelegramUpdate(
288
        $id,
289
        $chat_id,
290
        $message_id,
291
        $inline_query_id,
292
        $chosen_inline_result_id,
293
        $callback_query_id,
294
        $edited_message_id
295
    ) {
296
        if ($message_id === null && $inline_query_id === null && $chosen_inline_result_id === null && $callback_query_id === null && $edited_message_id === null) {
297
            throw new TelegramException('message_id, inline_query_id, chosen_inline_result_id, callback_query_id, edited_message_id are all null');
298
        }
299
300
        if (!self::isDbConnected()) {
301
            return false;
302
        }
303
304
        try {
305
            $sth = self::$pdo->prepare('
306
                INSERT IGNORE INTO `' . TB_TELEGRAM_UPDATE . '`
307
                (`id`, `chat_id`, `message_id`, `inline_query_id`, `chosen_inline_result_id`, `callback_query_id`, `edited_message_id`)
308
                VALUES
309
                (:id, :chat_id, :message_id, :inline_query_id, :chosen_inline_result_id, :callback_query_id, :edited_message_id)
310
            ');
311
312
            $sth->bindParam(':id', $id, PDO::PARAM_INT);
313
            $sth->bindParam(':chat_id', $chat_id, PDO::PARAM_INT);
314
            $sth->bindParam(':message_id', $message_id, PDO::PARAM_INT);
315
            $sth->bindParam(':inline_query_id', $inline_query_id, PDO::PARAM_INT);
316
            $sth->bindParam(':chosen_inline_result_id', $chosen_inline_result_id, PDO::PARAM_INT);
317
            $sth->bindParam(':callback_query_id', $callback_query_id, PDO::PARAM_INT);
318
            $sth->bindParam(':edited_message_id', $edited_message_id, PDO::PARAM_INT);
319
320
            return $sth->execute();
321
        } catch (PDOException $e) {
322
            throw new TelegramException($e->getMessage());
323
        }
324
    }
325
326
    /**
327
     * Insert users and save their connection to chats
328
     *
329
     * @param  \Longman\TelegramBot\Entities\User $user
330
     * @param  string                             $date
331
     * @param  \Longman\TelegramBot\Entities\Chat $chat
332
     *
333
     * @return bool If the insert was successful
334
     * @throws \Longman\TelegramBot\Exception\TelegramException
335
     */
336 6
    public static function insertUser(User $user, $date, Chat $chat = null)
337
    {
338 6
        if (!self::isDbConnected()) {
339
            return false;
340
        }
341
342 6
        $user_id    = $user->getId();
343 6
        $username   = $user->getUsername();
344 6
        $first_name = $user->getFirstName();
345 6
        $last_name  = $user->getLastName();
346
347
        try {
348 6
            $sth = self::$pdo->prepare('
349 6
                INSERT INTO `' . TB_USER . '`
350
                (`id`, `username`, `first_name`, `last_name`, `created_at`, `updated_at`)
351
                VALUES
352
                (:id, :username, :first_name, :last_name, :date, :date)
353
                ON DUPLICATE KEY UPDATE
354
                    `username`   = :username,
355
                    `first_name` = :first_name,
356
                    `last_name`  = :last_name,
357
                    `updated_at` = :date
358 6
            ');
359
360 6
            $sth->bindParam(':id', $user_id, PDO::PARAM_INT);
361 6
            $sth->bindParam(':username', $username, PDO::PARAM_STR, 255);
362 6
            $sth->bindParam(':first_name', $first_name, PDO::PARAM_STR, 255);
363 6
            $sth->bindParam(':last_name', $last_name, PDO::PARAM_STR, 255);
364 6
            $sth->bindParam(':date', $date, PDO::PARAM_STR);
365
366 6
            $status = $sth->execute();
367
        } catch (PDOException $e) {
368
            throw new TelegramException($e->getMessage());
369
        }
370
371
        //insert also the relationship to the chat into user_chat table
372 6
        if ($chat instanceof Chat) {
373 6
            $chat_id = $chat->getId();
374
            try {
375 6
                $sth = self::$pdo->prepare('
376 6
                    INSERT IGNORE INTO `' . TB_USER_CHAT . '`
377
                    (`user_id`, `chat_id`)
378
                    VALUES
379
                    (:user_id, :chat_id)
380 6
                ');
381
382 6
                $sth->bindParam(':user_id', $user_id, PDO::PARAM_INT);
383 6
                $sth->bindParam(':chat_id', $chat_id, PDO::PARAM_INT);
384
385 6
                $status = $sth->execute();
386
            } catch (PDOException $e) {
387
                throw new TelegramException($e->getMessage());
388
            }
389
        }
390
391 6
        return $status;
392
    }
393
394
    /**
395
     * Insert chat
396
     *
397
     * @param  \Longman\TelegramBot\Entities\Chat $chat
398
     * @param  string                             $date
399
     * @param  int                                $migrate_to_chat_id
400
     *
401
     * @return bool If the insert was successful
402
     * @throws \Longman\TelegramBot\Exception\TelegramException
403
     */
404 6
    public static function insertChat(Chat $chat, $date, $migrate_to_chat_id = null)
405
    {
406 6
        if (!self::isDbConnected()) {
407
            return false;
408
        }
409
410 6
        $chat_id    = $chat->getId();
411 6
        $chat_title = $chat->getTitle();
412 6
        $chat_type  = $chat->getType();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $chat_type is correct as $chat->getType() (which targets Longman\TelegramBot\Entities\Chat::getType()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
413
414
        try {
415 6
            $sth = self::$pdo->prepare('
416 6
                INSERT INTO `' . TB_CHAT . '`
417
                (`id`, `type`, `title`, `created_at` ,`updated_at`, `old_id`)
418
                VALUES
419
                (:id, :type, :title, :date, :date, :oldid)
420
                ON DUPLICATE KEY UPDATE
421
                    `type`       = :type,
422
                    `title`      = :title,
423
                    `updated_at` = :date
424 6
            ');
425
426 6
            if ($migrate_to_chat_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $migrate_to_chat_id of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
427
                $chat_type = 'supergroup';
428
429
                $sth->bindParam(':id', $migrate_to_chat_id, PDO::PARAM_INT);
430
                $sth->bindParam(':oldid', $chat_id, PDO::PARAM_INT);
431
            } else {
432 6
                $sth->bindParam(':id', $chat_id, PDO::PARAM_INT);
433 6
                $sth->bindParam(':oldid', $migrate_to_chat_id, PDO::PARAM_INT);
434
            }
435
436 6
            $sth->bindParam(':type', $chat_type, PDO::PARAM_INT);
437 6
            $sth->bindParam(':title', $chat_title, PDO::PARAM_STR, 255);
438 6
            $sth->bindParam(':date', $date, PDO::PARAM_STR);
439
440 6
            return $sth->execute();
441
        } catch (PDOException $e) {
442
            throw new TelegramException($e->getMessage());
443
        }
444
    }
445
446
    /**
447
     * Insert request into database
448
     *
449
     * @todo self::$pdo->lastInsertId() - unsafe usage if expected previous insert fails?
450
     *
451
     * @param \Longman\TelegramBot\Entities\Update $update
452
     *
453
     * @return bool
454
     * @throws \Longman\TelegramBot\Exception\TelegramException
455
     */
456
    public static function insertRequest(Update $update)
457
    {
458
        $update_id   = $update->getUpdateId();
459
        $update_type = $update->getUpdateType();
460
461
        if ($update_type === 'message') {
462
            $message = $update->getMessage();
463
464
            if (self::insertMessageRequest($message)) {
465
                $message_id = $message->getMessageId();
466
                $chat_id    = $message->getChat()->getId();
467
468
                return self::insertTelegramUpdate($update_id, $chat_id, $message_id, null, null, null, null);
469
            }
470 View Code Duplication
        } elseif ($update_type === 'inline_query') {
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...
471
            $inline_query = $update->getInlineQuery();
472
473
            if (self::insertInlineQueryRequest($inline_query)) {
474
                $inline_query_id = $inline_query->getId();
475
476
                return self::insertTelegramUpdate($update_id, null, null, $inline_query_id, null, null, null);
477
            }
478
        } elseif ($update_type === 'chosen_inline_result') {
479
            $chosen_inline_result = $update->getChosenInlineResult();
480
481
            if (self::insertChosenInlineResultRequest($chosen_inline_result)) {
482
                $chosen_inline_result_local_id = self::$pdo->lastInsertId();
483
484
                return self::insertTelegramUpdate(
485
                    $update_id,
486
                    null,
487
                    null,
488
                    null,
489
                    $chosen_inline_result_local_id,
490
                    null,
491
                    null
492
                );
493
            }
494 View Code Duplication
        } elseif ($update_type === 'callback_query') {
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...
495
            $callback_query = $update->getCallbackQuery();
496
497
            if (self::insertCallbackQueryRequest($callback_query)) {
498
                $callback_query_id = $callback_query->getId();
499
500
                return self::insertTelegramUpdate($update_id, null, null, null, null, $callback_query_id, null);
501
            }
502
        } elseif ($update_type === 'edited_message') {
503
            $edited_message = $update->getEditedMessage();
504
505
            if (self::insertEditedMessageRequest($edited_message)) {
506
                $chat_id                 = $edited_message->getChat()->getId();
507
                $edited_message_local_id = self::$pdo->lastInsertId();
508
509
                return self::insertTelegramUpdate(
510
                    $update_id,
511
                    $chat_id,
512
                    null,
513
                    null,
514
                    null,
515
                    null,
516
                    $edited_message_local_id
517
                );
518
            }
519
        }
520
521
        return false;
522
    }
523
524
    /**
525
     * Insert inline query request into database
526
     *
527
     * @param \Longman\TelegramBot\Entities\InlineQuery $inline_query
528
     *
529
     * @return bool If the insert was successful
530
     * @throws \Longman\TelegramBot\Exception\TelegramException
531
     */
532 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...
533
    {
534
        if (!self::isDbConnected()) {
535
            return false;
536
        }
537
538
        try {
539
            $sth = self::$pdo->prepare('
540
                INSERT IGNORE INTO `' . TB_INLINE_QUERY . '`
541
                (`id`, `user_id`, `location`, `query`, `offset`, `created_at`)
542
                VALUES
543
                (:inline_query_id, :user_id, :location, :query, :param_offset, :created_at)
544
            ');
545
546
            $date            = self::getTimestamp();
547
            $inline_query_id = $inline_query->getId();
548
            $from            = $inline_query->getFrom();
549
            $user_id         = null;
550
            if ($from instanceof User) {
551
                $user_id = $from->getId();
552
                self::insertUser($from, $date);
553
            }
554
555
            $location = $inline_query->getLocation();
556
            $query    = $inline_query->getQuery();
557
            $offset   = $inline_query->getOffset();
558
559
            $sth->bindParam(':inline_query_id', $inline_query_id, PDO::PARAM_INT);
560
            $sth->bindParam(':user_id', $user_id, PDO::PARAM_INT);
561
            $sth->bindParam(':location', $location, PDO::PARAM_STR);
562
            $sth->bindParam(':query', $query, PDO::PARAM_STR);
563
            $sth->bindParam(':param_offset', $offset, PDO::PARAM_STR);
564
            $sth->bindParam(':created_at', $date, PDO::PARAM_STR);
565
566
            return $sth->execute();
567
        } catch (PDOException $e) {
568
            throw new TelegramException($e->getMessage());
569
        }
570
    }
571
572
    /**
573
     * Insert chosen inline result request into database
574
     *
575
     * @param \Longman\TelegramBot\Entities\ChosenInlineResult $chosen_inline_result
576
     *
577
     * @return bool If the insert was successful
578
     * @throws \Longman\TelegramBot\Exception\TelegramException
579
     */
580 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...
581
    {
582
        if (!self::isDbConnected()) {
583
            return false;
584
        }
585
586
        try {
587
            $sth = self::$pdo->prepare('
588
                INSERT INTO `' . TB_CHOSEN_INLINE_RESULT . '`
589
                (`result_id`, `user_id`, `location`, `inline_message_id`, `query`, `created_at`)
590
                VALUES
591
                (:result_id, :user_id, :location, :inline_message_id, :query, :created_at)
592
            ');
593
594
            $date      = self::getTimestamp();
595
            $result_id = $chosen_inline_result->getResultId();
596
            $from      = $chosen_inline_result->getFrom();
597
            $user_id   = null;
598
            if ($from instanceof User) {
599
                $user_id = $from->getId();
600
                self::insertUser($from, $date);
601
            }
602
603
            $location          = $chosen_inline_result->getLocation();
604
            $inline_message_id = $chosen_inline_result->getInlineMessageId();
605
            $query             = $chosen_inline_result->getQuery();
606
607
            $sth->bindParam(':result_id', $result_id, PDO::PARAM_STR);
608
            $sth->bindParam(':user_id', $user_id, PDO::PARAM_INT);
609
            $sth->bindParam(':location', $location, PDO::PARAM_INT);
610
            $sth->bindParam(':inline_message_id', $inline_message_id, PDO::PARAM_STR);
611
            $sth->bindParam(':query', $query, PDO::PARAM_STR);
612
            $sth->bindParam(':created_at', $date, PDO::PARAM_STR);
613
614
            return $sth->execute();
615
        } catch (PDOException $e) {
616
            throw new TelegramException($e->getMessage());
617
        }
618
    }
619
620
    /**
621
     * Insert callback query request into database
622
     *
623
     * @param \Longman\TelegramBot\Entities\CallbackQuery $callback_query
624
     *
625
     * @return bool If the insert was successful
626
     * @throws \Longman\TelegramBot\Exception\TelegramException
627
     */
628
    public static function insertCallbackQueryRequest(CallbackQuery $callback_query)
629
    {
630
        if (!self::isDbConnected()) {
631
            return false;
632
        }
633
634
        try {
635
            $sth = self::$pdo->prepare(
636
                'INSERT IGNORE INTO `' . TB_CALLBACK_QUERY . '`
637
                (`id`, `user_id`, `chat_id`, `message_id`, `inline_message_id`, `data`, `created_at`)
638
                VALUES
639
                (:callback_query_id, :user_id, :chat_id, :message_id, :inline_message_id, :data, :created_at)
640
            ');
641
642
            $date              = self::getTimestamp();
643
            $callback_query_id = $callback_query->getId();
644
            $from              = $callback_query->getFrom();
645
            $user_id           = null;
646
            if ($from instanceof User) {
647
                $user_id = $from->getId();
648
                self::insertUser($from, $date);
649
            }
650
651
            $message    = $callback_query->getMessage();
652
            $chat_id    = null;
653
            $message_id = null;
654
            if ($message instanceof Message) {
655
                $chat_id    = $message->getChat()->getId();
656
                $message_id = $message->getMessageId();
657
658
                $is_message = self::$pdo->query('
659
                    SELECT *
660
                    FROM `' . TB_MESSAGE . '`
661
                    WHERE `id` = ' . $message_id . '
662
                      AND `chat_id` = ' . $chat_id . '
663
                    LIMIT 1
664
                ')->rowCount();
665
666
                if ($is_message) {
667
                    self::insertEditedMessageRequest($message);
668
                } else {
669
                    self::insertMessageRequest($message);
670
                }
671
            }
672
673
            $inline_message_id = $callback_query->getInlineMessageId();
674
            $data              = $callback_query->getData();
675
676
            $sth->bindParam(':callback_query_id', $callback_query_id, PDO::PARAM_INT);
677
            $sth->bindParam(':user_id', $user_id, PDO::PARAM_INT);
678
            $sth->bindParam(':chat_id', $chat_id, PDO::PARAM_INT);
679
            $sth->bindParam(':message_id', $message_id, PDO::PARAM_INT);
680
            $sth->bindParam(':inline_message_id', $inline_message_id, PDO::PARAM_STR);
681
            $sth->bindParam(':data', $data, PDO::PARAM_STR);
682
            $sth->bindParam(':created_at', $date, PDO::PARAM_STR);
683
684
            return $sth->execute();
685
        } catch (PDOException $e) {
686
            throw new TelegramException($e->getMessage());
687
        }
688
    }
689
690
    /**
691
     * Insert Message request in db
692
     *
693
     * @param \Longman\TelegramBot\Entities\Message $message
694
     *
695
     * @return bool If the insert was successful
696
     * @throws \Longman\TelegramBot\Exception\TelegramException
697
     */
698 6
    public static function insertMessageRequest(Message $message)
699
    {
700 6
        if (!self::isDbConnected()) {
701
            return false;
702
        }
703
704 6
        $from = $message->getFrom();
705 6
        $chat = $message->getChat();
706
707 6
        $chat_id = $chat->getId();
708
709 6
        $date = self::getTimestamp($message->getDate());
710
711 6
        $forward_from       = $message->getForwardFrom();
712 6
        $forward_from_chat  = $message->getForwardFromChat();
713 6
        $photo              = self::entitiesArrayToJson($message->getPhoto(), '');
0 ignored issues
show
Bug introduced by
It seems like $message->getPhoto() targeting Longman\TelegramBot\Entities\Message::getPhoto() can also be of type null; however, Longman\TelegramBot\DB::entitiesArrayToJson() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
714 6
        $entities           = self::entitiesArrayToJson($message->getEntities(), null);
715 6
        $new_chat_member    = $message->getNewChatMember();
716 6
        $new_chat_photo     = self::entitiesArrayToJson($message->getNewChatPhoto(), '');
717 6
        $left_chat_member   = $message->getLeftChatMember();
718 6
        $migrate_to_chat_id = $message->getMigrateToChatId();
719
720
        //Insert chat, update chat id in case it migrated
721 6
        self::insertChat($chat, $date, $migrate_to_chat_id);
0 ignored issues
show
Bug introduced by
It seems like $chat defined by $message->getChat() on line 705 can be null; however, Longman\TelegramBot\DB::insertChat() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
722
723
        //Insert user and the relation with the chat
724 6
        self::insertUser($from, $date, $chat);
0 ignored issues
show
Bug introduced by
It seems like $from defined by $message->getFrom() on line 704 can be null; however, Longman\TelegramBot\DB::insertUser() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
725
726
        //Insert the forwarded message user in users table
727 6
        if ($forward_from instanceof User) {
728
            $forward_date = self::getTimestamp($message->getForwardDate());
729
            self::insertUser($forward_from, $forward_date);
730
            $forward_from = $forward_from->getId();
731
        }
732
733 6
        if ($forward_from_chat instanceof Chat) {
734
            $forward_date = self::getTimestamp($message->getForwardDate());
735
            self::insertChat($forward_from_chat, $forward_date);
736
            $forward_from_chat = $forward_from_chat->getId();
737
        }
738
739
        //New and left chat member
740 6
        if ($new_chat_member instanceof User) {
741
            //Insert the new chat user
742
            self::insertUser($new_chat_member, $date, $chat);
743
            $new_chat_member = $new_chat_member->getId();
744
        } elseif ($left_chat_member instanceof User) {
745
            //Insert the left chat user
746
            self::insertUser($left_chat_member, $date, $chat);
747
            $left_chat_member = $left_chat_member->getId();
748
        }
749
750
        try {
751 6
            $sth = self::$pdo->prepare('
752 6
                INSERT IGNORE INTO `' . TB_MESSAGE . '`
753
                (
754
                    `id`, `user_id`, `chat_id`, `date`, `forward_from`, `forward_from_chat`,
755
                    `forward_date`, `reply_to_chat`, `reply_to_message`, `text`, `entities`, `audio`, `document`,
756
                    `photo`, `sticker`, `video`, `voice`, `caption`, `contact`,
757
                    `location`, `venue`, `new_chat_member`, `left_chat_member`,
758
                    `new_chat_title`,`new_chat_photo`, `delete_chat_photo`, `group_chat_created`,
759
                    `supergroup_chat_created`, `channel_chat_created`,
760
                    `migrate_from_chat_id`, `migrate_to_chat_id`, `pinned_message`
761
                ) VALUES (
762
                    :message_id, :user_id, :chat_id, :date, :forward_from, :forward_from_chat,
763
                    :forward_date, :reply_to_chat, :reply_to_message, :text, :entities, :audio, :document,
764
                    :photo, :sticker, :video, :voice, :caption, :contact,
765
                    :location, :venue, :new_chat_member, :left_chat_member,
766
                    :new_chat_title, :new_chat_photo, :delete_chat_photo, :group_chat_created,
767
                    :supergroup_chat_created, :channel_chat_created,
768
                    :migrate_from_chat_id, :migrate_to_chat_id, :pinned_message
769
                )
770 6
            ');
771
772 6
            $message_id = $message->getMessageId();
773 6
            $from_id    = $from->getId();
774
775 6
            $reply_to_message    = $message->getReplyToMessage();
776 6
            $reply_to_message_id = null;
777 6
            if ($reply_to_message instanceof ReplyToMessage) {
778
                $reply_to_message_id = $reply_to_message->getMessageId();
779
                // please notice that, as explained in the documentation, reply_to_message don't contain other
780
                // reply_to_message field so recursion deep is 1
781
                self::insertMessageRequest($reply_to_message);
782
            }
783
784 6
            $text                    = $message->getText();
785 6
            $audio                   = $message->getAudio();
786 6
            $document                = $message->getDocument();
787 6
            $sticker                 = $message->getSticker();
788 6
            $video                   = $message->getVideo();
789 6
            $voice                   = $message->getVoice();
790 6
            $caption                 = $message->getCaption();
791 6
            $contact                 = $message->getContact();
792 6
            $location                = $message->getLocation();
793 6
            $venue                   = $message->getVenue();
794 6
            $new_chat_title          = $message->getNewChatTitle();
795 6
            $delete_chat_photo       = $message->getDeleteChatPhoto();
796 6
            $group_chat_created      = $message->getGroupChatCreated();
797 6
            $supergroup_chat_created = $message->getSupergroupChatCreated();
798 6
            $channel_chat_created    = $message->getChannelChatCreated();
799 6
            $migrate_from_chat_id    = $message->getMigrateFromChatId();
800 6
            $migrate_to_chat_id      = $message->getMigrateToChatId();
801 6
            $pinned_message          = $message->getPinnedMessage();
802
803 6
            $sth->bindParam(':chat_id', $chat_id, PDO::PARAM_INT);
804 6
            $sth->bindParam(':message_id', $message_id, PDO::PARAM_INT);
805 6
            $sth->bindParam(':user_id', $from_id, PDO::PARAM_INT);
806 6
            $sth->bindParam(':date', $date, PDO::PARAM_STR);
807 6
            $sth->bindParam(':forward_from', $forward_from, PDO::PARAM_INT);
808 6
            $sth->bindParam(':forward_from_chat', $forward_from_chat, PDO::PARAM_INT);
809 6
            $sth->bindParam(':forward_date', $forward_date, PDO::PARAM_STR);
810
811 6
            $reply_to_chat_id = null;
812 6
            if ($reply_to_message_id) {
813
                $reply_to_chat_id = $chat_id;
814
            }
815
816 6
            $sth->bindParam(':reply_to_chat', $reply_to_chat_id, PDO::PARAM_INT);
817 6
            $sth->bindParam(':reply_to_message', $reply_to_message_id, PDO::PARAM_INT);
818 6
            $sth->bindParam(':text', $text, PDO::PARAM_STR);
819 6
            $sth->bindParam(':entities', $entities, PDO::PARAM_STR);
820 6
            $sth->bindParam(':audio', $audio, PDO::PARAM_STR);
821 6
            $sth->bindParam(':document', $document, PDO::PARAM_STR);
822 6
            $sth->bindParam(':photo', $photo, PDO::PARAM_STR);
823 6
            $sth->bindParam(':sticker', $sticker, PDO::PARAM_STR);
824 6
            $sth->bindParam(':video', $video, PDO::PARAM_STR);
825 6
            $sth->bindParam(':voice', $voice, PDO::PARAM_STR);
826 6
            $sth->bindParam(':caption', $caption, PDO::PARAM_STR);
827 6
            $sth->bindParam(':contact', $contact, PDO::PARAM_STR);
828 6
            $sth->bindParam(':location', $location, PDO::PARAM_STR);
829 6
            $sth->bindParam(':venue', $venue, PDO::PARAM_STR);
830 6
            $sth->bindParam(':new_chat_member', $new_chat_member, PDO::PARAM_INT);
831 6
            $sth->bindParam(':left_chat_member', $left_chat_member, PDO::PARAM_INT);
832 6
            $sth->bindParam(':new_chat_title', $new_chat_title, PDO::PARAM_STR);
833 6
            $sth->bindParam(':new_chat_photo', $new_chat_photo, PDO::PARAM_STR);
834 6
            $sth->bindParam(':delete_chat_photo', $delete_chat_photo, PDO::PARAM_STR);
835 6
            $sth->bindParam(':group_chat_created', $group_chat_created, PDO::PARAM_STR);
836 6
            $sth->bindParam(':supergroup_chat_created', $supergroup_chat_created, PDO::PARAM_INT);
837 6
            $sth->bindParam(':channel_chat_created', $channel_chat_created, PDO::PARAM_INT);
838 6
            $sth->bindParam(':migrate_from_chat_id', $migrate_from_chat_id, PDO::PARAM_INT);
839 6
            $sth->bindParam(':migrate_to_chat_id', $migrate_to_chat_id, PDO::PARAM_INT);
840 6
            $sth->bindParam(':pinned_message', $pinned_message, PDO::PARAM_INT);
841
842 6
            return $sth->execute();
843
        } catch (PDOException $e) {
844
            throw new TelegramException($e->getMessage());
845
        }
846
    }
847
848
    /**
849
     * Insert Edited Message request in db
850
     *
851
     * @param \Longman\TelegramBot\Entities\Message $edited_message
852
     *
853
     * @return bool If the insert was successful
854
     * @throws \Longman\TelegramBot\Exception\TelegramException
855
     */
856
    public static function insertEditedMessageRequest(Message $edited_message)
857
    {
858
        if (!self::isDbConnected()) {
859
            return false;
860
        }
861
862
        $from = $edited_message->getFrom();
863
        $chat = $edited_message->getChat();
864
865
        $chat_id = $chat->getId();
866
867
        $edit_date = self::getTimestamp($edited_message->getEditDate());
868
869
        $entities = self::entitiesArrayToJson($edited_message->getEntities(), null);
870
871
        //Insert chat
872
        self::insertChat($chat, $edit_date);
0 ignored issues
show
Bug introduced by
It seems like $chat defined by $edited_message->getChat() on line 863 can be null; however, Longman\TelegramBot\DB::insertChat() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
873
874
        //Insert user and the relation with the chat
875
        self::insertUser($from, $edit_date, $chat);
0 ignored issues
show
Bug introduced by
It seems like $from defined by $edited_message->getFrom() on line 862 can be null; however, Longman\TelegramBot\DB::insertUser() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
876
877
        try {
878
            $sth = self::$pdo->prepare('
879
                INSERT INTO `' . TB_EDITED_MESSAGE . '`
880
                (`chat_id`, `message_id`, `user_id`, `edit_date`, `text`, `entities`, `caption`)
881
                VALUES
882
                (:chat_id, :message_id, :user_id, :date, :text, :entities, :caption)
883
            ');
884
885
            $message_id = $edited_message->getMessageId();
886
            $from_id    = $from->getId();
887
888
            $text    = $edited_message->getText();
889
            $caption = $edited_message->getCaption();
890
891
            $sth->bindParam(':chat_id', $chat_id, PDO::PARAM_INT);
892
            $sth->bindParam(':message_id', $message_id, PDO::PARAM_INT);
893
            $sth->bindParam(':user_id', $from_id, PDO::PARAM_INT);
894
            $sth->bindParam(':date', $edit_date, PDO::PARAM_STR);
895
            $sth->bindParam(':text', $text, PDO::PARAM_STR);
896
            $sth->bindParam(':entities', $entities, PDO::PARAM_STR);
897
            $sth->bindParam(':caption', $caption, PDO::PARAM_STR);
898
899
            return $sth->execute();
900
        } catch (PDOException $e) {
901
            throw new TelegramException($e->getMessage());
902
        }
903
    }
904
905
    /**
906
     * Select Group and/or single Chats
907
     *
908
     * @param bool   $select_groups
909
     * @param bool   $select_super_groups
910
     * @param bool   $select_users
911
     * @param string $date_from
912
     * @param string $date_to
913
     * @param int    $chat_id
914
     * @param string $text
915
     *
916
     * @return array|bool (Selected chats or false if invalid arguments)
917
     * @throws \Longman\TelegramBot\Exception\TelegramException
918
     */
919
    public static function selectChats(
920
        $select_groups = true,
921
        $select_super_groups = true,
922
        $select_users = true,
923
        $date_from = null,
924
        $date_to = null,
925
        $chat_id = null,
926
        $text = null
927
    ) {
928
        if (!self::isDbConnected()) {
929
            return false;
930
        }
931
932
        if (!$select_groups && !$select_users && !$select_super_groups) {
933
            return false;
934
        }
935
936
        try {
937
            $query = '
938
                SELECT * ,
939
                ' . TB_CHAT . '.`id` AS `chat_id`,
940
                ' . TB_CHAT . '.`created_at` AS `chat_created_at`,
941
                ' . TB_CHAT . '.`updated_at` AS `chat_updated_at`
942
            ';
943
            if ($select_users) {
944
                $query .= '
945
                    , ' . TB_USER . '.`id` AS `user_id`
946
                    FROM `' . TB_CHAT . '`
947
                    LEFT JOIN `' . TB_USER . '`
948
                    ON ' . TB_CHAT . '.`id`=' . TB_USER . '.`id`
949
                ';
950
            } else {
951
                $query .= 'FROM `' . TB_CHAT . '`';
952
            }
953
954
            //Building parts of query
955
            $where  = [];
956
            $tokens = [];
957
958
            if (!$select_groups || !$select_users || !$select_super_groups) {
959
                $chat_or_user = [];
960
961
                $select_groups && $chat_or_user[] = TB_CHAT . '.`type` = "group"';
962
                $select_super_groups && $chat_or_user[] = TB_CHAT . '.`type` = "supergroup"';
963
                $select_users && $chat_or_user[] = TB_CHAT . '.`type` = "private"';
964
965
                $where[] = '(' . implode(' OR ', $chat_or_user) . ')';
966
            }
967
968
            if (null !== $date_from) {
969
                $where[]              = TB_CHAT . '.`updated_at` >= :date_from';
970
                $tokens[':date_from'] = $date_from;
971
            }
972
973
            if (null !== $date_to) {
974
                $where[]            = TB_CHAT . '.`updated_at` <= :date_to';
975
                $tokens[':date_to'] = $date_to;
976
            }
977
978
            if (null !== $chat_id) {
979
                $where[]            = TB_CHAT . '.`id` = :chat_id';
980
                $tokens[':chat_id'] = $chat_id;
981
            }
982
983
            if (null !== $text) {
984
                if ($select_users) {
985
                    $where[] = '(
986
                        LOWER(' . TB_CHAT . '.`title`) LIKE :text
987
                        OR LOWER(' . TB_USER . '.`first_name`) LIKE :text
988
                        OR LOWER(' . TB_USER . '.`last_name`) LIKE :text
989
                        OR LOWER(' . TB_USER . '.`username`) LIKE :text
990
                    )';
991
                } else {
992
                    $where[] = 'LOWER(' . TB_CHAT . '.`title`) LIKE :text';
993
                }
994
                $tokens[':text'] = '%' . strtolower($text) . '%';
995
            }
996
997
            if ($where) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $where of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
998
                $query .= ' WHERE ' . implode(' AND ', $where);
999
            }
1000
1001
            $query .= ' ORDER BY ' . TB_CHAT . '.`updated_at` ASC';
1002
1003
            $sth = self::$pdo->prepare($query);
1004
            $sth->execute($tokens);
1005
1006
            return $sth->fetchAll(PDO::FETCH_ASSOC);
1007
        } catch (PDOException $e) {
1008
            throw new TelegramException($e->getMessage());
1009
        }
1010
    }
1011
}
1012