Passed
Push — main ( 51b09b...a53d1a )
by Miaad
11:27
created

mysql::makeQueryReady()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 9
c 1
b 0
f 0
nc 8
nop 4
dl 0
loc 13
rs 9.6111
1
<?php
2
3
namespace BPT\database;
4
5
use BPT\BPT;
6
use BPT\constants\chatMemberStatus;
7
use BPT\constants\chatType;
8
use BPT\constants\loggerTypes;
9
use BPT\exception\bptException;
10
use BPT\lock;
11
use BPT\logger;
12
use BPT\settings;
13
use BPT\tools;
14
use BPT\types\callbackQuery;
15
use BPT\types\chatMemberUpdated;
16
use BPT\types\inlineQuery;
17
use BPT\types\message;
18
use Generator;
19
use mysqli;
20
use mysqli_result;
21
22
class mysql {
23
    private static mysqli $connection;
24
25
    private static bool $auto_process = true;
26
27
    /**
28
     * @internal Only for BPT self usage , Don't use it in your source!
29
     */
30
    public static function init (): void {
31
        $host = settings::$db['host'] ?? 'localhost';
32
        $port = settings::$db['port'] ?? 3306;
33
        $user = settings::$db['user'] ?? settings::$db['username'] ?? 'unknown';
34
        $pass = settings::$db['pass'] ?? settings::$db['password'] ?? 'unknown';
35
        self::$auto_process = settings::$db['auto_process'] == true;
36
        $dbname = settings::$db['dbname'];
37
        self::$connection = new mysqli($host, $user, $pass, $dbname, $port);
38
        if (self::$connection->connect_errno) {
39
            logger::write('SQL connection has problem : ' . self::$connection->connect_error, loggerTypes::ERROR);
40
            throw new bptException('SQL_CONNECTION_PROBLEM');
41
        }
42
        if (self::$auto_process && !lock::exist('BPT-MYSQL')) {
43
            self::install();
44
        }
45
    }
46
47
    private static function install (): void {
48
        self::pureQuery("
49
CREATE TABLE `users`
50
(
51
    `id`           BIGINT(20) NOT NULL,
52
    `username`     VARCHAR(32) NULL DEFAULT NULL,
53
    `lang_code`    VARCHAR(3)  NULL DEFAULT NULL,
54
    `first_active` INT(11) NOT NULL DEFAULT '0',
55
    `last_active`  INT(11) NOT NULL DEFAULT '0',
56
    `referral`     BIGINT(20) NULL DEFAULT NULL,
57
    `blocked`      BOOLEAN     NOT NULL DEFAULT FALSE,
58
    `step`         VARCHAR(64) NOT NULL DEFAULT 'main',
59
    `value`        TEXT NULL DEFAULT NULL,
60
    PRIMARY KEY (`id`)
61
) ENGINE = InnoDB;");
62
        lock::set('BPT-MYSQL');
63
    }
64
65
    /**
66
     * @internal Only for BPT self usage , Don't use it in your source!
67
     */
68
    public static function process (): void {
69
        if (self::$auto_process) {
70
            if (isset(BPT::$update->message)) {
71
                self::processMessage(BPT::$update->message);
0 ignored issues
show
Bug introduced by
It seems like BPT\BPT::update->message can also be of type null; however, parameter $update of BPT\database\mysql::processMessage() does only seem to accept BPT\types\message, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

71
                self::processMessage(/** @scrutinizer ignore-type */ BPT::$update->message);
Loading history...
72
            }
73
            elseif (isset(BPT::$update->edited_message)) {
74
                self::processMessage(BPT::$update->edited_message);
75
            }
76
            elseif (isset(BPT::$update->callback_query)) {
77
                self::processCallbackQuery(BPT::$update->callback_query);
0 ignored issues
show
Bug introduced by
It seems like BPT\BPT::update->callback_query can also be of type null; however, parameter $update of BPT\database\mysql::processCallbackQuery() does only seem to accept BPT\types\callbackQuery, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

77
                self::processCallbackQuery(/** @scrutinizer ignore-type */ BPT::$update->callback_query);
Loading history...
78
            }
79
            elseif (isset(BPT::$update->inline_query)) {
80
                self::processInlineQuery(BPT::$update->inline_query);
0 ignored issues
show
Bug introduced by
It seems like BPT\BPT::update->inline_query can also be of type null; however, parameter $update of BPT\database\mysql::processInlineQuery() does only seem to accept BPT\types\inlineQuery, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

80
                self::processInlineQuery(/** @scrutinizer ignore-type */ BPT::$update->inline_query);
Loading history...
81
            }
82
            elseif (isset(BPT::$update->my_chat_member)) {
83
                self::processMyChatMember(BPT::$update->my_chat_member);
0 ignored issues
show
Bug introduced by
It seems like BPT\BPT::update->my_chat_member can also be of type null; however, parameter $update of BPT\database\mysql::processMyChatMember() does only seem to accept BPT\types\chatMemberUpdated, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

83
                self::processMyChatMember(/** @scrutinizer ignore-type */ BPT::$update->my_chat_member);
Loading history...
84
            }
85
        }
86
    }
87
88
    private static function processMessage (message $update): void {
89
        $type = $update->chat->type;
90
        if ($type === chatType::PRIVATE) {
91
            $user_id = $update->from->id;
92
            $first_active = $last_active = time();
93
            $referral = null;
94
            $username = $update->from->username;
95
            $lang_code = $update->from->language_code;
96
            if (isset($update->commend) && isset($update->commend_payload) && $update->commend === 'start' && str_starts_with($update->commend_payload, 'ref_')) {
0 ignored issues
show
Bug introduced by
It seems like $update->commend_payload can also be of type null; however, parameter $haystack of str_starts_with() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

96
            if (isset($update->commend) && isset($update->commend_payload) && $update->commend === 'start' && str_starts_with(/** @scrutinizer ignore-type */ $update->commend_payload, 'ref_')) {
Loading history...
97
                if (tools::isShorted(substr($update->commend_payload, 4))) {
0 ignored issues
show
Bug introduced by
It seems like $update->commend_payload can also be of type null; however, parameter $string of substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

97
                if (tools::isShorted(substr(/** @scrutinizer ignore-type */ $update->commend_payload, 4))) {
Loading history...
98
                    $referral = tools::shortDecode(substr($update->commend_payload, 4));
99
                }
100
            }
101
            self::query("INSERT INTO `users`(`id`, `username`, `lang_code`, `first_active`, `last_active`, `referral`) VALUES (?,?,?,?,?,?) on duplicate key update `last_active` = ?, `username` = ?", [
102
                $user_id,
103
                $username,
104
                $lang_code,
105
                $first_active,
106
                $last_active,
107
                $referral,
108
                $last_active,
109
                $username
110
            ]);
111
        }
112
    }
113
114
    private static function processCallbackQuery (callbackQuery $update): void {
115
        $type = $update->message->chat->type;
116
        if ($type === chatType::PRIVATE) {
117
            $user_id = $update->from->id;
118
            $last_active = time();
119
            $username = $update->from->username;
120
            self::update('users', ['last_active' => $last_active, 'username' => $username], ['id' => $user_id], 1);
121
        }
122
    }
123
124
    private static function processInlineQuery (inlineQuery $update): void {
125
        $type = $update->chat_type;
126
        if ($type === chatType::PRIVATE || $type === chatType::SENDER) {
127
            $user_id = $update->from->id;
128
            $last_active = time();
129
            $username = $update->from->username;
130
            self::update('users', ['last_active' => $last_active, 'username' => $username], ['id' => $user_id], 1);
131
        }
132
    }
133
134
    private static function processMyChatMember (chatMemberUpdated $update): void {
135
        $type = $update->chat->type;
136
        if ($type === chatType::PRIVATE) {
137
            if ($update->new_chat_member->status === chatMemberStatus::MEMBER) {
138
                self::update('users', ['blocked' => false], ['id' => $update->from->id], 1);
139
            }
140
            else {
141
                self::update('users', ['blocked' => true], ['id' => $update->from->id], 1);
142
            }
143
        }
144
    }
145
146
    /**
147
     * Get real mysqli connections
148
     *
149
     * @return mysqli
150
     */
151
    public static function getMysqli (): mysqli {
152
        return self::$connection;
153
    }
154
155
    /**
156
     * Get affected rows
157
     *
158
     * @return int|string
159
     */
160
    public static function affected_rows (): int|string {
161
        return self::$connection->affected_rows;
162
    }
163
164
    /**
165
     * Get inserted id
166
     *
167
     * @return int|string
168
     */
169
    public static function insert_id (): int|string {
170
        return self::$connection->insert_id;
171
    }
172
173
    /**
174
     * Escape string with real_escape_string of mysqli class
175
     *
176
     * @param string $text
177
     *
178
     * @return string
179
     */
180
    public static function escapeString (string $text): string {
181
        return self::$connection->real_escape_string($text);
182
    }
183
184
    /**
185
     * Run query as what is it
186
     *
187
     * The library doesn't do anything on it
188
     *
189
     * It's like calling mysqli->query();
190
     *
191
     * @param string $query
192
     *
193
     * @return mysqli_result|bool
194
     */
195
    public static function pureQuery (string $query): mysqli_result|bool {
196
        return self::$connection->query($query);
197
    }
198
199
    /**
200
     * Run query with safe execution
201
     *
202
     * Replace inputs with `?` in query to be replaced safely with $vars in order
203
     *
204
     * e.g. : mysql::query('select * from `users` where `id` = ? limit 1',[123456789]);
205
     *
206
     * e.g. : mysql::query('update `users` set `step` = ? where `id` = ? limit 1',['main',123456789]);
207
     *
208
     * @param string $query
209
     * @param array  $vars        default [] or empty
210
     * @param bool   $need_result set if you want result be returned, default : true
211
     *
212
     * @return mysqli_result|bool
213
     */
214
    public static function query (string $query, array $vars = [], bool $need_result = true): mysqli_result|bool {
215
        $prepare = self::$connection->prepare($query);
216
        if (!$prepare->execute($vars)) {
0 ignored issues
show
Unused Code introduced by
The call to mysqli_stmt::execute() has too many arguments starting with $vars. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

216
        if (!$prepare->/** @scrutinizer ignore-call */ execute($vars)) {

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
217
            logger::write(loggerTypes::WARNING, $prepare->error);
218
            return false;
219
        }
220
        return $need_result ? $prepare->get_result() : true;
221
    }
222
223
    private static function makeArrayReady (string &$query, array $array, string $operator = ' AND '): array {
224
        $first = true;
225
        $values = [];
226
        foreach ($array as $name => $value) {
227
            if ($first) {
228
                $first = false;
229
            }
230
            else {
231
                $query .= $operator;
232
            }
233
            $query .= " `$name` = ?";
234
            $values[] = $value;
235
        }
236
        return $values;
237
    }
238
239
    private static function makeQueryReady (string &$query, array $where = null, int $count = null, int $offset = null): array {
240
        $values = [];
241
        if (!empty($where)) {
242
            $query .= " WHERE";
243
            $values = self::makeArrayReady($query, $where);
244
        }
245
        if (!empty($count)) {
246
            $query .= !empty($offset) ? " LIMIT $offset,$count" : " LIMIT $count";
247
        }
248
        elseif (!empty($offset)) {
249
            $query .= " OFFSET $offset";
250
        }
251
        return $values;
252
    }
253
254
    /**
255
     * Run delete query
256
     *
257
     * e.g. : `mysql::delete('users',['id'=>123456789],1);`
258
     *
259
     * @param string     $table  table name
260
     * @param array|null $where  Set your ifs default : null
261
     * @param int|null   $count  Set if you want to delete specific amount of row default : null
262
     * @param int|null   $offset Set if you want to delete rows after specific row default : null
263
     *
264
     * @return mysqli_result|bool
265
     */
266
    public static function delete (string $table, array $where = null, int $count = null, int $offset = null): mysqli_result|bool {
267
        $query = "DELETE FROM `$table`";
268
        $res = self::makeQueryReady($query, $where, $count, $offset);
269
        return self::query($query, $res, false);
270
    }
271
272
    /**
273
     * Run update query
274
     *
275
     * e.g. : mysql::update('users',['step'=>'panel'],['id'=>123456789],1);
276
     *
277
     * @param string     $table  table name
278
     * @param array      $modify Set the data's you want to modify
279
     * @param array|null $where  Set your ifs default : null
280
     * @param int|null   $count  Set if you want to update specific amount of row default : null
281
     * @param int|null   $offset Set if you want to update rows after specific row default : null
282
     *
283
     * @return mysqli_result|bool
284
     */
285
    public static function update (string $table, array $modify, array $where = null, int $count = null, int $offset = null): mysqli_result|bool {
286
        $query = "UPDATE `$table` SET";
287
        $values = self::makeArrayReady($query, $modify, ', ');
288
        $res = self::makeQueryReady($query, $where, $count, $offset);
289
        return self::query($query, array_merge($values, $res), false);
290
    }
291
292
    /**
293
     * Run insert query
294
     *
295
     * e.g. : `mysql::insert('users',['id','column1','column2','column3'],[123456789,'value1','value2','value3']);`
296
     *
297
     * @param string       $table   table name
298
     * @param string|array $columns sets columns that you want to fill
299
     * @param array|string $values  sets value that you want to set
300
     *
301
     * @return mysqli_result|bool
302
     */
303
    public static function insert (string $table, string|array $columns, array|string $values): mysqli_result|bool {
304
        $query = "INSERT INTO `$table`(";
305
        $query .= '`' . (is_string($columns) ? $columns : implode('`,`', $columns)) . '`) VALUES (';
0 ignored issues
show
introduced by
The condition is_string($columns) is always false.
Loading history...
306
        if (is_string($values)) $values = [$values];
0 ignored issues
show
introduced by
The condition is_string($values) is always false.
Loading history...
307
        $query .= '?' . str_repeat(',?', count($values) - 1) . ')';
308
        return self::query($query, $values, false);
309
    }
310
311
    /**
312
     * Run select query
313
     *
314
     * e.g. : mysql::select('users','*',['id'=>123456789],1);
315
     *
316
     * e.g. : mysql::select('users',['step','referrals'],['id'=>123456789],1);
317
     *
318
     * @param string       $table   table name
319
     * @param array|string $columns sets column that you want to retrieve , set '*' to retrieve all , default : '*'
320
     * @param array|null   $where   Set your ifs default : null
321
     * @param int|null     $count   Set if you want to select specific amount of row default : null
322
     * @param int|null     $offset  Set if you want to select rows after specific row default : null
323
     *
324
     * @return mysqli_result|bool
325
     */
326
    public static function select (string $table, array|string $columns = '*', array $where = null, int $count = null, int $offset = null): mysqli_result|bool {
327
        $query = "SELECT ";
328
        if ($columns == '*') {
329
            $query .= "* ";
330
        }
331
        else {
332
            $query .= '`' . (is_string($columns) ? $columns : implode('`,`', $columns)) . '` ';
333
        }
334
        $query .= "FROM `$table`";
335
        $res = self::makeQueryReady($query, $where, $count, $offset);
336
        return self::query($query, $res);
337
    }
338
339
    /**
340
     * Same as mysql::select but return first result as array
341
     *
342
     * mysql::selectArray('users','*',['id'=>123456789]);
343
     *
344
     * @param string       $table   table name
345
     * @param array|string $columns sets column that you want to retrieve , set '*' to retrieve all , default : '*'
346
     * @param array|null   $where   Set your ifs default : null
347
     *
348
     * @return null|bool|array
349
     */
350
    public static function selectArray (string $table, array|string $columns = '*', array $where = null): bool|array|null {
351
        $res = self::select($table, $columns, $where, 1);
352
        if ($res) {
353
            return $res->fetch_assoc();
354
        }
355
        return $res;
356
    }
357
358
    /**
359
     * Same as mysql::select but return first result as object(stdClass)
360
     *
361
     * mysql::selectObject('users','*',['id'=>123456789]);
362
     *
363
     * @param string       $table   table name
364
     * @param array|string $columns sets column that you want to retrieve , set '*' to retrieve all , default : '*'
365
     * @param array|null   $where   Set your ifs default : null
366
     */
367
    public static function selectObject (string $table, array|string $columns = '*', array $where = null) {
368
        $res = self::select($table, $columns, $where, 1);
369
        if ($res) {
370
            return $res->fetch_object();
371
        }
372
        return $res;
373
    }
374
375
    /**
376
     * Same as mysql::select but return each row as generator
377
     *
378
     * e.g. : mysql::selectEach('users','*',['id'=>123456789],1);
379
     * e.g. : mysql::selectEach('users',['id']);
380
     *
381
     * @param string       $table   table name
382
     * @param array|string $columns sets column that you want to retrieve , set '*' to retrieve all , default : '*'
383
     * @param array|null   $where   Set your ifs default : null
384
     * @param int|null     $count   Set if you want to select specific amount of row default : null
385
     * @param int|null     $offset  Set if you want to select rows after specific row default : null
386
     *
387
     * @return bool|Generator
388
     */
389
    public static function selectEach (string $table, array|string $columns = '*', array $where = null, int $count = null, int $offset = null): bool|Generator {
390
        $res = self::select($table, $columns, $where, $count, $offset);
391
        if ($res) {
392
            while ($row = $res->fetch_assoc()) yield $row;
393
        }
394
        else return $res;
395
    }
396
}