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-2019 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 <onokazu@xoops.org>
18: * @author Rodney Fulk <redheadedrod@hotmail.com>
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
28: * @author Kazumi Ono <onokazu@xoops.org>
29: * @copyright (c) 2000-2019 XOOPS Project (https://xoops.org)
30: * @package class
31: * @subpackage database
32: */
33: abstract class XoopsMySQLDatabase extends XoopsDatabase
34: {
35: /**
36: * Database connection
37: *
38: * @var XoopsDatabase|mysqli
39: */
40: public $conn;
41:
42: /**
43: * connect to the database
44: *
45: * @param bool $selectdb select the database now?
46: * @return bool successful?
47: */
48: public function connect($selectdb = true)
49: {
50: if (!extension_loaded('mysqli')) {
51: trigger_error('notrace:mysqli extension not loaded', E_USER_ERROR);
52:
53: return false;
54: }
55:
56: $this->allowWebChanges = ($_SERVER['REQUEST_METHOD'] !== 'GET');
57:
58: if ($selectdb) {
59: $dbname = constant('XOOPS_DB_NAME');
60: } else {
61: $dbname = '';
62: }
63: mysqli_report(MYSQLI_REPORT_OFF);
64: if (XOOPS_DB_PCONNECT == 1) {
65: $this->conn = new mysqli('p:' . XOOPS_DB_HOST, XOOPS_DB_USER, XOOPS_DB_PASS, $dbname);
66: } else {
67: $this->conn = new mysqli(XOOPS_DB_HOST, XOOPS_DB_USER, XOOPS_DB_PASS, $dbname);
68: }
69:
70: // errno is 0 if connect was successful
71: if (0 !== $this->conn->connect_errno) {
72: return false;
73: }
74:
75: if (defined('XOOPS_DB_CHARSET') && ('' !== XOOPS_DB_CHARSET)) {
76: // $this->queryF("SET NAMES '" . XOOPS_DB_CHARSET . "'");
77: $this->conn->set_charset(XOOPS_DB_CHARSET);
78: }
79: $this->queryF('SET SQL_BIG_SELECTS = 1');
80:
81: return true;
82: }
83:
84: /**
85: * generate an ID for a new row
86: *
87: * This is for compatibility only. Will always return 0, because MySQL supports
88: * autoincrement for primary keys.
89: *
90: * @param string $sequence name of the sequence from which to get the next ID
91: * @return int always 0, because mysql has support for autoincrement
92: */
93: public function genId($sequence)
94: {
95: return 0; // will use auto_increment
96: }
97:
98: /**
99: * Get a result row as an enumerated array
100: *
101: * @param mysqli_result $result
102: *
103: * @return array|false false on end of data
104: */
105: public function fetchRow($result)
106: {
107: $row = @mysqli_fetch_row($result);
108: return (null === $row) ? false : $row;
109: }
110:
111: /**
112: * Fetch a result row as an associative array
113: *
114: * @param mysqli_result $result
115: *
116: * @return array|false false on end of data
117: */
118: public function fetchArray($result)
119: {
120: $row = @mysqli_fetch_assoc($result);
121: return (null === $row) ? false : $row;
122:
123: }
124:
125: /**
126: * Fetch a result row as an associative array
127: *
128: * @param mysqli_result $result
129: *
130: * @return array|false false on end of data
131: */
132: public function fetchBoth($result)
133: {
134: $row = @mysqli_fetch_array($result, MYSQLI_BOTH);
135: return (null === $row) ? false : $row;
136: }
137:
138: /**
139: * XoopsMySQLDatabase::fetchObject()
140: *
141: * @param mysqli_result $result
142: * @return stdClass|false false on end of data
143: */
144: public function fetchObject($result)
145: {
146: $row = @mysqli_fetch_object($result);
147: return (null === $row) ? false : $row;
148: }
149:
150: /**
151: * Get the ID generated from the previous INSERT operation
152: *
153: * @return int|string
154: */
155: public function getInsertId()
156: {
157: return mysqli_insert_id($this->conn);
158: }
159:
160: /**
161: * Get number of rows in result
162: *
163: * @param mysqli_result $result
164: *
165: * @return int
166: */
167: public function getRowsNum($result)
168: {
169: return (int)@mysqli_num_rows($result);
170: }
171:
172: /**
173: * Get number of affected rows
174: *
175: * @return int
176: */
177: public function getAffectedRows()
178: {
179: return (int)mysqli_affected_rows($this->conn);
180: }
181:
182: /**
183: * Close MySQL connection
184: *
185: * @return void
186: */
187: public function close()
188: {
189: mysqli_close($this->conn);
190: }
191:
192: /**
193: * will free all memory associated with the result identifier result.
194: *
195: * @param mysqli_result $result result
196: *
197: * @return void
198: */
199: public function freeRecordSet($result)
200: {
201: mysqli_free_result($result);
202: }
203:
204: /**
205: * Returns the text of the error message from previous MySQL operation
206: *
207: * @return string Returns the error text from the last MySQL function, or '' (the empty string) if no error occurred.
208: */
209: public function error()
210: {
211: return @mysqli_error($this->conn);
212: }
213:
214: /**
215: * Returns the numerical value of the error message from previous MySQL operation
216: *
217: * @return int Returns the error number from the last MySQL function, or 0 (zero) if no error occurred.
218: */
219: public function errno()
220: {
221: return @mysqli_errno($this->conn);
222: }
223:
224: /**
225: * Returns escaped string text with single quotes around it to be safely stored in database
226: *
227: * @param string $str unescaped string text
228: * @return string escaped string text with single quotes around
229: */
230: public function quoteString($str)
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 = $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: $query_time = $this->logger->dumpTime('query_time', true);
281: if ($result) {
282: $this->logger->addQuery($sql, null, null, $query_time);
283:
284: return $result;
285: } else {
286: $this->logger->addQuery($sql, $this->error(), $this->errno(), $query_time);
287:
288: return false;
289: }
290: }
291:
292: /**
293: * perform a query
294: *
295: * This method is empty and does nothing! It should therefore only be
296: * used if nothing is exactly what you want done! ;-)
297: *
298: * @param string $sql a valid MySQL query
299: * @param int $limit number of records to return
300: * @param int $start offset of first record to return
301: *
302: * @return mysqli_result|bool query result or FALSE if successful
303: * or TRUE if successful and no result
304: */
305: abstract public function query($sql, $limit = 0, $start = 0);
306:
307: /**
308: * perform queries from SQL dump file in a batch
309: *
310: * @param string $file file path to an SQL dump file
311: * @return bool FALSE if failed reading SQL file or TRUE if the file has been read and queries executed
312: */
313: public function queryFromFile($file)
314: {
315: if (false !== ($fp = fopen($file, 'r'))) {
316: include_once XOOPS_ROOT_PATH . '/class/database/sqlutility.php';
317: $sql_queries = trim(fread($fp, filesize($file)));
318: SqlUtility::splitMySqlFile($pieces, $sql_queries);
319: foreach ($pieces as $query) {
320: // [0] contains the prefixed query
321: // [4] contains unprefixed table name
322: $prefixed_query = SqlUtility::prefixQuery(trim($query), $this->prefix());
323: if ($prefixed_query != false) {
324: $this->query($prefixed_query[0]);
325: }
326: }
327:
328: return true;
329: }
330:
331: return false;
332: }
333:
334: /**
335: * Get field name
336: *
337: * @param mysqli_result $result query result
338: * @param int $offset numerical field index
339: *
340: * @return string
341: */
342: public function getFieldName($result, $offset)
343: {
344: return $result->fetch_field_direct($offset)->name;
345: }
346:
347: /**
348: * Get field type
349: *
350: * @param mysqli_result $result query result
351: * @param int $offset numerical field index
352: *
353: * @return string
354: */
355: public function getFieldType($result, $offset)
356: {
357: $typecode = $result->fetch_field_direct($offset)->type;
358: switch ($typecode) {
359: case MYSQLI_TYPE_DECIMAL:
360: case MYSQLI_TYPE_NEWDECIMAL:
361: $type = 'decimal';
362: break;
363: case MYSQLI_TYPE_BIT:
364: $type = 'bit';
365: break;
366: case MYSQLI_TYPE_TINY:
367: case MYSQLI_TYPE_CHAR:
368: $type = 'tinyint';
369: break;
370: case MYSQLI_TYPE_SHORT:
371: $type = 'smallint';
372: break;
373: case MYSQLI_TYPE_LONG:
374: $type = 'int';
375: break;
376: case MYSQLI_TYPE_FLOAT:
377: $type = 'float';
378: break;
379: case MYSQLI_TYPE_DOUBLE:
380: $type = 'double';
381: break;
382: case MYSQLI_TYPE_NULL:
383: $type = 'NULL';
384: break;
385: case MYSQLI_TYPE_TIMESTAMP:
386: $type = 'timestamp';
387: break;
388: case MYSQLI_TYPE_LONGLONG:
389: $type = 'bigint';
390: break;
391: case MYSQLI_TYPE_INT24:
392: $type = 'mediumint';
393: break;
394: case MYSQLI_TYPE_NEWDATE:
395: case MYSQLI_TYPE_DATE:
396: $type = 'date';
397: break;
398: case MYSQLI_TYPE_TIME:
399: $type = 'time';
400: break;
401: case MYSQLI_TYPE_DATETIME:
402: $type = 'datetime';
403: break;
404: case MYSQLI_TYPE_YEAR:
405: $type = 'year';
406: break;
407: case MYSQLI_TYPE_INTERVAL:
408: $type = 'interval';
409: break;
410: case MYSQLI_TYPE_ENUM:
411: $type = 'enum';
412: break;
413: case MYSQLI_TYPE_SET:
414: $type = 'set';
415: break;
416: case MYSQLI_TYPE_TINY_BLOB:
417: $type = 'tinyblob';
418: break;
419: case MYSQLI_TYPE_MEDIUM_BLOB:
420: $type = 'mediumblob';
421: break;
422: case MYSQLI_TYPE_LONG_BLOB:
423: $type = 'longblob';
424: break;
425: case MYSQLI_TYPE_BLOB:
426: $type = 'blob';
427: break;
428: case MYSQLI_TYPE_VAR_STRING:
429: $type = 'varchar';
430: break;
431: case MYSQLI_TYPE_STRING:
432: $type = 'char';
433: break;
434: case MYSQLI_TYPE_GEOMETRY:
435: $type = 'geometry';
436: break;
437: default:
438: $type = 'unknown';
439: break;
440: }
441:
442: return $type;
443: }
444:
445: /**
446: * Get number of fields in result
447: *
448: * @param mysqli_result $result query result
449: *
450: * @return int
451: */
452: public function getFieldsNum($result)
453: {
454: return mysqli_num_fields($result);
455: }
456:
457: /**
458: * getServerVersion get version of the mysql server
459: *
460: * @return string
461: */
462: public function getServerVersion()
463: {
464: return mysqli_get_server_info($this->conn);
465: }
466:
467: /**
468: * Test the passed result to determine if it is a valid result set
469: *
470: * @param mixed $result value to test
471: *
472: * @return bool true if $result is a database result set, otherwise false
473: */
474: public function isResultSet($result)
475: {
476: return is_a($result, 'mysqli_result');
477: }
478: }
479:
480: /**
481: * Safe Connection to a MySQL database.
482: *
483: * @author Kazumi Ono <onokazu@xoops.org>
484: * @copyright (c) 2000-2019 XOOPS Project (https://xoops.org)
485: * @package kernel
486: * @subpackage database
487: */
488: class XoopsMySQLDatabaseSafe extends XoopsMySQLDatabase
489: {
490: /**
491: * perform a query on the database
492: *
493: * @param string $sql a valid MySQL query
494: * @param int $limit number of records to return
495: * @param int $start offset of first record to return
496: * @return mysqli_result|bool query result or FALSE if successful
497: * or TRUE if successful and no result
498: */
499: public function query($sql, $limit = 0, $start = 0)
500: {
501: return $this->queryF($sql, $limit, $start);
502: }
503: }
504:
505: /**
506: * Read-Only connection to a MySQL database.
507: *
508: * This class allows only SELECT queries to be performed through its
509: * {@link query()} method for security reasons.
510: *
511: * @author Kazumi Ono <onokazu@xoops.org>
512: * @copyright (c) 2000-2019 XOOPS Project (https://xoops.org)
513: * @package class
514: * @subpackage database
515: */
516: class XoopsMySQLDatabaseProxy extends XoopsMySQLDatabase
517: {
518: /**
519: * perform a query on the database
520: *
521: * this method allows only SELECT queries for safety.
522: *
523: * @param string $sql a valid MySQL query
524: * @param int $limit number of records to return
525: * @param int $start offset of first record to return
526: *
527: * @return mysqli_result|bool query result or FALSE if successful
528: * or TRUE if successful and no result
529: */
530: public function query($sql, $limit = 0, $start = 0)
531: {
532: $sql = ltrim($sql);
533: if (!$this->allowWebChanges && strtolower(substr($sql, 0, 6)) !== 'select') {
534: trigger_error('Database updates are not allowed during processing of a GET request', E_USER_WARNING);
535:
536: return false;
537: }
538:
539: return $this->queryF($sql, $limit, $start);
540: }
541: }
542: