Passed
Push — main ( 926a07...7834cc )
by Miaad
10:19
created

mysql::affected_rows()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 2
rs 10
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
    /**
26
     * @internal Only for BPT self usage , Don't use it in your source!
27
     */
28
    public static function init (): void {
29
        $host = settings::$db['host'] ?? 'localhost';
30
        $port = settings::$db['port'] ?? 3306;
31
        $user = settings::$db['user'] ?? settings::$db['username'] ?? 'unknown';
32
        $pass = settings::$db['pass'] ?? settings::$db['password'] ?? 'unknown';
33
        $dbname = settings::$db['dbname'];
34
        self::$connection = new mysqli($host, $user, $pass, $dbname, $port);
35
        if (self::$connection->connect_errno) {
36
            logger::write('SQL connection has problem : ' . self::$connection->connect_error, loggerTypes::ERROR);
37
            throw new bptException('SQL_CONNECTION_PROBLEM');
38
        }
39
        if (!lock::exist('BPT-MYSQL')) {
40
            self::install();
41
        }
42
    }
43
44
    private static function install (): void {
45
        self::pureQuery("
46
CREATE TABLE `users`
47
(
48
    `id`           BIGINT(20) NOT NULL,
49
    `username`     VARCHAR(32) NULL DEFAULT NULL,
50
    `lang_code`    VARCHAR(3)  NULL DEFAULT NULL,
51
    `first_active` INT(11) NOT NULL DEFAULT '0',
52
    `last_active`  INT(11) NOT NULL DEFAULT '0',
53
    `referral`     BIGINT(20) NULL DEFAULT NULL,
54
    `blocked`      BOOLEAN     NOT NULL DEFAULT FALSE,
55
    `step`         VARCHAR(64) NOT NULL DEFAULT 'main',
56
    `value`        TEXT NULL DEFAULT NULL,
57
    PRIMARY KEY (`id`)
58
) ENGINE = InnoDB;");
59
        lock::set('BPT-MYSQL');
60
    }
61
62
    /**
63
     * @internal Only for BPT self usage , Don't use it in your source!
64
     */
65
    public static function process (): void {
66
        if (isset(BPT::$update->message)) {
67
            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

67
            self::processMessage(/** @scrutinizer ignore-type */ BPT::$update->message);
Loading history...
68
        }
69
        elseif (isset(BPT::$update->edited_message)) {
70
            self::processMessage(BPT::$update->edited_message);
71
        }
72
        elseif (isset(BPT::$update->callback_query)) {
73
            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

73
            self::processCallbackQuery(/** @scrutinizer ignore-type */ BPT::$update->callback_query);
Loading history...
74
        }
75
        elseif (isset(BPT::$update->inline_query)) {
76
            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

76
            self::processInlineQuery(/** @scrutinizer ignore-type */ BPT::$update->inline_query);
Loading history...
77
        }
78
        elseif (isset(BPT::$update->my_chat_member)) {
79
            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

79
            self::processMyChatMember(/** @scrutinizer ignore-type */ BPT::$update->my_chat_member);
Loading history...
80
        }
81
    }
82
83
    private static function processMessage (message $update): void {
84
        $type = $update->chat->type;
85
        if ($type === chatType::PRIVATE) {
86
            $user_id = $update->from->id;
87
            $first_active = $last_active = time();
88
            $referral = null;
89
            $username = $update->from->username;
90
            $lang_code = $update->from->language_code;
91
            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

91
            if (isset($update->commend) && isset($update->commend_payload) && $update->commend === 'start' && str_starts_with(/** @scrutinizer ignore-type */ $update->commend_payload, 'ref_')) {
Loading history...
92
                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

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

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