Passed
Pull Request — master (#1572)
by Michael
09:39
created

XoopsMySQLDatabase::exec()   B

Complexity

Conditions 10
Paths 16

Size

Total Lines 34
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 10
eloc 16
c 1
b 1
f 0
nc 16
nop 1
dl 0
loc 34
rs 7.6666

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * MySQL access using MySQLi extension
4
 *
5
 * You may not change or alter any portion of this comment or credits
6
 * of supporting developers from this source code or any supporting source code
7
 * which is considered copyrighted (c) material of the original comment or credit authors.
8
 * This program is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
 *
12
 * @copyright       (c) 2000-2025 XOOPS Project (https://xoops.org)
13
 * @license             GNU GPL 2 (https://www.gnu.org/licenses/gpl-2.0.html)
14
 * @package             class
15
 * @subpackage          database
16
 * @since               1.0.0
17
 * @author              Kazumi Ono <[email protected]>
18
 * @author              Rodney Fulk <[email protected]>
19
 */
20
defined('XOOPS_ROOT_PATH') || die('Restricted access');
21
22
include_once XOOPS_ROOT_PATH . '/class/database/database.php';
23
24
/**
25
 * Connection to a MySQL database using MySQLi extension
26
 */
27
abstract class XoopsMySQLDatabase extends XoopsDatabase
28
{
29
    /**
30
     * Database connection
31
     *
32
     * @var XoopsDatabase|mysqli
33
     */
34
    public $conn;
35
36
    /**
37
     * connect to the database
38
     *
39
     * @param bool $selectdb select the database now?
40
     * @return bool successful?
41
     */
42
    public function connect($selectdb = true)
43
    {
44
        if (!extension_loaded('mysqli')) {
45
            throw new \Exception('notrace:mysqli extension not loaded');
46
47
            return false;
0 ignored issues
show
Unused Code introduced by
return false is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
48
        }
49
50
        $this->allowWebChanges = ($_SERVER['REQUEST_METHOD'] !== 'GET');
51
52
        if ($selectdb) {
53
            $dbname = constant('XOOPS_DB_NAME');
54
        } else {
55
            $dbname = '';
56
        }
57
        mysqli_report(MYSQLI_REPORT_OFF);
58
        if (XOOPS_DB_PCONNECT == 1) {
0 ignored issues
show
introduced by
The condition XOOPS_DB_PCONNECT == 1 is always false.
Loading history...
59
            $this->conn = new mysqli('p:' . XOOPS_DB_HOST, XOOPS_DB_USER, XOOPS_DB_PASS, $dbname);
60
        } else {
61
            $this->conn = new mysqli(XOOPS_DB_HOST, XOOPS_DB_USER, XOOPS_DB_PASS, $dbname);
62
        }
63
64
        // errno is 0 if connect was successful
65
        if (0 !== $this->conn->connect_errno) {
66
            return false;
67
        }
68
69
        if (defined('XOOPS_DB_CHARSET') && ('' !== XOOPS_DB_CHARSET)) {
0 ignored issues
show
introduced by
The condition '' !== XOOPS_DB_CHARSET is always false.
Loading history...
70
            // $this->queryF("SET NAMES '" . XOOPS_DB_CHARSET . "'");
71
            $this->conn->set_charset(XOOPS_DB_CHARSET);
72
        }
73
        $this->queryF('SET SQL_BIG_SELECTS = 1');
74
75
        return true;
76
    }
77
78
    /**
79
     * generate an ID for a new row
80
     *
81
     * This is for compatibility only. Will always return 0, because MySQL supports
82
     * autoincrement for primary keys.
83
     *
84
     * @param string $sequence name of the sequence from which to get the next ID
85
     * @return int always 0, because mysql has support for autoincrement
86
     */
87
    public function genId($sequence)
0 ignored issues
show
Unused Code introduced by
The parameter $sequence is not used and could be removed. ( Ignorable by Annotation )

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

87
    public function genId(/** @scrutinizer ignore-unused */ $sequence)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
88
    {
89
        return 0; // will use auto_increment
90
    }
91
92
    /**
93
     * Get a result row as an enumerated array
94
     *
95
     * @param \mysqli_result $result
96
     *
97
     * @return array|false false on end of data
98
     */
99
    public function fetchRow($result)
100
    {
101
        $row = @mysqli_fetch_row($result);
102
        return $row ?? false;
103
    }
104
105
    /**
106
     * Fetch a result row as an associative array
107
     *
108
     * @param \mysqli_result $result
109
     *
110
     * @return array|false false on end of data
111
     */
112
    public function fetchArray($result)
113
    {
114
        $row = @mysqli_fetch_assoc($result);
115
        return $row ?? false;
116
117
    }
118
119
    /**
120
     * Fetch a result row as an associative array
121
     *
122
     * @param \mysqli_result $result
123
     *
124
     * @return array|false false on end of data
125
     */
126
    public function fetchBoth($result)
127
    {
128
        $row = @mysqli_fetch_array($result, MYSQLI_BOTH);
129
        return $row ?? false;
130
    }
131
132
    /**
133
     * XoopsMySQLDatabase::fetchObject()
134
     *
135
     * @param \mysqli_result $result
136
     * @return stdClass|false false on end of data
137
     */
138
    public function fetchObject($result)
139
    {
140
        $row = @mysqli_fetch_object($result);
141
        return $row ?? false;
142
    }
143
144
    /**
145
     * Get the ID generated from the previous INSERT operation
146
     *
147
     * @return int|string
148
     */
149
    public function getInsertId()
150
    {
151
        return mysqli_insert_id($this->conn);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_insert_id() does only seem to accept mysqli, 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

151
        return mysqli_insert_id(/** @scrutinizer ignore-type */ $this->conn);
Loading history...
152
    }
153
154
    /**
155
     * Get number of rows in result
156
     *
157
     * @param \mysqli_result $result
158
     *
159
     * @return int
160
     */
161
    public function getRowsNum($result)
162
    {
163
        return (int)@mysqli_num_rows($result);
164
    }
165
166
    /**
167
     * Get number of affected rows
168
     *
169
     * @return int
170
     */
171
    public function getAffectedRows()
172
    {
173
        return (int)mysqli_affected_rows($this->conn);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_affected_rows() does only seem to accept mysqli, 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

173
        return (int)mysqli_affected_rows(/** @scrutinizer ignore-type */ $this->conn);
Loading history...
174
    }
175
176
    /**
177
     * Close MySQL connection
178
     *
179
     * @return void
180
     */
181
    public function close()
182
    {
183
        mysqli_close($this->conn);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_close() does only seem to accept mysqli, 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

183
        mysqli_close(/** @scrutinizer ignore-type */ $this->conn);
Loading history...
184
    }
185
186
    /**
187
     * will free all memory associated with the result identifier result.
188
     *
189
     * @param \mysqli_result $result result
190
     *
191
     * @return void
192
     */
193
    public function freeRecordSet($result)
194
    {
195
        mysqli_free_result($result);
196
    }
197
198
    /**
199
     * Returns the text of the error message from previous MySQL operation
200
     *
201
     * @return string Returns the error text from the last MySQL function, or '' (the empty string) if no error occurred.
202
     */
203
    public function error()
204
    {
205
        return @mysqli_error($this->conn);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_error() does only seem to accept mysqli, 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

205
        return @mysqli_error(/** @scrutinizer ignore-type */ $this->conn);
Loading history...
206
    }
207
208
    /**
209
     * Returns the numerical value of the error message from previous MySQL operation
210
     *
211
     * @return int Returns the error number from the last MySQL function, or 0 (zero) if no error occurred.
212
     */
213
    public function errno()
214
    {
215
        return @mysqli_errno($this->conn);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_errno() does only seem to accept mysqli, 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

215
        return @mysqli_errno(/** @scrutinizer ignore-type */ $this->conn);
Loading history...
216
    }
217
218
    /**
219
     * Returns escaped string text with single quotes around it to be safely stored in database
220
     *
221
     * @param string $str unescaped string text
222
     * @return string escaped string text with single quotes around
223
     * @deprecated : delegate to exec().
224
     */
225
    public function quoteString($str)
226
    {
227
228
        if (is_object($GLOBALS['xoopsLogger'])) {
229
            $GLOBALS['xoopsLogger']->addDeprecated(__METHOD__ . " is deprecated since XOOPS 2.5.12, please use 'quote()' instead.");
230
        }
231
232
        return $this->quote($str);
233
    }
234
235
    /**
236
     * Quotes a string for use in a query.
237
     *
238
     * @param string $string string to quote/escape for use in query
239
     *
240
     * @return string
241
     */
242
    public function quote($string)
243
    {
244
        $quoted = $this->escape($string);
245
        return "'{$quoted}'";
246
    }
247
248
    /**
249
     * Escapes a string for use in a query. Does not add surrounding quotes.
250
     *
251
     * @param string $string string to escape
252
     *
253
     * @return string
254
     */
255
    public function escape($string)
256
    {
257
        return mysqli_real_escape_string($this->conn, (string) $string);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_real_escape_string() does only seem to accept mysqli, 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

257
        return mysqli_real_escape_string(/** @scrutinizer ignore-type */ $this->conn, (string) $string);
Loading history...
258
    }
259
260
    /**
261
     * perform a query on the database
262
     *
263
     * @param string $sql   a valid MySQL query
264
     * @param int    $limit number of records to return
265
     * @param int    $start offset of first record to return
266
     * @return mysqli_result|bool query result or FALSE if successful
267
     *                      or TRUE if successful and no result
268
     */
269
    public function queryF($sql, $limit = 0, $start = 0)
270
    {
271
        if (!empty($limit)) {
272
            if (empty($start)) {
273
                $start = 0;
274
            }
275
            $sql .= ' LIMIT ' . (int)$start . ', ' . (int)$limit;
276
        }
277
        $this->logger->startTime('query_time');
278
        $result = mysqli_query($this->conn, $sql);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_query() does only seem to accept mysqli, 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

278
        $result = mysqli_query(/** @scrutinizer ignore-type */ $this->conn, $sql);
Loading history...
279
        $this->logger->stopTime('query_time');
280
        $t = $this->logger->dumpTime('query_time', true);
281
282
        if ($result) {
283
            $this->logger->addQuery($sql, null, null, $t);
284
            return $result;             // mysqli_result for SELECT, true for writes
285
        } else {
286
            $this->logger->addQuery($sql, $this->error(), $this->errno(), $t);
287
            return false;
288
        }
289
    }
290
291
    /**
292
     * perform a query
293
     *
294
     * This method is empty and does nothing! It should therefore only be
295
     * used if nothing is exactly what you want done! ;-)
296
     *
297
     * @param string $sql   a valid MySQL query
298
     * @param int|null    $limit number of records to return
299
     * @param int|null    $start offset of first record to return
300
     *
301
     * @return \mysqli_result|bool query result or FALSE if successful
302
     *                      or TRUE if successful and no result
303
     */
304
    public function query(string $sql, ?int $limit = null, ?int $start = null)
305
    {
306
        // Optional: dev-only guard to catch misuse (writes via query()).
307
        if (defined('XOOPS_DB_STRICT') && XOOPS_DB_STRICT) {
0 ignored issues
show
Bug introduced by
The constant XOOPS_DB_STRICT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
308
            // Treat these as read-like; everything else is suspicious in query().
309
            if (!preg_match('/^\s*(SELECT|WITH|SHOW|DESCRIBE|EXPLAIN)\b/i', $sql)) {
310
                if (isset($GLOBALS['xoopsLogger']) && is_object($GLOBALS['xoopsLogger'])) {
311
                    $GLOBALS['xoopsLogger']->warning('query() called with a mutating statement; use exec() instead');
312
                } else {
313
                    trigger_error('query() called with a mutating statement; use exec() instead', E_USER_WARNING);
314
                }
315
            }
316
        }
317
318
        // Append pagination if requested (null = no pagination)
319
        if ($limit !== null) {
320
            $start = max(0, $start ?? 0);
321
            // MySQL supports both "LIMIT count OFFSET start" and "LIMIT start, count".
322
            // Using the former improves cross-driver readability.
323
            $sql .= ' LIMIT ' . (int)$limit . ' OFFSET ' . $start;
324
        }
325
326
        // No error suppression: handle failures explicitly
327
        $res = mysqli_query($this->conn, $sql);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_query() does only seem to accept mysqli, 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

327
        $res = mysqli_query(/** @scrutinizer ignore-type */ $this->conn, $sql);
Loading history...
328
329
        if ($res === false) {
330
            // Surface diagnostics; error()/errno() will also reflect this.
331
            if (isset($GLOBALS['xoopsLogger']) && is_object($GLOBALS['xoopsLogger'])) {
332
                $GLOBALS['xoopsLogger']->error('DB query failed', [
333
                    'errno' => mysqli_errno($this->conn),
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_errno() does only seem to accept mysqli, 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

333
                    'errno' => mysqli_errno(/** @scrutinizer ignore-type */ $this->conn),
Loading history...
334
                    'error' => mysqli_error($this->conn),
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_error() does only seem to accept mysqli, 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

334
                    'error' => mysqli_error(/** @scrutinizer ignore-type */ $this->conn),
Loading history...
335
                    // 'sql' => $this->redactSql($sql), // if you have a redactor
336
                ]);
337
            }
338
            return false;
339
        }
340
341
        // For proper SELECT-like statements, $res is a mysqli_result.
342
        // If someone sent a write by mistake, mysqli_query() returns true; we pass it through.
343
        return $res;
344
    }
345
346
347
    /**
348
     * perform queries from SQL dump file in a batch
349
     *
350
     * @param string $file file path to an SQL dump file
351
     * @return bool FALSE if failed reading SQL file or TRUE if the file has been read and queries executed
352
     */
353
    public function queryFromFile($file)
354
    {
355
        if (false !== ($fp = fopen($file, 'r'))) {
356
            include_once XOOPS_ROOT_PATH . '/class/database/sqlutility.php';
357
            $sql_queries = trim(fread($fp, filesize($file)));
358
            SqlUtility::splitMySqlFile($pieces, $sql_queries);
359
            foreach ($pieces as $query) {
360
                // [0] contains the prefixed query
361
                // [4] contains unprefixed table name
362
                $prefixed_query = SqlUtility::prefixQuery(trim($query), $this->prefix());
363
                if ($prefixed_query != false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
364
                    $this->query($prefixed_query[0]);
365
                }
366
            }
367
368
            return true;
369
        }
370
371
        return false;
372
    }
373
374
    /**
375
     * Get field name
376
     *
377
     * @param \mysqli_result $result query result
378
     * @param int           $offset numerical field index
379
     *
380
     * @return string
381
     */
382
    public function getFieldName($result, $offset)
383
    {
384
        return $result->fetch_field_direct($offset)->name;
385
    }
386
387
    /**
388
     * Get field type
389
     *
390
     * @param \mysqli_result $result query result
391
     * @param int           $offset numerical field index
392
     *
393
     * @return string
394
     */
395
    public function getFieldType($result, $offset)
396
    {
397
        $typecode = $result->fetch_field_direct($offset)->type;
398
        switch ($typecode) {
399
            case MYSQLI_TYPE_DECIMAL:
400
            case MYSQLI_TYPE_NEWDECIMAL:
401
                $type = 'decimal';
402
                break;
403
            case MYSQLI_TYPE_BIT:
404
                $type = 'bit';
405
                break;
406
            case MYSQLI_TYPE_TINY:
407
            case MYSQLI_TYPE_CHAR:
408
                $type = 'tinyint';
409
                break;
410
            case MYSQLI_TYPE_SHORT:
411
                $type = 'smallint';
412
                break;
413
            case MYSQLI_TYPE_LONG:
414
                $type = 'int';
415
                break;
416
            case MYSQLI_TYPE_FLOAT:
417
                $type = 'float';
418
                break;
419
            case MYSQLI_TYPE_DOUBLE:
420
                $type = 'double';
421
                break;
422
            case MYSQLI_TYPE_NULL:
423
                $type = 'NULL';
424
                break;
425
            case MYSQLI_TYPE_TIMESTAMP:
426
                $type = 'timestamp';
427
                break;
428
            case MYSQLI_TYPE_LONGLONG:
429
                $type = 'bigint';
430
                break;
431
            case MYSQLI_TYPE_INT24:
432
                $type = 'mediumint';
433
                break;
434
            case MYSQLI_TYPE_NEWDATE:
435
            case MYSQLI_TYPE_DATE:
436
                $type = 'date';
437
                break;
438
            case MYSQLI_TYPE_TIME:
439
                $type = 'time';
440
                break;
441
            case MYSQLI_TYPE_DATETIME:
442
                $type = 'datetime';
443
                break;
444
            case MYSQLI_TYPE_YEAR:
445
                $type = 'year';
446
                break;
447
            case MYSQLI_TYPE_INTERVAL:
448
                $type = 'interval';
449
                break;
450
            case MYSQLI_TYPE_ENUM:
451
                $type = 'enum';
452
                break;
453
            case MYSQLI_TYPE_SET:
454
                $type = 'set';
455
                break;
456
            case MYSQLI_TYPE_TINY_BLOB:
457
                $type = 'tinyblob';
458
                break;
459
            case MYSQLI_TYPE_MEDIUM_BLOB:
460
                $type = 'mediumblob';
461
                break;
462
            case MYSQLI_TYPE_LONG_BLOB:
463
                $type = 'longblob';
464
                break;
465
            case MYSQLI_TYPE_BLOB:
466
                $type = 'blob';
467
                break;
468
            case MYSQLI_TYPE_VAR_STRING:
469
                $type = 'varchar';
470
                break;
471
            case MYSQLI_TYPE_STRING:
472
                $type = 'char';
473
                break;
474
            case MYSQLI_TYPE_GEOMETRY:
475
                $type = 'geometry';
476
                break;
477
            default:
478
                $type = 'unknown';
479
                break;
480
        }
481
482
        return $type;
483
    }
484
485
    /**
486
     * Get number of fields in result
487
     *
488
     * @param \mysqli_result $result query result
489
     *
490
     * @return int
491
     */
492
    public function getFieldsNum($result)
493
    {
494
        return mysqli_num_fields($result);
495
    }
496
497
    /**
498
     * getServerVersion get version of the mysql server
499
     *
500
     * @return string
501
     */
502
    public function getServerVersion()
503
    {
504
        return mysqli_get_server_info($this->conn);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_get_server_info() does only seem to accept mysqli, 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

504
        return mysqli_get_server_info(/** @scrutinizer ignore-type */ $this->conn);
Loading history...
505
    }
506
507
    /**
508
     * Test the passed result to determine if it is a valid result set
509
     *
510
     * @param mixed $result value to test
511
     *
512
     * @return bool true if $result is a database result set, otherwise false
513
     */
514
    public function isResultSet($result)
515
    {
516
        return is_a($result, 'mysqli_result');
517
    }
518
519
    public function exec(string $sql): bool
520
    {
521
        // Optional dev-only guard: flag accidental read statements passed to exec()
522
        if (defined('XOOPS_DB_STRICT') && XOOPS_DB_STRICT) {
0 ignored issues
show
Bug introduced by
The constant XOOPS_DB_STRICT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
523
            if (preg_match('/^\s*(?:SELECT|WITH|SHOW|DESCRIBE|EXPLAIN)\b/i', $sql)) {
524
                if (isset($GLOBALS['xoopsLogger']) && is_object($GLOBALS['xoopsLogger'])) {
525
                    $GLOBALS['xoopsLogger']->warning('exec() called with a read-only statement');
526
                } else {
527
                    trigger_error('exec() called with a read-only statement', E_USER_WARNING);
528
                }
529
            }
530
        }
531
532
        // No error suppression: let us see and handle failures explicitly
533
        $res = mysqli_query($this->conn, $sql);
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_query() does only seem to accept mysqli, 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

533
        $res = mysqli_query(/** @scrutinizer ignore-type */ $this->conn, $sql);
Loading history...
534
535
        if ($res === false) {
536
            // Normalize failure handling; error() / errno() will reflect these too
537
            if (isset($GLOBALS['xoopsLogger']) && is_object($GLOBALS['xoopsLogger'])) {
538
                $GLOBALS['xoopsLogger']->error('DB exec failed', [
539
                    'errno' => mysqli_errno($this->conn),
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_errno() does only seem to accept mysqli, 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

539
                    'errno' => mysqli_errno(/** @scrutinizer ignore-type */ $this->conn),
Loading history...
540
                    'error' => mysqli_error($this->conn),
0 ignored issues
show
Bug introduced by
It seems like $this->conn can also be of type XoopsDatabase; however, parameter $mysql of mysqli_error() does only seem to accept mysqli, 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

540
                    'error' => mysqli_error(/** @scrutinizer ignore-type */ $this->conn),
Loading history...
541
                    // 'sql' => $this->redactSql($sql)  // use if you have a redactor
542
                ]);
543
            }
544
            return false;
545
        }
546
547
        // If someone passes a SELECT by mistake, mysqli returns a result; free it and treat as success
548
        if ($res instanceof \mysqli_result) {
549
            mysqli_free_result($res);
550
        }
551
552
        return true; // mysqli_query() returned true or a freed result set
553
    }
554
}
555
556
/**
557
 * Safe Connection to a MySQL database.
558
 *
559
 * Delegates to parent; signature matches parent for LSP.
560
 */
561
class XoopsMySQLDatabaseSafe extends XoopsMySQLDatabase
562
{
563
    /**
564
     * perform a query on the database
565
     *
566
     * @param string $sql   a valid MySQL query
567
     * @param int    $limit number of records to return
568
     * @param int    $start offset of first record to return
569
     * @return mysqli_result|bool query result or FALSE if successful
570
     *                      or TRUE if successful and no result
571
     */
572
    public function query($sql, $limit = 0, $start = 0)
573
    {
574
        return parent::query($sql, $limit ?: null, $start ?: null);
575
    }
576
}
577
578
/**
579
 * Read-Only connection to a MySQL database.
580
 *
581
 * This class allows only SELECT queries to be performed through its
582
 * {@link query()} method for security reasons.
583
 *
584
 * @author              Kazumi Ono <[email protected]>
585
 * @copyright       (c) 2000-2025 XOOPS Project (https://xoops.org)
586
 * @package             class
587
 * @subpackage          database
588
 */
589
class XoopsMySQLDatabaseProxy extends XoopsMySQLDatabase
590
{
591
    /**
592
     * perform a query on the database
593
     *
594
     * this method allows only SELECT queries for safety.
595
     *
596
     * @param string $sql   a valid MySQL query
597
     * @param int    $limit number of records to return
598
     * @param int    $start offset of first record to return
599
     *
600
     * @return mysqli_result|bool query result or FALSE if successful
601
     *                      or TRUE if successful and no result
602
     */
603
    public function query(string $sql, ?int $limit = null, ?int $start = null)
604
    {
605
        $sql = ltrim($sql);
606
        if (!$this->allowWebChanges && stripos($sql, 'select') !== 0) {
607
            trigger_error('Database updates are not allowed during processing of a GET request', E_USER_WARNING);
608
609
            return false;
610
        }
611
        // Execute via queryF() to preserve legacy path (and LIMIT semantics)
612
        if ($limit !== null) {
613
            $start = max(0, $start ?? 0);
614
            return $this->queryF($sql, (int)$limit, (int)$start);
615
    }
616
        return $this->queryF($sql);
617
    }
618
619
}
620