Passed
Push — patch_1-1-7 ( 90a951...ab62ad )
by Emanuele
04:23 queued 03:46
created

Database_MySQL   F

Complexity

Total Complexity 229

Size/Duplication

Total Lines 1243
Duplicated Lines 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
eloc 459
dl 0
loc 1243
rs 2
c 2
b 1
f 0
wmc 229

32 Methods

Rating   Name   Duplication   Size   Complexity  
A db() 0 3 1
A validConnection() 0 3 1
A select_db() 0 6 2
A last_error() 0 7 3
B insert() 0 54 9
F error() 0 201 58
A free_result() 0 4 1
B _clean_4byte_chars() 0 39 7
B insert_sql() 0 67 10
A data_seek() 0 4 1
A unescape_string() 0 3 1
A num_rows() 0 4 1
A insert_id() 0 4 2
A fetch_assoc() 0 3 1
A db_transaction() 0 13 5
F db_table_sql() 0 103 25
A db_server_version() 0 11 1
A db_list_tables() 0 25 5
A db_title() 0 3 1
A db_client_version() 0 11 1
A num_fields() 0 3 1
A escape_string() 0 5 1
A db_server_info() 0 6 2
A fix_prefix() 0 5 2
A fetch_row() 0 4 1
A affected_rows() 0 3 2
F db_backup_table() 0 122 20
A support_ignore() 0 3 1
C _uniord() 0 16 15
A db_case_sensitive() 0 3 1
B initiate() 0 58 10
F query() 0 155 37

How to fix   Complexity   

Complex Class

Complex classes like Database_MySQL often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Database_MySQL, and based on these observations, apply Extract Interface, too.

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.6
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
		// Select the database. Maybe.
66
		if (empty($db_options['dont_select_db']))
67
			$connection = @mysqli_connect((!empty($db_options['persist']) ? 'p:' : '') . $db_server, $db_user, $db_passwd, $db_name, $db_port);
68
		else
69
			$connection = @mysqli_connect((!empty($db_options['persist']) ? 'p:' : '') . $db_server, $db_user, $db_passwd, '', $db_port);
70
71
		// Something's wrong, show an error if its fatal (which we assume it is)
72
		if (!$connection)
73
		{
74
			if (!empty($db_options['non_fatal']))
75
				return null;
76
			else
77
				Errors::instance()->display_db_error();
78
		}
79
80
		self::$_db->_connection = $connection;
81
82
		// This makes it possible to automatically change the sql_mode and autocommit if needed.
83
		if (isset($mysql_set_mode) && $mysql_set_mode === true)
84
		{
85
			self::$_db->query('', 'SET sql_mode = \'\', AUTOCOMMIT = 1',
86
				array(),
87
				false
88
			);
89
		}
90
91
		// Few databases still have not set UTF-8 as their default input charset
92
		self::$_db->query('', '
93
			SET NAMES UTF8',
94
			array(
95
			)
96
		);
97
98
		// Unfortunately Elk still have some trouble dealing with the most recent settings of MySQL/Mariadb
99
		// disable ONLY_FULL_GROUP_BY
100
		self::$_db->query('', '
101
			SET sql_mode=(SELECT REPLACE(@@sql_mode, {string:disable}, {string:empty}))',
102
			array(
103
				'disable' => 'ONLY_FULL_GROUP_BY',
104
				'empty' => ''
105
			)
106
		);
107
108
		return $connection;
109
	}
110
111
	/**
112
	 * Fix up the prefix so it doesn't require the database to be selected.
113
	 *
114
	 * @param string $db_prefix
115
	 * @param string $db_name
116
	 *
117
	 * @return string
118
	 */
119
	public function fix_prefix($db_prefix, $db_name)
120
	{
121
		$db_prefix = is_numeric(substr($db_prefix, 0, 1)) ? $db_name . '.' . $db_prefix : '`' . $db_name . '`.' . $db_prefix;
122
123
		return $db_prefix;
124
	}
125
126
	/**
127
	 * Do a query.  Takes care of errors too.
128
	 *
129
	 * @param string $identifier
130
	 * @param string $db_string
131
	 * @param mixed[]|false $db_values = array()
132
	 * @param mysqli_result|false|null $connection = null
133
	 * @throws Elk_Exception
134
	 */
135
	public function query($identifier, $db_string, $db_values = array(), $connection = null)
136
	{
137
		global $db_show_debug, $time_start, $modSettings;
138
139
		// Comments that are allowed in a query are preg_removed.
140
		static $allowed_comments_from = array(
141
			'~\s+~s',
142
			'~/\*!40001 SQL_NO_CACHE \*/~',
143
			'~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~',
144
			'~/\*!40100 ON DUPLICATE KEY UPDATE id_msg = \d+ \*/~',
145
		);
146
		static $allowed_comments_to = array(
147
			' ',
148
			'',
149
			'',
150
			'',
151
		);
152
153
		// Decide which connection to use.
154
		$connection = $connection === null ? $this->_connection : $connection;
155
156
		// One more query....
157
		$this->_query_count++;
158
159
		if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override']))
160
			$this->error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__);
161
162
		// Use "ORDER BY null" to prevent Mysql doing filesorts for Group By clauses without an Order By
163
		if (strpos($db_string, 'GROUP BY') !== false && strpos($db_string, 'ORDER BY') === false && strpos($db_string, 'INSERT INTO') === false)
164
		{
165
			// Add before LIMIT
166
			if ($pos = strpos($db_string, 'LIMIT '))
167
				$db_string = substr($db_string, 0, $pos) . "\t\t\tORDER BY null\n" . substr($db_string, $pos, strlen($db_string));
168
			else
169
				// Append it.
170
				$db_string .= "\n\t\t\tORDER BY null";
171
		}
172
173
		if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false))
174
		{
175
			// Store these values for use in the callback function.
176
			$this->_db_callback_values = $db_values;
177
			$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...
178
179
			// Inject the values passed to this function.
180
			$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', array($this, 'replacement__callback'), $db_string);
181
182
			// No need for them any longer.
183
			$this->_db_callback_values = array();
184
			$this->_db_callback_connection = null;
185
		}
186
187
		// Debugging.
188
		if ($db_show_debug === true)
189
		{
190
			$debug = Debug::instance();
191
192
			// Get the file and line number this function was called.
193
			list ($file, $line) = $this->error_backtrace('', '', 'return', __FILE__, __LINE__);
194
195
			if (!empty($_SESSION['debug_redirect']))
196
			{
197
				$debug->merge_db($_SESSION['debug_redirect']);
198
				// @todo this may be off by 1
199
				$this->_query_count += count($_SESSION['debug_redirect']);
200
				$_SESSION['debug_redirect'] = array();
201
			}
202
203
			// Don't overload it.
204
			$st = microtime(true);
205
			$db_cache = array();
206
			$db_cache['q'] = $this->_query_count < 50 ? $db_string : '...';
207
			$db_cache['f'] = $file;
208
			$db_cache['l'] = $line;
209
			$db_cache['s'] = $st - $time_start;
210
		}
211
212
		// First, we clean strings out of the query, reduce whitespace, lowercase, and trim - so we can check it over.
213
		if (empty($modSettings['disableQueryCheck']))
214
		{
215
			$clean = '';
216
			$old_pos = 0;
217
			$pos = -1;
218
			while (true)
219
			{
220
				$pos = strpos($db_string, '\'', $pos + 1);
221
				if ($pos === false)
222
					break;
223
				$clean .= substr($db_string, $old_pos, $pos - $old_pos);
224
225
				while (true)
226
				{
227
					$pos1 = strpos($db_string, '\'', $pos + 1);
228
					$pos2 = strpos($db_string, '\\', $pos + 1);
229
					if ($pos1 === false)
230
						break;
231
					elseif ($pos2 === false || $pos2 > $pos1)
232
					{
233
						$pos = $pos1;
234
						break;
235
					}
236
237
					$pos = $pos2 + 1;
238
				}
239
240
				$clean .= ' %s ';
241
				$old_pos = $pos + 1;
242
			}
243
244
			$clean .= substr($db_string, $old_pos);
245
			$clean = trim(strtolower(preg_replace($allowed_comments_from, $allowed_comments_to, $clean)));
246
247
			// Comments?  We don't use comments in our queries, we leave 'em outside!
248
			if (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false)
249
				$fail = true;
250
			// Trying to change passwords, slow us down, or something?
251
			elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[_a-z])~s', $clean) != 0)
252
				$fail = true;
253
			elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
254
				$fail = true;
255
256
			if (!empty($fail) && function_exists('log_error'))
257
				$this->error_backtrace('Hacking attempt...', 'Hacking attempt...' . "\n" . $db_string, E_USER_ERROR, __FILE__, __LINE__);
258
		}
259
260
		if ($this->_unbuffered === false)
261
			$ret = @mysqli_query($connection, $db_string);
262
		else
263
			$ret = @mysqli_query($connection, $db_string, MYSQLI_USE_RESULT);
264
265
		// @deprecated since 1.1 - use skip_next_error method
266
		if (!empty($db_values['db_error_skip']))
267
		{
268
			$this->_skip_error = true;
269
		}
270
271
		if ($ret === false && $this->_skip_error === false)
272
		{
273
			$ret = $this->error($db_string, $connection);
274
		}
275
276
		// Revert not to skip errors
277
		if ($this->_skip_error === true)
278
		{
279
			$this->_skip_error = false;
280
		}
281
282
		// Debugging.
283
		if ($db_show_debug === true)
284
		{
285
			$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...
286
			$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...
287
		}
288
289
		return $ret;
290
	}
291
292
	/**
293
	 * Checks if the string contains any 4byte chars and if so,
294
	 * converts them into HTML entities.
295
	 *
296
	 * This is necessary because MySQL utf8 doesn't know how to store such
297
	 * characters and would generate an error any time one is used.
298
	 * The 4byte chars are used by emoji
299
	 *
300
	 * @param string $string
301
	 * @return string
302
	 */
303
	private function _clean_4byte_chars($string)
304
	{
305
		global $modSettings;
306
307
		if (!empty($modSettings['using_utf8mb4']))
308
			return $string;
309
310
		$result = $string;
311
		$ord = array_map('ord', str_split($string));
312
313
		// If we are in the 4-byte range
314
		if (max($ord) >= 240)
315
		{
316
			// Byte length
317
			$length = strlen($string);
318
			$result = '';
319
320
			// Look for a 4byte marker
321
			for ($i = 0; $i < $length; $i++)
322
			{
323
				// The first byte of a 4-byte character encoding starts with the bytes 0xF0-0xF4 (240 <-> 244)
324
				// but look all the way to 247 for safe measure
325
				$ord1 = $ord[$i];
326
				if ($ord1 >= 240 && $ord1 <= 247)
327
				{
328
					// Replace it with the corresponding html entity
329
					$entity = $this->_uniord(chr($ord[$i]) . chr($ord[$i + 1]) . chr($ord[$i + 2]) . chr($ord[$i + 3]));
330
					if ($entity === false)
331
						$result .= "\xEF\xBF\xBD";
332
					else
333
						$result .= '&#x' . dechex($entity) . ';';
334
					$i += 3;
335
				}
336
				else
337
					$result .= $string[$i];
338
			}
339
		}
340
341
		return $result;
342
	}
343
344
	/**
345
	 * Converts a 4byte char into the corresponding HTML entity code.
346
	 *
347
	 * This function is derived from:
348
	 * http://www.greywyvern.com/code/php/utf8_html.phps
349
	 *
350
	 * @param string $c
351
	 * @return integer|false
352
	 */
353
	private function _uniord($c)
354
	{
355
		if (ord($c[0]) >= 0 && ord($c[0]) <= 127)
356
			return ord($c[0]);
357
		if (ord($c[0]) >= 192 && ord($c[0]) <= 223)
358
			return (ord($c[0]) - 192) * 64 + (ord($c[1]) - 128);
359
		if (ord($c[0]) >= 224 && ord($c[0]) <= 239)
360
			return (ord($c[0]) - 224) * 4096 + (ord($c[1]) - 128) * 64 + (ord($c[2]) - 128);
361
		if (ord($c[0]) >= 240 && ord($c[0]) <= 247)
362
			return (ord($c[0]) - 240) * 262144 + (ord($c[1]) - 128) * 4096 + (ord($c[2]) - 128) * 64 + (ord($c[3]) - 128);
363
		if (ord($c[0]) >= 248 && ord($c[0]) <= 251)
364
			return (ord($c[0]) - 248) * 16777216 + (ord($c[1]) - 128) * 262144 + (ord($c[2]) - 128) * 4096 + (ord($c[3]) - 128) * 64 + (ord($c[4]) - 128);
365
		if (ord($c[0]) >= 252 && ord($c[0]) <= 253)
366
			return (ord($c[0]) - 252) * 1073741824 + (ord($c[1]) - 128) * 16777216 + (ord($c[2]) - 128) * 262144 + (ord($c[3]) - 128) * 4096 + (ord($c[4]) - 128) * 64 + (ord($c[5]) - 128);
367
		if (ord($c[0]) >= 254 && ord($c[0]) <= 255)
368
			return false;
369
	}
370
371
	/**
372
	 * Affected rows from previous operation.
373
	 *
374
	 * @param mysqli|null $connection
375
	 */
376
	public function affected_rows($connection = null)
377
	{
378
		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 $link 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

378
		return mysqli_affected_rows(/** @scrutinizer ignore-type */ $connection === null ? $this->_connection : $connection);
Loading history...
379
	}
380
381
	/**
382
	 * Last inserted id.
383
	 *
384
	 * @param string $table
385
	 * @param string|null $field = null
386
	 * @param mysqli|null $connection = null
387
	 */
388
	public function insert_id($table, $field = null, $connection = null)
389
	{
390
		// MySQL doesn't need the table or field information.
391
		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 $link 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

391
		return mysqli_insert_id(/** @scrutinizer ignore-type */ $connection === null ? $this->_connection : $connection);
Loading history...
392
	}
393
394
	/**
395
	 * Fetch a row from the result set given as parameter.
396
	 * MySQL implementation doesn't use $counter parameter.
397
	 *
398
	 * @param mysqli_result $result
399
	 * @param integer|bool $counter = false
400
	 */
401
	public function fetch_row($result, $counter = false)
402
	{
403
		// Just delegate to MySQL's function
404
		return mysqli_fetch_row($result);
405
	}
406
407
	/**
408
	 * Free the resultset.
409
	 *
410
	 * @param mysqli_result $result
411
	 */
412
	public function free_result($result)
413
	{
414
		// Just delegate to MySQL's function
415
		mysqli_free_result($result);
416
	}
417
418
	/**
419
	 * Get the number of rows in the result.
420
	 *
421
	 * @param mysqli_result $result
422
	 */
423
	public function num_rows($result)
424
	{
425
		// Simply delegate to the native function
426
		return mysqli_num_rows($result);
427
	}
428
429
	/**
430
	 * Get the number of fields in the result set.
431
	 *
432
	 * @param mysqli_result $request
433
	 */
434
	public function num_fields($request)
435
	{
436
		return mysqli_num_fields($request);
437
	}
438
439
	/**
440
	 * Reset the internal result pointer.
441
	 *
442
	 * @param mysqli_result $request
443
	 * @param integer $counter
444
	 */
445
	public function data_seek($request, $counter)
446
	{
447
		// Delegate to native mysql function
448
		return mysqli_data_seek($request, $counter);
449
	}
450
451
	/**
452
	 * Do a transaction.
453
	 *
454
	 * @param string $type - the step to perform (i.e. 'begin', 'commit', 'rollback')
455
	 * @param mysqli|null $connection = null
456
	 */
457
	public function db_transaction($type = 'commit', $connection = null)
458
	{
459
		// Decide which connection to use
460
		$connection = $connection === null ? $this->_connection : $connection;
461
462
		if ($type == 'begin')
463
			return @mysqli_query($connection, 'BEGIN');
0 ignored issues
show
Bug introduced by
It seems like $connection can also be of type resource; however, parameter $link 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

463
			return @mysqli_query(/** @scrutinizer ignore-type */ $connection, 'BEGIN');
Loading history...
464
		elseif ($type == 'rollback')
465
			return @mysqli_query($connection, 'ROLLBACK');
466
		elseif ($type == 'commit')
467
			return @mysqli_query($connection, 'COMMIT');
468
469
		return false;
470
	}
471
472
	/**
473
	 * Return last error string from the database server
474
	 *
475
	 * @param mysqli|null $connection = null
476
	 */
477
	public function last_error($connection = null)
478
	{
479
		// Decide which connection to use
480
		$connection = $connection === null ? $this->_connection : $connection;
481
482
		if (is_object($connection))
483
			return mysqli_error($connection);
484
	}
485
486
	/**
487
	 * Database error.
488
	 * Backtrace, log, try to fix.
489
	 *
490
	 * @param string      $db_string
491
	 * @param mysqli|null $connection = null
492
	 *
493
	 * @return bool
494
	 * @throws Elk_Exception
495
	 */
496
	public function error($db_string, $connection = null)
497
	{
498
		global $txt, $context, $webmaster_email, $modSettings, $db_persist;
499
		global $db_server, $db_user, $db_passwd, $db_name, $db_show_debug, $ssi_db_user, $ssi_db_passwd;
500
501
		// Get the file and line numbers.
502
		list ($file, $line) = $this->error_backtrace('', '', 'return', __FILE__, __LINE__);
503
504
		// Decide which connection to use
505
		$connection = $connection === null ? $this->_connection : $connection;
506
507
		// This is the error message...
508
		$query_error = mysqli_error($connection);
0 ignored issues
show
Bug introduced by
It seems like $connection can also be of type resource; however, parameter $link 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

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

509
		$query_errno = mysqli_errno(/** @scrutinizer ignore-type */ $connection);
Loading history...
510
511
		// Error numbers:
512
		//    1016: Can't open file '....MYI'
513
		//    1030: Got error ??? from table handler.
514
		//    1034: Incorrect key file for table.
515
		//    1035: Old key file for table.
516
		//    1142: Command denied
517
		//    1205: Lock wait timeout exceeded.
518
		//    1213: Deadlock found.
519
		//    2006: Server has gone away.
520
		//    2013: Lost connection to server during query.
521
522
		// We cannot do something, try to find out what and act accordingly
523
		if ($query_errno == 1142)
524
		{
525
			$command = substr(trim($db_string), 0, 6);
526
			if ($command === 'DELETE' || $command === 'UPDATE' || $command === 'INSERT')
527
			{
528
				// We can try to ignore it (warning the admin though it's a thing to do)
529
				// and serve the page just SELECTing
530
				$_SESSION['query_command_denied'][$command] = $query_error;
531
532
				// Let the admin know there is a command denied issue
533
				if (class_exists('Errors'))
534
				{
535
					Errors::instance()->log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n$db_string" : ''), 'database', $file, $line);
536
				}
537
538
				return false;
539
			}
540
		}
541
542
		// Log the error.
543
		if ($query_errno != 1213 && $query_errno != 1205 && class_exists('Errors'))
544
		{
545
			Errors::instance()->log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n$db_string" : ''), 'database', $file, $line);
546
		}
547
548
		// Database error auto fixing ;).
549
		if (function_exists('Cache::instance()->get') && (!isset($modSettings['autoFixDatabase']) || $modSettings['autoFixDatabase'] == '1'))
550
		{
551
			$db_last_error = db_last_error();
552
553
			// Force caching on, just for the error checking.
554
			$old_cache = isset($modSettings['cache_enable']) ? $modSettings['cache_enable'] : null;
555
			$modSettings['cache_enable'] = '1';
556
557
			if (Cache::instance()->getVar($temp, 'db_last_error', 600))
558
				$db_last_error = max($db_last_error, $temp);
559
560
			if ($db_last_error < time() - 3600 * 24 * 3)
561
			{
562
				// We know there's a problem... but what?  Try to auto detect.
563
				if ($query_errno == 1030 && strpos($query_error, ' 127 ') !== false)
564
				{
565
					preg_match_all('~(?:[\n\r]|^)[^\']+?(?:FROM|JOIN|UPDATE|TABLE) ((?:[^\n\r(]+?(?:, )?)*)~s', $db_string, $matches);
566
567
					$fix_tables = array();
568
					foreach ($matches[1] as $tables)
569
					{
570
						$tables = array_unique(explode(',', $tables));
571
						foreach ($tables as $table)
572
						{
573
							// Now, it's still theoretically possible this could be an injection.  So backtick it!
574
							if (trim($table) != '')
575
								$fix_tables[] = '`' . strtr(trim($table), array('`' => '')) . '`';
576
						}
577
					}
578
579
					$fix_tables = array_unique($fix_tables);
580
				}
581
				// Table crashed.  Let's try to fix it.
582
				elseif ($query_errno == 1016)
583
				{
584
					if (preg_match('~\'([^\.\']+)~', $query_error, $match) != 0)
585
						$fix_tables = array('`' . $match[1] . '`');
586
				}
587
				// Indexes crashed.  Should be easy to fix!
588
				elseif ($query_errno == 1034 || $query_errno == 1035)
589
				{
590
					preg_match('~\'([^\']+?)\'~', $query_error, $match);
591
					$fix_tables = array('`' . $match[1] . '`');
592
				}
593
			}
594
595
			// 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...)
596
			if (!empty($fix_tables))
597
			{
598
				// subs/Admin.subs.php for updateDbLastError(), subs/Mail.subs.php for sendmail().
599
				// @todo this should go somewhere else, not into the db-mysql layer I think
600
				require_once(SUBSDIR . '/Admin.subs.php');
601
				require_once(SUBSDIR . '/Mail.subs.php');
602
603
				// Make a note of the REPAIR...
604
				Cache::instance()->put('db_last_error', time(), 600);
605
				if (!Cache::instance()->getVar($temp, 'db_last_error', 600))
606
					updateDbLastError(time());
607
608
				// Attempt to find and repair the broken table.
609
				foreach ($fix_tables as $table)
610
				{
611
					$this->query('', "
612
						REPAIR TABLE $table", false, false);
613
				}
614
615
				// And send off an email!
616
				sendmail($webmaster_email, $txt['database_error'], $txt['tried_to_repair']);
617
618
				$modSettings['cache_enable'] = $old_cache;
619
620
				// Try the query again...?
621
				$ret = $this->query('', $db_string, false, false);
622
				if ($ret !== false)
623
					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...
624
			}
625
			else
626
				$modSettings['cache_enable'] = $old_cache;
627
628
			// Check for the "lost connection" or "deadlock found" errors - and try it just one more time.
629
			if (in_array($query_errno, array(1205, 1213, 2006, 2013)))
630
			{
631
				$new_connection = false;
632
				if (in_array($query_errno, array(2006, 2013)) && $this->_connection == $connection)
633
				{
634
					// Are we in SSI mode?  If so try that username and password first
635
					if (ELK == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
636
						$new_connection = @mysqli_connect((!empty($db_persist) ? 'p:' : '') . $db_server, $ssi_db_user, $ssi_db_passwd, $db_name);
637
638
					// Fall back to the regular username and password if need be
639
					if (!$new_connection)
640
						$new_connection = @mysqli_connect((!empty($db_persist) ? 'p:' : '') . $db_server, $db_user, $db_passwd, $db_name);
641
				}
642
643
				if ($new_connection)
644
				{
645
					$this->_connection = $new_connection;
646
647
					// Try a deadlock more than once more.
648
					for ($n = 0; $n < 4; $n++)
649
					{
650
						$ret = $this->query('', $db_string, false, false);
651
652
						$new_errno = mysqli_errno($new_connection);
653
						if ($ret !== false || in_array($new_errno, array(1205, 1213)))
654
							break;
655
					}
656
657
					// If it failed again, shucks to be you... we're not trying it over and over.
658
					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...
659
						return $ret;
660
				}
661
			}
662
			// Are they out of space, perhaps?
663
			elseif ($query_errno == 1030 && (strpos($query_error, ' -1 ') !== false || strpos($query_error, ' 28 ') !== false || strpos($query_error, ' 12 ') !== false))
664
			{
665
				if (!isset($txt))
666
					$query_error .= ' - check database storage space.';
667
				else
668
				{
669
					if (!isset($txt['mysql_error_space']))
670
						loadLanguage('Errors');
671
672
					$query_error .= !isset($txt['mysql_error_space']) ? ' - check database storage space.' : $txt['mysql_error_space'];
673
				}
674
			}
675
		}
676
677
		// Nothing's defined yet... just die with it.
678
		if (empty($context) || empty($txt))
679
			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...
680
681
		// Show an error message, if possible.
682
		$context['error_title'] = $txt['database_error'];
683
		if (allowedTo('admin_forum'))
684
			$context['error_message'] = nl2br($query_error) . '<br />' . $txt['file'] . ': ' . $file . '<br />' . $txt['line'] . ': ' . $line;
685
		else
686
			$context['error_message'] = $txt['try_again'];
687
688
		// Add database version that we know of, for the admin to know. (and ask for support)
689
		if (allowedTo('admin_forum'))
690
			$context['error_message'] .= '<br /><br />' . sprintf($txt['database_error_versions'], $modSettings['elkVersion']);
691
692
		if (allowedTo('admin_forum') && $db_show_debug === true)
693
			$context['error_message'] .= '<br /><br />' . nl2br($db_string);
694
695
		// It's already been logged... don't log it again.
696
		throw new Elk_Exception($context['error_message'], false);
697
	}
698
699
	/**
700
	 * Insert data.
701
	 *
702
	 * @param string $method - options 'replace', 'ignore', 'insert'
703
	 * @param string $table
704
	 * @param mixed[] $columns
705
	 * @param mixed[] $data
706
	 * @param mixed[] $keys
707
	 * @param bool $disable_trans = false
708
	 * @param mysqli|null $connection = null
709
	 * @throws Elk_Exception
710
	 */
711
	public function insert($method = 'replace', $table, $columns, $data, $keys, $disable_trans = false, $connection = null)
712
	{
713
		global $db_prefix;
714
715
		$connection = $connection === null ? $this->_connection : $connection;
716
717
		// With nothing to insert, simply return.
718
		if (empty($data))
719
			return;
720
721
		// Inserting data as a single row can be done as a single array.
722
		if (!is_array($data[array_rand($data)]))
723
			$data = array($data);
724
725
		// Replace the prefix holder with the actual prefix.
726
		$table = str_replace('{db_prefix}', $db_prefix, $table);
727
728
		// Create the mold for a single row insert.
729
		$insertData = '(';
730
		foreach ($columns as $columnName => $type)
731
		{
732
			// Are we restricting the length?
733
			if (strpos($type, 'string-') !== false)
734
				$insertData .= sprintf('SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName);
735
			else
736
				$insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName);
737
		}
738
		$insertData = substr($insertData, 0, -2) . ')';
739
740
		// Create an array consisting of only the columns.
741
		$indexed_columns = array_keys($columns);
742
743
		// Here's where the variables are injected to the query.
744
		$insertRows = array();
745
		foreach ($data as $dataRow)
746
		{
747
			$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

747
			$insertRows[] = $this->quote($insertData, $this->_array_combine($indexed_columns, $dataRow), /** @scrutinizer ignore-type */ $connection);
Loading history...
748
		}
749
750
		// Determine the method of insertion.
751
		$queryTitle = $method === 'replace' ? 'REPLACE' : ($method == 'ignore' ? 'INSERT IGNORE' : 'INSERT');
752
753
		$skip_error = $table === $db_prefix . 'log_errors';
754
		$this->_skip_error = $skip_error;
755
		// Do the insert.
756
		$this->query('', '
757
			' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`)
758
			VALUES
759
				' . implode(',
760
				', $insertRows),
761
			array(
762
				'security_override' => true,
763
			),
764
			$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

764
			/** @scrutinizer ignore-type */ $connection
Loading history...
765
		);
766
	}
767
768
	/**
769
	 * Unescape an escaped string!
770
	 *
771
	 * @param string $string
772
	 */
773
	public function unescape_string($string)
774
	{
775
		return stripslashes($string);
776
	}
777
778
	/**
779
	 * Returns whether the database system supports ignore.
780
	 *
781
	 * @return boolean
782
	 */
783
	public function support_ignore()
784
	{
785
		return true;
786
	}
787
788
	/**
789
	 * Gets all the necessary INSERTs for the table named table_name.
790
	 * It goes in 250 row segments.
791
	 *
792
	 * @param string $tableName - the table to create the inserts for.
793
	 * @param bool $new_table
794
	 *
795
	 * @return string the query to insert the data back in, or an empty string if the table was empty.
796
	 * @throws Elk_Exception
797
	 */
798
	public function insert_sql($tableName, $new_table = false)
799
	{
800
		global $db_prefix;
801
802
		static $start = 0, $num_rows, $fields, $limit;
803
804
		if ($new_table)
805
		{
806
			$limit = strstr($tableName, 'log_') !== false ? 500 : 250;
807
			$start = 0;
808
		}
809
810
		$tableName = str_replace('{db_prefix}', $db_prefix, $tableName);
811
812
		// This will be handy...
813
		$crlf = "\r\n";
814
815
		$result = $this->query('', '
816
			SELECT /*!40001 SQL_NO_CACHE */ *
817
			FROM `' . $tableName . '`
818
			LIMIT ' . $start . ', ' . $limit,
819
			array(
820
				'security_override' => true,
821
			)
822
		);
823
824
		// The number of rows, just for record keeping and breaking INSERTs up.
825
		$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

825
		$num_rows = $this->num_rows(/** @scrutinizer ignore-type */ $result);
Loading history...
826
827
		if ($num_rows == 0)
828
			return '';
829
830
		if ($new_table)
831
		{
832
			$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

832
			$fields = array_keys($this->fetch_assoc(/** @scrutinizer ignore-type */ $result));
Loading history...
833
			$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

833
			$this->data_seek(/** @scrutinizer ignore-type */ $result, 0);
Loading history...
834
		}
835
836
		// Start it off with the basic INSERT INTO.
837
		$data = 'INSERT INTO `' . $tableName . '`' . $crlf . "\t" . '(`' . implode('`, `', $fields) . '`)' . $crlf . 'VALUES ';
838
839
		// Loop through each row.
840
		while ($row = $this->fetch_assoc($result))
841
		{
842
			// Get the fields in this row...
843
			$field_list = array();
844
845
			foreach ($row as $key => $item)
846
			{
847
				// Try to figure out the type of each field. (NULL, number, or 'string'.)
848
				if (!isset($item))
849
					$field_list[] = 'NULL';
850
				elseif (is_numeric($item) && (int) $item == $item)
851
					$field_list[] = $item;
852
				else
853
					$field_list[] = '\'' . $this->escape_string($item) . '\'';
854
			}
855
856
			$data .= '(' . implode(', ', $field_list) . '),' . $crlf . "\t";
857
		}
858
859
		$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

859
		$this->free_result(/** @scrutinizer ignore-type */ $result);
Loading history...
860
		$data = substr(trim($data), 0, -1) . ';' . $crlf . $crlf;
861
862
		$start += $limit;
863
864
		return $data;
865
	}
866
867
	/**
868
	 * Dumps the schema (CREATE) for a table.
869
	 *
870
	 * @param string $tableName - the table
871
	 *
872
	 * @return string - the CREATE statement as string
873
	 * @throws Elk_Exception
874
	 */
875
	public function db_table_sql($tableName)
876
	{
877
		global $db_prefix;
878
879
		$tableName = str_replace('{db_prefix}', $db_prefix, $tableName);
880
881
		// This will be needed...
882
		$crlf = "\r\n";
883
884
		// Drop it if it exists.
885
		$schema_create = 'DROP TABLE IF EXISTS `' . $tableName . '`;' . $crlf . $crlf;
886
887
		// Start the create table...
888
		$schema_create .= 'CREATE TABLE `' . $tableName . '` (' . $crlf;
889
890
		// Find all the fields.
891
		$result = $this->query('', '
892
			SHOW FIELDS
893
			FROM `{raw:table}`',
894
			array(
895
				'table' => $tableName,
896
			)
897
		);
898
		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

898
		while ($row = $this->fetch_assoc(/** @scrutinizer ignore-type */ $result))
Loading history...
899
		{
900
			// Make the CREATE for this column.
901
			$schema_create .= ' `' . $row['Field'] . '` ' . $row['Type'] . ($row['Null'] != 'YES' ? ' NOT NULL' : '');
902
903
			// Add a default...?
904
			if (!empty($row['Default']) || $row['Null'] !== 'YES')
905
			{
906
				// Make a special case of auto-timestamp.
907
				if ($row['Default'] == 'CURRENT_TIMESTAMP')
908
					$schema_create .= ' /*!40102 NOT NULL default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP */';
909
				// Text shouldn't have a default.
910
				elseif ($row['Default'] !== null)
911
				{
912
					// If this field is numeric the default needs no escaping.
913
					$type = strtolower($row['Type']);
914
					$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;
915
916
					$schema_create .= ' default ' . ($isNumericColumn ? $row['Default'] : '\'' . $this->escape_string($row['Default']) . '\'');
917
				}
918
			}
919
920
			// And now any extra information. (such as auto_increment.)
921
			$schema_create .= ($row['Extra'] != '' ? ' ' . $row['Extra'] : '') . ',' . $crlf;
922
		}
923
		$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

923
		$this->free_result(/** @scrutinizer ignore-type */ $result);
Loading history...
924
925
		// Take off the last comma.
926
		$schema_create = substr($schema_create, 0, -strlen($crlf) - 1);
927
928
		// Find the keys.
929
		$result = $this->query('', '
930
			SHOW KEYS
931
			FROM `{raw:table}`',
932
			array(
933
				'table' => $tableName,
934
			)
935
		);
936
		$indexes = array();
937
		while ($row = $this->fetch_assoc($result))
938
		{
939
			// Is this a primary key, unique index, or regular index?
940
			$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'] . '`';
941
942
			// Is this the first column in the index?
943
			if (empty($indexes[$row['Key_name']]))
944
				$indexes[$row['Key_name']] = array();
945
946
			// A sub part, like only indexing 15 characters of a varchar.
947
			if (!empty($row['Sub_part']))
948
				$indexes[$row['Key_name']][$row['Seq_in_index']] = '`' . $row['Column_name'] . '`(' . $row['Sub_part'] . ')';
949
			else
950
				$indexes[$row['Key_name']][$row['Seq_in_index']] = '`' . $row['Column_name'] . '`';
951
		}
952
		$this->free_result($result);
953
954
		// Build the CREATEs for the keys.
955
		foreach ($indexes as $keyname => $columns)
956
		{
957
			// Ensure the columns are in proper order.
958
			ksort($columns);
959
960
			$schema_create .= ',' . $crlf . ' ' . $keyname . ' (' . implode(', ', $columns) . ')';
961
		}
962
963
		// Now just get the comment and type... (MyISAM, etc.)
964
		$result = $this->query('', '
965
			SHOW TABLE STATUS
966
			LIKE {string:table}',
967
			array(
968
				'table' => strtr($tableName, array('_' => '\\_', '%' => '\\%')),
969
			)
970
		);
971
		$row = $this->fetch_assoc($result);
972
		$this->free_result($result);
973
974
		// Probably MyISAM.... and it might have a comment.
975
		$schema_create .= $crlf . ') ENGINE=' . (isset($row['Type']) ? $row['Type'] : $row['Engine']) . ($row['Comment'] != '' ? ' COMMENT="' . $row['Comment'] . '"' : '');
976
977
		return $schema_create;
978
	}
979
980
	/**
981
	 * {@inheritdoc}
982
	 */
983
	public function db_list_tables($db_name_str = false, $filter = false)
984
	{
985
		global $db_name;
986
987
		$db_name_str = $db_name_str === false ? $db_name : $db_name_str;
988
		$db_name_str = trim($db_name_str);
989
		$filter = $filter === false ? '' : ' LIKE \'' . $filter . '\'';
990
991
		$request = $this->query('', '
992
			SHOW TABLES
993
			FROM `{raw:db_name_str}`
994
			{raw:filter}',
995
			array(
996
				'db_name_str' => $db_name_str[0] == '`' ? strtr($db_name_str, array('`' => '')) : $db_name_str,
997
				'filter' => $filter,
998
			)
999
		);
1000
		$tables = array();
1001
		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

1001
		while ($row = $this->fetch_row(/** @scrutinizer ignore-type */ $request))
Loading history...
1002
		{
1003
			$tables[] = $row[0];
1004
		}
1005
		$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

1005
		$this->free_result(/** @scrutinizer ignore-type */ $request);
Loading history...
1006
1007
		return $tables;
1008
	}
1009
1010
	/**
1011
	 * Backup $table_name to $backup_table.
1012
	 *
1013
	 * @param string $table_name
1014
	 * @param string $backup_table
1015
	 *
1016
	 * @return resource - the request handle to the table creation query
1017
	 * @throws Elk_Exception
1018
	 */
1019
	public function db_backup_table($table_name, $backup_table)
1020
	{
1021
		global $db_prefix;
1022
1023
		$table = str_replace('{db_prefix}', $db_prefix, $table_name);
1024
1025
		// First, get rid of the old table.
1026
		$db_table = db_table();
1027
		$db_table->db_drop_table($backup_table);
1028
1029
		// Can we do this the quick way?
1030
		$result = $this->query('', '
1031
			CREATE TABLE {raw:backup_table} LIKE {raw:table}',
1032
			array(
1033
				'backup_table' => $backup_table,
1034
				'table' => $table
1035
		));
1036
		// If this failed, we go old school.
1037
		if ($result)
1038
		{
1039
			$request = $this->query('', '
1040
				INSERT INTO {raw:backup_table}
1041
				SELECT *
1042
				FROM {raw:table}',
1043
				array(
1044
					'backup_table' => $backup_table,
1045
					'table' => $table
1046
				));
1047
1048
			// Old school or no school?
1049
			if ($request)
1050
				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...
1051
		}
1052
1053
		// At this point, the quick method failed.
1054
		$result = $this->query('', '
1055
			SHOW CREATE TABLE {raw:table}',
1056
			array(
1057
				'table' => $table,
1058
			)
1059
		);
1060
		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

1060
		list (, $create) = $this->fetch_row(/** @scrutinizer ignore-type */ $result);
Loading history...
1061
		$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

1061
		$this->free_result(/** @scrutinizer ignore-type */ $result);
Loading history...
1062
1063
		$create = preg_split('/[\n\r]/', $create);
1064
1065
		$auto_inc = '';
1066
1067
		// Default engine type.
1068
		$engine = 'MyISAM';
1069
		$charset = '';
1070
		$collate = '';
1071
1072
		foreach ($create as $k => $l)
1073
		{
1074
			// Get the name of the auto_increment column.
1075
			if (strpos($l, 'auto_increment'))
1076
				$auto_inc = trim($l);
1077
1078
			// For the engine type, see if we can work out what it is.
1079
			if (strpos($l, 'ENGINE') !== false || strpos($l, 'TYPE') !== false)
1080
			{
1081
				// Extract the engine type.
1082
				preg_match('~(ENGINE|TYPE)=(\w+)(\sDEFAULT)?(\sCHARSET=(\w+))?(\sCOLLATE=(\w+))?~', $l, $match);
1083
1084
				if (!empty($match[1]))
1085
					$engine = $match[1];
1086
1087
				if (!empty($match[2]))
1088
					$engine = $match[2];
1089
1090
				if (!empty($match[5]))
1091
					$charset = $match[5];
1092
1093
				if (!empty($match[7]))
1094
					$collate = $match[7];
1095
			}
1096
1097
			// Skip everything but keys...
1098
			if (strpos($l, 'KEY') === false)
1099
				unset($create[$k]);
1100
		}
1101
1102
		if (!empty($create))
1103
			$create = '(
1104
				' . implode('
1105
				', $create) . ')';
1106
		else
1107
			$create = '';
1108
1109
		$request = $this->query('', '
1110
			CREATE TABLE {raw:backup_table} {raw:create}
1111
			ENGINE={raw:engine}' . (empty($charset) ? '' : ' CHARACTER SET {raw:charset}' . (empty($collate) ? '' : ' COLLATE {raw:collate}')) . '
1112
			SELECT *
1113
			FROM {raw:table}',
1114
			array(
1115
				'backup_table' => $backup_table,
1116
				'table' => $table,
1117
				'create' => $create,
1118
				'engine' => $engine,
1119
				'charset' => empty($charset) ? '' : $charset,
1120
				'collate' => empty($collate) ? '' : $collate,
1121
			)
1122
		);
1123
1124
		if ($auto_inc != '')
1125
		{
1126
			if (preg_match('~\`(.+?)\`\s~', $auto_inc, $match) != 0 && substr($auto_inc, -1, 1) == ',')
1127
				$auto_inc = substr($auto_inc, 0, -1);
1128
1129
			$this->query('', '
1130
				ALTER TABLE {raw:backup_table}
1131
				CHANGE COLUMN {raw:column_detail} {raw:auto_inc}',
1132
				array(
1133
					'backup_table' => $backup_table,
1134
					'column_detail' => $match[1],
1135
					'auto_inc' => $auto_inc,
1136
				)
1137
			);
1138
		}
1139
1140
		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...
1141
	}
1142
1143
	/**
1144
	 * Get the version number.
1145
	 *
1146
	 * @return string - the version
1147
	 * @throws Elk_Exception
1148
	 */
1149
	public function db_server_version()
1150
	{
1151
		$request = $this->query('', '
1152
			SELECT VERSION()',
1153
			array(
1154
			)
1155
		);
1156
		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

1156
		list ($ver) = $this->fetch_row(/** @scrutinizer ignore-type */ $request);
Loading history...
1157
		$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

1157
		$this->free_result(/** @scrutinizer ignore-type */ $request);
Loading history...
1158
1159
		return $ver;
1160
	}
1161
1162
	/**
1163
	 * Get the name (title) of the database system.
1164
	 *
1165
	 * @return string
1166
	 */
1167
	public function db_title()
1168
	{
1169
		return 'MySQL';
1170
	}
1171
1172
	/**
1173
	 * Whether the database system is case sensitive.
1174
	 *
1175
	 * @return false
1176
	 */
1177
	public function db_case_sensitive()
1178
	{
1179
		return false;
1180
	}
1181
1182
	/**
1183
	 * Escape string for the database input
1184
	 *
1185
	 * @param string $string
1186
	 */
1187
	public function escape_string($string)
1188
	{
1189
		$string = $this->_clean_4byte_chars($string);
1190
1191
		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 $link 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

1191
		return mysqli_real_escape_string(/** @scrutinizer ignore-type */ $this->_connection, $string);
Loading history...
1192
	}
1193
1194
	/**
1195
	 * Fetch next result as association.
1196
	 * The mysql implementation simply delegates to mysqli_fetch_assoc().
1197
	 * It ignores $counter parameter.
1198
	 *
1199
	 * @param mysqli_result $request
1200
	 * @param int|bool $counter = false
1201
	 */
1202
	public function fetch_assoc($request, $counter = false)
1203
	{
1204
		return mysqli_fetch_assoc($request);
1205
	}
1206
1207
	/**
1208
	 * Return server info.
1209
	 *
1210
	 * @param mysqli|null $connection
1211
	 *
1212
	 * @return string
1213
	 */
1214
	public function db_server_info($connection = null)
1215
	{
1216
		// Decide which connection to use
1217
		$connection = $connection === null ? $this->_connection : $connection;
1218
1219
		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 $link 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

1219
		return mysqli_get_server_info(/** @scrutinizer ignore-type */ $connection);
Loading history...
1220
	}
1221
1222
	/**
1223
	 *  Get the version number.
1224
	 *
1225
	 * @return string - the version
1226
	 * @throws Elk_Exception
1227
	 */
1228
	public function db_client_version()
1229
	{
1230
		$request = $this->query('', '
1231
			SELECT VERSION()',
1232
			array(
1233
			)
1234
		);
1235
		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

1235
		list ($ver) = $this->fetch_row(/** @scrutinizer ignore-type */ $request);
Loading history...
1236
		$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

1236
		$this->free_result(/** @scrutinizer ignore-type */ $request);
Loading history...
1237
1238
		return $ver;
1239
	}
1240
1241
	/**
1242
	 * Select database.
1243
	 *
1244
	 * @param string|null $dbName = null
1245
	 * @param mysqli|null $connection = null
1246
	 */
1247
	public function select_db($dbName = null, $connection = null)
1248
	{
1249
		// Decide which connection to use
1250
		$connection = $connection === null ? $this->_connection : $connection;
1251
1252
		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 $link 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

1252
		return mysqli_select_db(/** @scrutinizer ignore-type */ $connection, $dbName);
Loading history...
1253
	}
1254
1255
	/**
1256
	 * Returns a reference to the existing instance
1257
	 */
1258
	public static function db()
1259
	{
1260
		return self::$_db;
1261
	}
1262
1263
	/**
1264
	 * Finds out if the connection is still valid.
1265
	 *
1266
	 * @param mysqli|null $connection = null
1267
	 */
1268
	public function validConnection($connection = null)
1269
	{
1270
		return is_object($connection);
1271
	}
1272
}
1273