Passed
Push — master ( d9e5dd...36764d )
by Spuds
01:07 queued 26s
created

Database_MySQL::_clean_4byte_chars()   B

Complexity

Conditions 7
Paths 3

Size

Total Lines 39
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 18.9628

Importance

Changes 0
Metric Value
cc 7
eloc 20
dl 0
loc 39
rs 8.6666
c 0
b 0
f 0
nc 3
nop 1
ccs 9
cts 24
cp 0.375
crap 18.9628

3 Methods

Rating   Name   Duplication   Size   Complexity  
A Database_MySQL::free_result() 0 4 1
A Database_MySQL::insert_id() 0 4 2
A Database_MySQL::fetch_row() 0 4 1
1
<?php
2
3
/**
4
 * This file has all the main functions in it that relate to the mysql database.
5
 *
6
 * @name      ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
9
 *
10
 * This file contains code covered by:
11
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
12
 * license:  	BSD, See included LICENSE.TXT for terms and conditions.
13
 *
14
 * copyright:	2004-2011, GreyWyvern - All rights reserved.
15
 * license:  	BSD, See included LICENSE.TXT for terms and conditions.
16
 *
17
 * @version 1.1.9
18
 *
19
 */
20
21
// Let's define the name of the class so that we will be able to use it in the instantiations
22
if (!defined('DB_TYPE'))
23
	define('DB_TYPE', 'MySQL');
24
25
/**
26
 * SQL database class, implements database class to control mysql functions
27
 */
28
class Database_MySQL extends Database_Abstract
29
{
30
	/**
31
	 * Holds current instance of the class
32
	 *
33
	 * @var Database_MySQL
34
	 */
35
	private static $_db = null;
36
37
	/**
38
	 * Initializes a database connection.
39
	 * It returns the connection, if successful.
40
	 *
41
	 * @param string $db_server
42
	 * @param string $db_name
43
	 * @param string $db_user
44
	 * @param string $db_passwd
45
	 * @param string $db_prefix
46
	 * @param mixed[] $db_options
47
	 *
48
	 * @return mysqli|null
49
	 * @throws Elk_Exception
50
	 */
51
	public static function initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $db_options = array())
52
	{
53
		global $mysql_set_mode;
54
55
		// Initialize the instance... if not done already!
56
		if (self::$_db === null)
57
			self::$_db = new self();
58
59
		// Non-standard port
60
		if (!empty($db_options['port']))
61
			$db_port = (int) $db_options['port'];
62
		else
63
			$db_port = 0;
64
65
		// PHP 8.1 default is to throw exceptions, this reverts it to the <=php8 semantics
66
		mysqli_report(MYSQLI_REPORT_OFF);
67
68
		// Select the database. Maybe.
69
		if (empty($db_options['dont_select_db']))
70
			$connection = @mysqli_connect((!empty($db_options['persist']) ? 'p:' : '') . $db_server, $db_user, $db_passwd, $db_name, $db_port);
0 ignored issues
show
Bug introduced by
The call to mysqli_connect() has too few arguments starting with socket. ( Ignorable by Annotation )

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

70
			$connection = @/** @scrutinizer ignore-call */ mysqli_connect((!empty($db_options['persist']) ? 'p:' : '') . $db_server, $db_user, $db_passwd, $db_name, $db_port);

This check compares calls to functions or methods with their respective definitions. If the call has less 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...
71
		else
72
			$connection = @mysqli_connect((!empty($db_options['persist']) ? 'p:' : '') . $db_server, $db_user, $db_passwd, '', $db_port);
73
74
		// Something's wrong, show an error if its fatal (which we assume it is)
75
		if (!$connection)
76
		{
77
			if (!empty($db_options['non_fatal']))
78
				return null;
79
			else
80
				Errors::instance()->display_db_error();
81
		}
82
83
		self::$_db->_connection = $connection;
84
85
		// This makes it possible to automatically change the sql_mode and autocommit if needed.
86
		if (isset($mysql_set_mode) && $mysql_set_mode === true)
87
		{
88
			self::$_db->query('', 'SET sql_mode = \'\', AUTOCOMMIT = 1',
89
				array(),
90
				false
91
			);
92
		}
93
94
		// Few databases still have not set UTF-8 as their default input charset
95
		self::$_db->query('', '
96
			SET NAMES UTF8',
97
			array(
98
			)
99
		);
100
101
		// Unfortunately Elk still have some trouble dealing with the most recent settings of MySQL/Mariadb
102
		// disable ONLY_FULL_GROUP_BY
103
		self::$_db->query('', '
104
			SET sql_mode=(SELECT REPLACE(@@sql_mode, {string:disable}, {string:empty}))',
105
			array(
106
				'disable' => 'ONLY_FULL_GROUP_BY',
107
				'empty' => ''
108
			)
109
		);
110
111
		return $connection;
112
	}
113
114
	/**
115
	 * Fix up the prefix so it doesn't require the database to be selected.
116
	 *
117
	 * @param string $db_prefix
118
	 * @param string $db_name
119
	 *
120
	 * @return string
121
	 */
122
	public function fix_prefix($db_prefix, $db_name)
123
	{
124
		$db_prefix = is_numeric(substr($db_prefix, 0, 1)) ? $db_name . '.' . $db_prefix : '`' . $db_name . '`.' . $db_prefix;
125
126
		return $db_prefix;
127
	}
128
129
	/**
130
	 * Do a query.  Takes care of errors too.
131
	 *
132
	 * @param string $identifier
133
	 * @param string $db_string
134
	 * @param mixed[]|false $db_values = array()
135 38
	 * @param mysqli_result|false|null $connection = null
136
	 * @throws Elk_Exception
137 38
	 */
138
	public function query($identifier, $db_string, $db_values = array(), $connection = null)
139
	{
140
		global $db_show_debug, $time_start, $modSettings;
141
142
		// Comments that are allowed in a query are preg_removed.
143
		static $allowed_comments_from = array(
144
			'~\s+~s',
145 38
			'~/\*!40001 SQL_NO_CACHE \*/~',
146
			'~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~',
147
			'~/\*!40100 ON DUPLICATE KEY UPDATE id_msg = \d+ \*/~',
148
		);
149
		static $allowed_comments_to = array(
150
			' ',
151 38
			'',
152
			'',
153
			'',
154 38
		);
155
156
		// Decide which connection to use.
157 38
		$connection = $connection === null ? $this->_connection : $connection;
158
159 38
		// One more query....
160 38
		$this->_query_count++;
161
162
		if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override']))
163 38
			$this->error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__);
164 38
165
		// Use "ORDER BY null" to prevent Mysql doing filesorts for Group By clauses without an Order By
166 7
		if (strpos($db_string, 'GROUP BY') !== false && strpos($db_string, 'ORDER BY') === false && strpos($db_string, 'INSERT INTO') === false)
167 7
		{
168
			// Add before LIMIT
169
			if ($pos = strpos($db_string, 'LIMIT '))
170 6
				$db_string = substr($db_string, 0, $pos) . "\t\t\tORDER BY null\n" . substr($db_string, $pos, strlen($db_string));
171 7
			else
172
				// Append it.
173 38
				$db_string .= "\n\t\t\tORDER BY null";
174 38
		}
175
176 38
		if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false))
177 38
		{
178
			// Store these values for use in the callback function.
179
			$this->_db_callback_values = $db_values;
180 38
			$this->_db_callback_connection = $connection;
0 ignored issues
show
Documentation Bug introduced by
It seems like $connection can also be of type false. However, the property $_db_callback_connection is declared as type object|resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
181
182
			// Inject the values passed to this function.
183 38
			$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', array($this, 'replacement__callback'), $db_string);
184 38
185 38
			// No need for them any longer.
186
			$this->_db_callback_values = array();
187
			$this->_db_callback_connection = null;
188 38
		}
189 38
190
		// Debugging.
191
		if ($db_show_debug === true)
192
		{
193
			$debug = Debug::instance();
194
195
			// Get the file and line number this function was called.
196
			list ($file, $line) = $this->error_backtrace('', '', 'return', __FILE__, __LINE__);
197
198
			if (!empty($_SESSION['debug_redirect']))
199
			{
200
				$debug->merge_db($_SESSION['debug_redirect']);
201
				// @todo this may be off by 1
202
				$this->_query_count += count($_SESSION['debug_redirect']);
203
				$_SESSION['debug_redirect'] = array();
204
			}
205
206
			// Don't overload it.
207
			$st = microtime(true);
208
			$db_cache = array();
209
			$db_cache['q'] = $this->_query_count < 50 ? $db_string : '...';
210
			$db_cache['f'] = $file;
211
			$db_cache['l'] = $line;
212
			$db_cache['s'] = $st - $time_start;
213 38
		}
214 38
215 38
		// First, we clean strings out of the query, reduce whitespace, lowercase, and trim - so we can check it over.
216 38
		if (empty($modSettings['disableQueryCheck']))
217 38
		{
218 38
			$clean = '';
219
			$old_pos = 0;
220 38
			$pos = -1;
221 38
			while (true)
222 38
			{
223 32
				$pos = strpos($db_string, '\'', $pos + 1);
224
				if ($pos === false)
225 32
					break;
226
				$clean .= substr($db_string, $old_pos, $pos - $old_pos);
227 32
228 32
				while (true)
229 32
				{
230 32
					$pos1 = strpos($db_string, '\'', $pos + 1);
231 32
					$pos2 = strpos($db_string, '\\', $pos + 1);
232
					if ($pos1 === false)
233 32
						break;
234 32
					elseif ($pos2 === false || $pos2 > $pos1)
235
					{
236
						$pos = $pos1;
237 4
						break;
238 4
					}
239
240 32
					$pos = $pos2 + 1;
241 32
				}
242 32
243
				$clean .= ' %s ';
244 38
				$old_pos = $pos + 1;
245 38
			}
246
247
			$clean .= substr($db_string, $old_pos);
248 38
			$clean = trim(strtolower(preg_replace($allowed_comments_from, $allowed_comments_to, $clean)));
249 38
250
			// Comments?  We don't use comments in our queries, we leave 'em outside!
251 38
			if (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false)
252
				$fail = true;
253 38
			// Trying to change passwords, slow us down, or something?
254
			elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[_a-z])~s', $clean) != 0)
255
				$fail = true;
256 38
			elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
257 38
				$fail = true;
258 38
259
			if (!empty($fail) && function_exists('log_error'))
260 38
				$this->error_backtrace('Hacking attempt...', 'Hacking attempt...' . "\n" . $db_string, E_USER_ERROR, __FILE__, __LINE__);
261 38
		}
262
263
		if ($this->_unbuffered === false)
264
			$ret = @mysqli_query($connection, $db_string);
265
		else
266 38
			$ret = @mysqli_query($connection, $db_string, MYSQLI_USE_RESULT);
267 38
268 11
		// @deprecated since 1.1 - use skip_next_error method
269 11
		if (!empty($db_values['db_error_skip']))
270
		{
271 38
			$this->_skip_error = true;
272 38
		}
273
274
		if ($ret === false && $this->_skip_error === false)
275
		{
276
			$ret = $this->error($db_string, $connection);
277 38
		}
278 38
279 11
		// Revert not to skip errors
280 11
		if ($this->_skip_error === true)
281
		{
282
			$this->_skip_error = false;
283 38
		}
284 38
285
		// Debugging.
286
		if ($db_show_debug === true)
287
		{
288
			$db_cache['t'] = microtime(true) - $st;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $st does not seem to be defined for all execution paths leading up to this point.
Loading history...
289 38
			$debug->db_query($db_cache);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $debug does not seem to be defined for all execution paths leading up to this point.
Loading history...
290
		}
291
292
		return $ret;
293
	}
294
295
	/**
296
	 * Affected rows from previous operation.
297
	 *
298
	 * @param mysqli|null $connection
299
	 */
300
	public function affected_rows($connection = null)
301
	{
302
		return mysqli_affected_rows($connection === null ? $this->_connection : $connection);
0 ignored issues
show
Bug introduced by
It seems like $connection === null ? $...onnection : $connection can also be of type resource; 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

302
		return mysqli_affected_rows(/** @scrutinizer ignore-type */ $connection === null ? $this->_connection : $connection);
Loading history...
303 32
	}
304
305 32
	/**
306
	 * Last inserted id.
307 32
	 *
308 32
	 * @param string $table
309
	 * @param string|null $field = null
310 32
	 * @param mysqli|null $connection = null
311 32
	 */
312
	public function insert_id($table, $field = null, $connection = null)
313
	{
314 32
		// MySQL doesn't need the table or field information.
315 32
		return mysqli_insert_id($connection === null ? $this->_connection : $connection);
0 ignored issues
show
Bug introduced by
It seems like $connection === null ? $...onnection : $connection can also be of type resource; 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

315
		return mysqli_insert_id(/** @scrutinizer ignore-type */ $connection === null ? $this->_connection : $connection);
Loading history...
316
	}
317
318
	/**
319
	 * Fetch a row from the result set given as parameter.
320
	 * MySQL implementation doesn't use $counter parameter.
321
	 *
322
	 * @param mysqli_result $result
323
	 * @param integer|bool $counter = false
324
	 */
325
	public function fetch_row($result, $counter = false)
326
	{
327
		// Just delegate to MySQL's function
328
		return mysqli_fetch_row($result);
329
	}
330
331
	/**
332
	 * Free the resultset.
333
	 *
334
	 * @param mysqli_result $result
335
	 */
336
	public function free_result($result)
337
	{
338
		// Just delegate to MySQL's function
339
		mysqli_free_result($result);
340
	}
341 32
342
	/**
343
	 * Get the number of rows in the result.
344
	 *
345
	 * @param mysqli_result $result
346
	 */
347
	public function num_rows($result)
348
	{
349
		// Simply delegate to the native function
350
		return mysqli_num_rows($result);
351
	}
352
353
	/**
354
	 * Get the number of fields in the result set.
355
	 *
356
	 * @param mysqli_result $request
357
	 */
358
	public function num_fields($request)
359
	{
360
		return mysqli_num_fields($request);
361
	}
362
363
	/**
364
	 * Reset the internal result pointer.
365
	 *
366
	 * @param mysqli_result $request
367
	 * @param integer $counter
368
	 */
369
	public function data_seek($request, $counter)
370
	{
371
		// Delegate to native mysql function
372
		return mysqli_data_seek($request, $counter);
373
	}
374
375
	/**
376 9
	 * Do a transaction.
377
	 *
378 9
	 * @param string $type - the step to perform (i.e. 'begin', 'commit', 'rollback')
379
	 * @param mysqli|null $connection = null
380
	 */
381
	public function db_transaction($type = 'commit', $connection = null)
382
	{
383
		// Decide which connection to use
384
		$connection = $connection === null ? $this->_connection : $connection;
385
386
		if ($type == 'begin')
387
			return @mysqli_query($connection, 'BEGIN');
0 ignored issues
show
Bug introduced by
It seems like $connection can also be of type resource; 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

387
			return @mysqli_query(/** @scrutinizer ignore-type */ $connection, 'BEGIN');
Loading history...
388 15
		elseif ($type == 'rollback')
389
			return @mysqli_query($connection, 'ROLLBACK');
390
		elseif ($type == 'commit')
391 15
			return @mysqli_query($connection, 'COMMIT');
392
393
		return false;
394
	}
395
396
	/**
397
	 * Return last error string from the database server
398
	 *
399
	 * @param mysqli|null $connection = null
400
	 */
401 15
	public function last_error($connection = null)
402
	{
403
		// Decide which connection to use
404 15
		$connection = $connection === null ? $this->_connection : $connection;
405
406
		if (is_object($connection))
407
			return mysqli_error($connection);
408
	}
409
410
	/**
411
	 * Database error.
412 36
	 * Backtrace, log, try to fix.
413
	 *
414
	 * @param string      $db_string
415 36
	 * @param mysqli|null $connection = null
416 36
	 *
417
	 * @return bool
418
	 * @throws Elk_Exception
419
	 */
420
	public function error($db_string, $connection = null)
421
	{
422
		global $txt, $context, $webmaster_email, $modSettings, $db_persist;
423 19
		global $db_server, $db_user, $db_passwd, $db_name, $db_show_debug, $ssi_db_user, $ssi_db_passwd;
424
425
		// Get the file and line numbers.
426 19
		list ($file, $line) = $this->error_backtrace('', '', 'return', __FILE__, __LINE__);
427
428
		// Decide which connection to use
429
		$connection = $connection === null ? $this->_connection : $connection;
430
431
		// This is the error message...
432
		$query_error = mysqli_error($connection);
0 ignored issues
show
Bug introduced by
It seems like $connection can also be of type resource; 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

432
		$query_error = mysqli_error(/** @scrutinizer ignore-type */ $connection);
Loading history...
433
		$query_errno = mysqli_errno($connection);
0 ignored issues
show
Bug introduced by
It seems like $connection can also be of type resource; 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

433
		$query_errno = mysqli_errno(/** @scrutinizer ignore-type */ $connection);
Loading history...
434
435
		// Error numbers:
436
		//    1016: Can't open file '....MYI'
437
		//    1030: Got error ??? from table handler.
438
		//    1034: Incorrect key file for table.
439
		//    1035: Old key file for table.
440
		//    1142: Command denied
441
		//    1205: Lock wait timeout exceeded.
442
		//    1213: Deadlock found.
443
		//    2006: Server has gone away.
444
		//    2013: Lost connection to server during query.
445
446
		// We cannot do something, try to find out what and act accordingly
447
		if ($query_errno == 1142)
448
		{
449
			$command = substr(trim($db_string), 0, 6);
450
			if ($command === 'DELETE' || $command === 'UPDATE' || $command === 'INSERT')
451
			{
452
				// We can try to ignore it (warning the admin though it's a thing to do)
453
				// and serve the page just SELECTing
454
				$_SESSION['query_command_denied'][$command] = $query_error;
455
456
				// Let the admin know there is a command denied issue
457
				if (class_exists('Errors'))
458
				{
459
					Errors::instance()->log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n$db_string" : ''), 'database', $file, $line);
460
				}
461
462
				return false;
463
			}
464
		}
465
466
		// Log the error.
467
		if ($query_errno != 1213 && $query_errno != 1205 && class_exists('Errors'))
468
		{
469
			Errors::instance()->log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n$db_string" : ''), 'database', $file, $line);
470
		}
471
472
		// Database error auto fixing ;).
473
		if (function_exists('Cache::instance()->get') && (!isset($modSettings['autoFixDatabase']) || $modSettings['autoFixDatabase'] == '1'))
474
		{
475
			$db_last_error = db_last_error();
476
477
			// Force caching on, just for the error checking.
478
			$old_cache = isset($modSettings['cache_enable']) ? $modSettings['cache_enable'] : null;
479
			$modSettings['cache_enable'] = '1';
480
481
			if (Cache::instance()->getVar($temp, 'db_last_error', 600))
482
				$db_last_error = max($db_last_error, $temp);
483
484
			if ($db_last_error < time() - 3600 * 24 * 3)
485
			{
486
				// We know there's a problem... but what?  Try to auto detect.
487
				if ($query_errno == 1030 && strpos($query_error, ' 127 ') !== false)
488
				{
489
					preg_match_all('~(?:[\n\r]|^)[^\']+?(?:FROM|JOIN|UPDATE|TABLE) ((?:[^\n\r(]+?(?:, )?)*)~s', $db_string, $matches);
490
491
					$fix_tables = array();
492
					foreach ($matches[1] as $tables)
493
					{
494
						$tables = array_unique(explode(',', $tables));
495
						foreach ($tables as $table)
496
						{
497
							// Now, it's still theoretically possible this could be an injection.  So backtick it!
498
							if (trim($table) != '')
499
								$fix_tables[] = '`' . strtr(trim($table), array('`' => '')) . '`';
500
						}
501
					}
502
503
					$fix_tables = array_unique($fix_tables);
504
				}
505
				// Table crashed.  Let's try to fix it.
506
				elseif ($query_errno == 1016)
507
				{
508
					if (preg_match('~\'([^\.\']+)~', $query_error, $match) != 0)
509
						$fix_tables = array('`' . $match[1] . '`');
510
				}
511
				// Indexes crashed.  Should be easy to fix!
512
				elseif ($query_errno == 1034 || $query_errno == 1035)
513
				{
514
					preg_match('~\'([^\']+?)\'~', $query_error, $match);
515
					$fix_tables = array('`' . $match[1] . '`');
516
				}
517
			}
518
519
			// Check for errors like 145... only fix it once every three days, and send an email. (can't use empty because it might not be set yet...)
520
			if (!empty($fix_tables))
521
			{
522
				// subs/Admin.subs.php for updateDbLastError(), subs/Mail.subs.php for sendmail().
523
				// @todo this should go somewhere else, not into the db-mysql layer I think
524
				require_once(SUBSDIR . '/Admin.subs.php');
525
				require_once(SUBSDIR . '/Mail.subs.php');
526
527
				// Make a note of the REPAIR...
528
				Cache::instance()->put('db_last_error', time(), 600);
529
				if (!Cache::instance()->getVar($temp, 'db_last_error', 600))
530
					updateDbLastError(time());
531
532
				// Attempt to find and repair the broken table.
533
				foreach ($fix_tables as $table)
534
				{
535
					$this->query('', "
536
						REPAIR TABLE $table", false, false);
537
				}
538
539
				// And send off an email!
540
				sendmail($webmaster_email, $txt['database_error'], $txt['tried_to_repair']);
541
542
				$modSettings['cache_enable'] = $old_cache;
543
544
				// Try the query again...?
545
				$ret = $this->query('', $db_string, false, false);
546
				if ($ret !== false)
547
					return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret also could return the type mysqli_result which is incompatible with the documented return type boolean.
Loading history...
548
			}
549
			else
550
				$modSettings['cache_enable'] = $old_cache;
551
552
			// Check for the "lost connection" or "deadlock found" errors - and try it just one more time.
553
			if (in_array($query_errno, array(1205, 1213, 2006, 2013)))
554
			{
555
				$new_connection = false;
556
				if (in_array($query_errno, array(2006, 2013)) && $this->_connection == $connection)
557
				{
558
					// Are we in SSI mode?  If so try that username and password first
559
					if (ELK == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
560
						$new_connection = @mysqli_connect((!empty($db_persist) ? 'p:' : '') . $db_server, $ssi_db_user, $ssi_db_passwd, $db_name);
0 ignored issues
show
Bug introduced by
The call to mysqli_connect() has too few arguments starting with port. ( Ignorable by Annotation )

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

560
						$new_connection = @/** @scrutinizer ignore-call */ mysqli_connect((!empty($db_persist) ? 'p:' : '') . $db_server, $ssi_db_user, $ssi_db_passwd, $db_name);

This check compares calls to functions or methods with their respective definitions. If the call has less 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...
561
562
					// Fall back to the regular username and password if need be
563
					if (!$new_connection)
564
						$new_connection = @mysqli_connect((!empty($db_persist) ? 'p:' : '') . $db_server, $db_user, $db_passwd, $db_name);
565
				}
566
567
				if ($new_connection)
568
				{
569
					$this->_connection = $new_connection;
570
571
					// Try a deadlock more than once more.
572
					for ($n = 0; $n < 4; $n++)
573
					{
574
						$ret = $this->query('', $db_string, false, false);
575
576
						$new_errno = mysqli_errno($new_connection);
577
						if ($ret !== false || in_array($new_errno, array(1205, 1213)))
578
							break;
579
					}
580
581
					// If it failed again, shucks to be you... we're not trying it over and over.
582
					if ($ret !== false)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ret does not seem to be defined for all execution paths leading up to this point.
Loading history...
583
						return $ret;
584
				}
585
			}
586
			// Are they out of space, perhaps?
587
			elseif ($query_errno == 1030 && (strpos($query_error, ' -1 ') !== false || strpos($query_error, ' 28 ') !== false || strpos($query_error, ' 12 ') !== false))
588
			{
589
				if (!isset($txt))
590
					$query_error .= ' - check database storage space.';
591
				else
592
				{
593
					if (!isset($txt['mysql_error_space']))
594
						loadLanguage('Errors');
595
596
					$query_error .= !isset($txt['mysql_error_space']) ? ' - check database storage space.' : $txt['mysql_error_space'];
597
				}
598
			}
599
		}
600
601
		// Nothing's defined yet... just die with it.
602
		if (empty($context) || empty($txt))
603
			die($query_error);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
604
605
		// Show an error message, if possible.
606
		$context['error_title'] = $txt['database_error'];
607
		if (allowedTo('admin_forum'))
608
			$context['error_message'] = nl2br($query_error) . '<br />' . $txt['file'] . ': ' . $file . '<br />' . $txt['line'] . ': ' . $line;
0 ignored issues
show
Bug introduced by
It seems like $query_error can also be of type null; however, parameter $string of nl2br() 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

608
			$context['error_message'] = nl2br(/** @scrutinizer ignore-type */ $query_error) . '<br />' . $txt['file'] . ': ' . $file . '<br />' . $txt['line'] . ': ' . $line;
Loading history...
609
		else
610
			$context['error_message'] = $txt['try_again'];
611
612
		// Add database version that we know of, for the admin to know. (and ask for support)
613
		if (allowedTo('admin_forum'))
614
			$context['error_message'] .= '<br /><br />' . sprintf($txt['database_error_versions'], $modSettings['elkVersion']);
615
616
		if (allowedTo('admin_forum') && $db_show_debug === true)
617
			$context['error_message'] .= '<br /><br />' . nl2br($db_string);
618
619
		// It's already been logged... don't log it again.
620
		throw new Elk_Exception($context['error_message'], false);
621
	}
622
623
	/**
624
	 * Insert data.
625
	 *
626
	 * @param string $method - options 'replace', 'ignore', 'insert'
627
	 * @param string $table
628
	 * @param mixed[] $columns
629
	 * @param mixed[] $data
630
	 * @param mixed[] $keys
631
	 * @param bool $disable_trans = false
632
	 * @param mysqli|null $connection = null
633
	 * @throws Elk_Exception
634
	 */
635
	public function insert($method, $table, $columns, $data, $keys, $disable_trans = false, $connection = null)
636
	{
637
		global $db_prefix;
638
639
		$connection = $connection === null ? $this->_connection : $connection;
640
641
		// With nothing to insert, simply return.
642
		if (empty($data))
643
			return;
644
645
		// Inserting data as a single row can be done as a single array.
646
		if (!is_array($data[array_rand($data)]))
647
			$data = array($data);
648
649
		// Replace the prefix holder with the actual prefix.
650
		$table = str_replace('{db_prefix}', $db_prefix, $table);
651
652
		// Create the mold for a single row insert.
653
		$insertData = '(';
654
		foreach ($columns as $columnName => $type)
655
		{
656
			// Are we restricting the length?
657
			if (strpos($type, 'string-') !== false)
658
				$insertData .= sprintf('SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName);
659
			else
660
				$insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName);
661
		}
662
		$insertData = substr($insertData, 0, -2) . ')';
663
664
		// Create an array consisting of only the columns.
665
		$indexed_columns = array_keys($columns);
666
667
		// Here's where the variables are injected to the query.
668
		$insertRows = array();
669
		foreach ($data as $dataRow)
670
		{
671
			$insertRows[] = $this->quote($insertData, $this->_array_combine($indexed_columns, $dataRow), $connection);
0 ignored issues
show
Bug introduced by
It seems like $connection can also be of type resource; however, parameter $connection of Database_Abstract::quote() does only seem to accept mysqli|null|postgre, 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

671
			$insertRows[] = $this->quote($insertData, $this->_array_combine($indexed_columns, $dataRow), /** @scrutinizer ignore-type */ $connection);
Loading history...
672
		}
673
674
		// Determine the method of insertion.
675
		$queryTitle = $method === 'replace' ? 'REPLACE' : ($method == 'ignore' ? 'INSERT IGNORE' : 'INSERT');
676
677
		$skip_error = $table === $db_prefix . 'log_errors';
678
		$this->_skip_error = $skip_error;
679
		// Do the insert.
680
		$this->query('', '
681
			' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`)
682
			VALUES
683
				' . implode(',
684
				', $insertRows),
685
			array(
686
				'security_override' => true,
687
			),
688
			$connection
0 ignored issues
show
Bug introduced by
It seems like $connection can also be of type mysqli and resource; however, parameter $connection of Database_MySQL::query() does only seem to accept false|mysqli_result|null, 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

688
			/** @scrutinizer ignore-type */ $connection
Loading history...
689
		);
690
	}
691
692
	/**
693
	 * Unescape an escaped string!
694
	 *
695
	 * @param string $string
696
	 */
697
	public function unescape_string($string)
698
	{
699
		return stripslashes($string);
700
	}
701
702
	/**
703
	 * Returns whether the database system supports ignore.
704
	 *
705
	 * @return boolean
706
	 */
707
	public function support_ignore()
708
	{
709
		return true;
710
	}
711 24
712
	/**
713 24
	 * Gets all the necessary INSERTs for the table named table_name.
714
	 * It goes in 250 row segments.
715 24
	 *
716
	 * @param string $tableName - the table to create the inserts for.
717
	 * @param bool $new_table
718 24
	 *
719 24
	 * @return string the query to insert the data back in, or an empty string if the table was empty.
720
	 * @throws Elk_Exception
721
	 */
722 24
	public function insert_sql($tableName, $new_table = false)
723 24
	{
724
		global $db_prefix;
725
726 24
		static $start = 0, $num_rows, $fields, $limit;
727
728
		if ($new_table)
729 24
		{
730 24
			$limit = strstr($tableName, 'log_') !== false ? 500 : 250;
731
			$start = 0;
732
		}
733 24
734 24
		$tableName = str_replace('{db_prefix}', $db_prefix, $tableName);
735
736 20
		// This will be handy...
737 24
		$crlf = "\r\n";
738 24
739
		$result = $this->query('', '
740
			SELECT /*!40001 SQL_NO_CACHE */ *
741 24
			FROM `' . $tableName . '`
742
			LIMIT ' . $start . ', ' . $limit,
743
			array(
744 24
				'security_override' => true,
745 24
			)
746
		);
747 24
748 24
		// The number of rows, just for record keeping and breaking INSERTs up.
749
		$num_rows = $this->num_rows($result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type boolean; however, parameter $result of Database_MySQL::num_rows() does only seem to accept mysqli_result, 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

749
		$num_rows = $this->num_rows(/** @scrutinizer ignore-type */ $result);
Loading history...
750
751 24
		if ($num_rows == 0)
752
			return '';
753 24
754 24
		if ($new_table)
755
		{
756 24
			$fields = array_keys($this->fetch_assoc($result));
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type boolean; however, parameter $request of Database_MySQL::fetch_assoc() does only seem to accept mysqli_result, 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

756
			$fields = array_keys($this->fetch_assoc(/** @scrutinizer ignore-type */ $result));
Loading history...
757 24
			$this->data_seek($result, 0);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type boolean; however, parameter $request of Database_MySQL::data_seek() does only seem to accept mysqli_result, 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

757
			$this->data_seek(/** @scrutinizer ignore-type */ $result, 0);
Loading history...
758
		}
759 24
760 24
		// Start it off with the basic INSERT INTO.
761
		$data = 'INSERT INTO `' . $tableName . '`' . $crlf . "\t" . '(`' . implode('`, `', $fields) . '`)' . $crlf . 'VALUES ';
762 24
763 24
		// Loop through each row.
764
		while ($row = $this->fetch_assoc($result))
765 24
		{
766 24
			// Get the fields in this row...
767
			$field_list = array();
768
769
			foreach ($row as $key => $item)
770
			{
771
				// Try to figure out the type of each field. (NULL, number, or 'string'.)
772
				if (!isset($item))
773
					$field_list[] = 'NULL';
774
				elseif (is_numeric($item) && (int) $item == $item)
775
					$field_list[] = $item;
776
				else
777
					$field_list[] = '\'' . $this->escape_string($item) . '\'';
778
			}
779
780
			$data .= '(' . implode(', ', $field_list) . '),' . $crlf . "\t";
781
		}
782
783
		$this->free_result($result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type boolean; however, parameter $result of Database_MySQL::free_result() does only seem to accept mysqli_result, 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

783
		$this->free_result(/** @scrutinizer ignore-type */ $result);
Loading history...
784
		$data = substr(trim($data), 0, -1) . ';' . $crlf . $crlf;
785
786
		$start += $limit;
787
788
		return $data;
789
	}
790
791
	/**
792
	 * Dumps the schema (CREATE) for a table.
793
	 *
794
	 * @param string $tableName - the table
795
	 *
796
	 * @return string - the CREATE statement as string
797
	 * @throws Elk_Exception
798
	 */
799
	public function db_table_sql($tableName)
800
	{
801
		global $db_prefix;
802
803
		$tableName = str_replace('{db_prefix}', $db_prefix, $tableName);
804
805
		// This will be needed...
806
		$crlf = "\r\n";
807
808
		// Drop it if it exists.
809
		$schema_create = 'DROP TABLE IF EXISTS `' . $tableName . '`;' . $crlf . $crlf;
810
811
		// Start the create table...
812
		$schema_create .= 'CREATE TABLE `' . $tableName . '` (' . $crlf;
813
814
		// Find all the fields.
815
		$result = $this->query('', '
816
			SHOW FIELDS
817
			FROM `{raw:table}`',
818
			array(
819
				'table' => $tableName,
820
			)
821
		);
822
		while ($row = $this->fetch_assoc($result))
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type boolean; however, parameter $request of Database_MySQL::fetch_assoc() does only seem to accept mysqli_result, 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

822
		while ($row = $this->fetch_assoc(/** @scrutinizer ignore-type */ $result))
Loading history...
823
		{
824
			// Make the CREATE for this column.
825
			$schema_create .= ' `' . $row['Field'] . '` ' . $row['Type'] . ($row['Null'] != 'YES' ? ' NOT NULL' : '');
826
827
			// Add a default...?
828
			if (!empty($row['Default']) || $row['Null'] !== 'YES')
829
			{
830
				// Make a special case of auto-timestamp.
831
				if ($row['Default'] == 'CURRENT_TIMESTAMP')
832
					$schema_create .= ' /*!40102 NOT NULL default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP */';
833
				// Text shouldn't have a default.
834
				elseif ($row['Default'] !== null)
835
				{
836
					// If this field is numeric the default needs no escaping.
837
					$type = strtolower($row['Type']);
838
					$isNumericColumn = strpos($type, 'int') !== false || strpos($type, 'bool') !== false || strpos($type, 'bit') !== false || strpos($type, 'float') !== false || strpos($type, 'double') !== false || strpos($type, 'decimal') !== false;
839
840
					$schema_create .= ' default ' . ($isNumericColumn ? $row['Default'] : '\'' . $this->escape_string($row['Default']) . '\'');
841
				}
842
			}
843
844
			// And now any extra information. (such as auto_increment.)
845
			$schema_create .= ($row['Extra'] != '' ? ' ' . $row['Extra'] : '') . ',' . $crlf;
846
		}
847
		$this->free_result($result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type boolean; however, parameter $result of Database_MySQL::free_result() does only seem to accept mysqli_result, 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

847
		$this->free_result(/** @scrutinizer ignore-type */ $result);
Loading history...
848
849
		// Take off the last comma.
850
		$schema_create = substr($schema_create, 0, -strlen($crlf) - 1);
851
852
		// Find the keys.
853
		$result = $this->query('', '
854
			SHOW KEYS
855
			FROM `{raw:table}`',
856
			array(
857
				'table' => $tableName,
858
			)
859
		);
860
		$indexes = array();
861
		while ($row = $this->fetch_assoc($result))
862
		{
863
			// Is this a primary key, unique index, or regular index?
864
			$row['Key_name'] = $row['Key_name'] == 'PRIMARY' ? 'PRIMARY KEY' : (empty($row['Non_unique']) ? 'UNIQUE ' : ($row['Comment'] == 'FULLTEXT' || (isset($row['Index_type']) && $row['Index_type'] == 'FULLTEXT') ? 'FULLTEXT ' : 'KEY ')) . '`' . $row['Key_name'] . '`';
865
866
			// Is this the first column in the index?
867
			if (empty($indexes[$row['Key_name']]))
868
				$indexes[$row['Key_name']] = array();
869
870
			// A sub part, like only indexing 15 characters of a varchar.
871
			if (!empty($row['Sub_part']))
872
				$indexes[$row['Key_name']][$row['Seq_in_index']] = '`' . $row['Column_name'] . '`(' . $row['Sub_part'] . ')';
873
			else
874
				$indexes[$row['Key_name']][$row['Seq_in_index']] = '`' . $row['Column_name'] . '`';
875
		}
876
		$this->free_result($result);
877
878
		// Build the CREATEs for the keys.
879
		foreach ($indexes as $keyname => $columns)
880
		{
881
			// Ensure the columns are in proper order.
882
			ksort($columns);
883
884
			$schema_create .= ',' . $crlf . ' ' . $keyname . ' (' . implode(', ', $columns) . ')';
885
		}
886
887
		// Now just get the comment and type... (MyISAM, etc.)
888
		$result = $this->query('', '
889
			SHOW TABLE STATUS
890
			LIKE {string:table}',
891
			array(
892
				'table' => strtr($tableName, array('_' => '\\_', '%' => '\\%')),
893
			)
894
		);
895
		$row = $this->fetch_assoc($result);
896
		$this->free_result($result);
897
898
		// Probably MyISAM.... and it might have a comment.
899
		$schema_create .= $crlf . ') ENGINE=' . (isset($row['Type']) ? $row['Type'] : $row['Engine']) . ($row['Comment'] != '' ? ' COMMENT="' . $row['Comment'] . '"' : '');
900
901
		return $schema_create;
902
	}
903
904
	/**
905
	 * {@inheritdoc}
906
	 */
907
	public function db_list_tables($db_name_str = false, $filter = false)
908
	{
909
		global $db_name;
910
911
		$db_name_str = $db_name_str === false ? $db_name : $db_name_str;
912
		$db_name_str = trim($db_name_str);
913
		$filter = $filter === false ? '' : ' LIKE \'' . $filter . '\'';
914
915
		$request = $this->query('', '
916
			SHOW TABLES
917
			FROM `{raw:db_name_str}`
918
			{raw:filter}',
919
			array(
920
				'db_name_str' => $db_name_str[0] == '`' ? strtr($db_name_str, array('`' => '')) : $db_name_str,
921
				'filter' => $filter,
922
			)
923
		);
924
		$tables = array();
925
		while ($row = $this->fetch_row($request))
0 ignored issues
show
Bug introduced by
It seems like $request can also be of type boolean; however, parameter $result of Database_MySQL::fetch_row() does only seem to accept mysqli_result, 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

925
		while ($row = $this->fetch_row(/** @scrutinizer ignore-type */ $request))
Loading history...
926
		{
927
			$tables[] = $row[0];
928
		}
929
		$this->free_result($request);
0 ignored issues
show
Bug introduced by
It seems like $request can also be of type boolean; however, parameter $result of Database_MySQL::free_result() does only seem to accept mysqli_result, 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

929
		$this->free_result(/** @scrutinizer ignore-type */ $request);
Loading history...
930
931
		return $tables;
932
	}
933
934
	/**
935
	 * Backup $table_name to $backup_table.
936
	 *
937
	 * @param string $table_name
938
	 * @param string $backup_table
939
	 *
940
	 * @return resource - the request handle to the table creation query
941
	 * @throws Elk_Exception
942
	 */
943
	public function db_backup_table($table_name, $backup_table)
944
	{
945
		global $db_prefix;
946
947
		$table = str_replace('{db_prefix}', $db_prefix, $table_name);
948
949
		// First, get rid of the old table.
950
		$db_table = db_table();
951
		$db_table->db_drop_table($backup_table);
952
953
		// Can we do this the quick way?
954
		$result = $this->query('', '
955
			CREATE TABLE {raw:backup_table} LIKE {raw:table}',
956
			array(
957
				'backup_table' => $backup_table,
958
				'table' => $table
959
		));
960
		// If this failed, we go old school.
961
		if ($result)
962
		{
963
			$request = $this->query('', '
964
				INSERT INTO {raw:backup_table}
965
				SELECT *
966
				FROM {raw:table}',
967
				array(
968
					'backup_table' => $backup_table,
969
					'table' => $table
970
				));
971
972
			// Old school or no school?
973
			if ($request)
974
				return $request;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $request returns the type mysqli_result|true which is incompatible with the documented return type resource.
Loading history...
975
		}
976
977
		// At this point, the quick method failed.
978
		$result = $this->query('', '
979
			SHOW CREATE TABLE {raw:table}',
980
			array(
981
				'table' => $table,
982
			)
983 2
		);
984
		list (, $create) = $this->fetch_row($result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type boolean; however, parameter $result of Database_MySQL::fetch_row() does only seem to accept mysqli_result, 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

984
		list (, $create) = $this->fetch_row(/** @scrutinizer ignore-type */ $result);
Loading history...
985 2
		$this->free_result($result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type boolean; however, parameter $result of Database_MySQL::free_result() does only seem to accept mysqli_result, 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

985
		$this->free_result(/** @scrutinizer ignore-type */ $result);
Loading history...
986
987 2
		$create = preg_split('/[\n\r]/', $create);
988 2
989 2
		$auto_inc = '';
990
991 2
		// Default engine type.
992
		$engine = 'MyISAM';
993
		$charset = '';
994 2
		$collate = '';
995
996 2
		foreach ($create as $k => $l)
997 2
		{
998
			// Get the name of the auto_increment column.
999 2
			if (strpos($l, 'auto_increment'))
1000 2
				$auto_inc = trim($l);
1001 2
1002
			// For the engine type, see if we can work out what it is.
1003 2
			if (strpos($l, 'ENGINE') !== false || strpos($l, 'TYPE') !== false)
1004 2
			{
1005 2
				// Extract the engine type.
1006
				preg_match('~(ENGINE|TYPE)=(\w+)(\sDEFAULT)?(\sCHARSET=(\w+))?(\sCOLLATE=(\w+))?~', $l, $match);
1007 2
1008
				if (!empty($match[1]))
1009
					$engine = $match[1];
1010
1011
				if (!empty($match[2]))
1012
					$engine = $match[2];
1013
1014
				if (!empty($match[5]))
1015
					$charset = $match[5];
1016
1017
				if (!empty($match[7]))
1018
					$collate = $match[7];
1019
			}
1020
1021
			// Skip everything but keys...
1022
			if (strpos($l, 'KEY') === false)
1023
				unset($create[$k]);
1024
		}
1025
1026
		if (!empty($create))
1027
			$create = '(
1028
				' . implode('
1029
				', $create) . ')';
1030
		else
1031
			$create = '';
1032
1033
		$request = $this->query('', '
1034
			CREATE TABLE {raw:backup_table} {raw:create}
1035
			ENGINE={raw:engine}' . (empty($charset) ? '' : ' CHARACTER SET {raw:charset}' . (empty($collate) ? '' : ' COLLATE {raw:collate}')) . '
1036
			SELECT *
1037
			FROM {raw:table}',
1038
			array(
1039
				'backup_table' => $backup_table,
1040
				'table' => $table,
1041
				'create' => $create,
1042
				'engine' => $engine,
1043
				'charset' => empty($charset) ? '' : $charset,
1044
				'collate' => empty($collate) ? '' : $collate,
1045
			)
1046
		);
1047
1048
		if ($auto_inc != '')
1049
		{
1050
			if (preg_match('~\`(.+?)\`\s~', $auto_inc, $match) != 0 && substr($auto_inc, -1, 1) == ',')
1051
				$auto_inc = substr($auto_inc, 0, -1);
1052
1053
			$this->query('', '
1054
				ALTER TABLE {raw:backup_table}
1055
				CHANGE COLUMN {raw:column_detail} {raw:auto_inc}',
1056
				array(
1057
					'backup_table' => $backup_table,
1058
					'column_detail' => $match[1],
1059
					'auto_inc' => $auto_inc,
1060
				)
1061
			);
1062
		}
1063
1064
		return $request;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $request returns the type boolean|mysqli_result which is incompatible with the documented return type resource.
Loading history...
1065
	}
1066
1067
	/**
1068
	 * Get the version number.
1069
	 *
1070
	 * @return string - the version
1071
	 * @throws Elk_Exception
1072
	 */
1073
	public function db_server_version()
1074
	{
1075
		$request = $this->query('', '
1076
			SELECT VERSION()',
1077
			array(
1078
			)
1079
		);
1080
		list ($ver) = $this->fetch_row($request);
0 ignored issues
show
Bug introduced by
It seems like $request can also be of type boolean; however, parameter $result of Database_MySQL::fetch_row() does only seem to accept mysqli_result, 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

1080
		list ($ver) = $this->fetch_row(/** @scrutinizer ignore-type */ $request);
Loading history...
1081
		$this->free_result($request);
0 ignored issues
show
Bug introduced by
It seems like $request can also be of type boolean; however, parameter $result of Database_MySQL::free_result() does only seem to accept mysqli_result, 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

1081
		$this->free_result(/** @scrutinizer ignore-type */ $request);
Loading history...
1082
1083
		return $ver;
1084
	}
1085
1086
	/**
1087
	 * Get the name (title) of the database system.
1088
	 *
1089
	 * @return string
1090
	 */
1091
	public function db_title()
1092
	{
1093
		return 'MySQL';
1094
	}
1095
1096
	/**
1097
	 * Whether the database system is case sensitive.
1098
	 *
1099
	 * @return false
1100
	 */
1101
	public function db_case_sensitive()
1102
	{
1103
		return false;
1104
	}
1105
1106
	/**
1107
	 * Escape string for the database input
1108
	 *
1109
	 * @param string $string
1110
	 */
1111
	public function escape_string($string)
1112
	{
1113
		// This is necessary because MySQL utf8 doesn't know how to store such
1114
		// characters and would generate an error any time one is used.
1115
		// The 4byte chars are used by emoji
1116
		$string = Util::clean_4byte_chars($string);
1117
1118
		return mysqli_real_escape_string($this->_connection, $string);
0 ignored issues
show
Bug introduced by
It seems like $this->_connection can also be of type resource; 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

1118
		return mysqli_real_escape_string(/** @scrutinizer ignore-type */ $this->_connection, $string);
Loading history...
1119
	}
1120
1121
	/**
1122
	 * Fetch next result as association.
1123
	 * The mysql implementation simply delegates to mysqli_fetch_assoc().
1124
	 * It ignores $counter parameter.
1125
	 *
1126
	 * @param mysqli_result $request
1127
	 * @param int|bool $counter = false
1128
	 */
1129
	public function fetch_assoc($request, $counter = false)
1130
	{
1131
		return mysqli_fetch_assoc($request);
1132
	}
1133
1134
	/**
1135
	 * Return server info.
1136
	 *
1137
	 * @param mysqli|null $connection
1138
	 *
1139
	 * @return string
1140
	 */
1141
	public function db_server_info($connection = null)
1142
	{
1143
		// Decide which connection to use
1144
		$connection = $connection === null ? $this->_connection : $connection;
1145
1146
		return mysqli_get_server_info($connection);
0 ignored issues
show
Bug introduced by
It seems like $connection can also be of type resource; 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

1146
		return mysqli_get_server_info(/** @scrutinizer ignore-type */ $connection);
Loading history...
1147
	}
1148
1149
	/**
1150
	 *  Get the version number.
1151
	 *
1152
	 * @return string - the version
1153
	 * @throws Elk_Exception
1154
	 */
1155
	public function db_client_version()
1156
	{
1157
		$request = $this->query('', '
1158
			SELECT VERSION()',
1159
			array(
1160
			)
1161
		);
1162
		list ($ver) = $this->fetch_row($request);
0 ignored issues
show
Bug introduced by
It seems like $request can also be of type boolean; however, parameter $result of Database_MySQL::fetch_row() does only seem to accept mysqli_result, 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

1162
		list ($ver) = $this->fetch_row(/** @scrutinizer ignore-type */ $request);
Loading history...
1163
		$this->free_result($request);
0 ignored issues
show
Bug introduced by
It seems like $request can also be of type boolean; however, parameter $result of Database_MySQL::free_result() does only seem to accept mysqli_result, 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

1163
		$this->free_result(/** @scrutinizer ignore-type */ $request);
Loading history...
1164
1165
		return $ver;
1166
	}
1167
1168
	/**
1169
	 * Select database.
1170
	 *
1171
	 * @param string|null $dbName = null
1172
	 * @param mysqli|null $connection = null
1173
	 */
1174
	public function select_db($dbName = null, $connection = null)
1175
	{
1176
		// Decide which connection to use
1177
		$connection = $connection === null ? $this->_connection : $connection;
1178
1179
		return mysqli_select_db($connection, $dbName);
0 ignored issues
show
Bug introduced by
It seems like $connection can also be of type resource; however, parameter $mysql of mysqli_select_db() 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

1179
		return mysqli_select_db(/** @scrutinizer ignore-type */ $connection, $dbName);
Loading history...
Bug introduced by
It seems like $dbName can also be of type null; however, parameter $database of mysqli_select_db() 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

1179
		return mysqli_select_db($connection, /** @scrutinizer ignore-type */ $dbName);
Loading history...
1180
	}
1181
1182
	/**
1183
	 * Returns a reference to the existing instance
1184
	 */
1185
	public static function db()
1186
	{
1187 32
		return self::$_db;
1188
	}
1189 32
1190
	/**
1191 32
	 * Finds out if the connection is still valid.
1192
	 *
1193
	 * @param mysqli|null $connection = null
1194
	 */
1195
	public function validConnection($connection = null)
1196
	{
1197
		return is_object($connection);
1198
	}
1199
}
1200