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; |
|
|
|
|
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) { |
|
|
|
|
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)) { |
|
|
|
|
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) |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Close MySQL connection |
178
|
|
|
* |
179
|
|
|
* @return void |
180
|
|
|
*/ |
181
|
|
|
public function close() |
182
|
|
|
{ |
183
|
|
|
mysqli_close($this->conn); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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) { |
|
|
|
|
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); |
|
|
|
|
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), |
|
|
|
|
334
|
|
|
'error' => mysqli_error($this->conn), |
|
|
|
|
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) { |
|
|
|
|
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); |
|
|
|
|
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) { |
|
|
|
|
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); |
|
|
|
|
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), |
|
|
|
|
540
|
|
|
'error' => mysqli_error($this->conn), |
|
|
|
|
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
|
|
|
|
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
orexit
statements that have been added for debug purposes.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.