Completed
Push — release-2.1 ( dd6be9...02c57b )
by John
33:22 queued 26:19
created

Subs-Db-mysql.php ➔ smf_is_resource()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file has all the main functions in it that relate to the database.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2017 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 3
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 *  Maps the implementations in this file (smf_db_function_name)
21
 *  to the $smcFunc['db_function_name'] variable.
22
 *
23
 * @param string $db_server The database server
24
 * @param string $db_name The name of the database
25
 * @param string $db_user The database username
26
 * @param string $db_passwd The database password
27
 * @param string $db_prefix The table prefix
28
 * @param array $db_options An array of database options
29
 * @return null|resource Returns null on failure if $db_options['non_fatal'] is true or a MySQL connection resource handle if the connection was successful.
30
 */
31
function smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $db_options = array())
0 ignored issues
show
Unused Code introduced by
The parameter $db_prefix is not used and could be removed.

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

Loading history...
32
{
33
	global $smcFunc, $mysql_set_mode;
34
35
	// Map some database specific functions, only do this once.
36 View Code Duplication
	if (!isset($smcFunc['db_fetch_assoc']) || $smcFunc['db_fetch_assoc'] != 'mysqli_fetch_assoc')
37
		$smcFunc += array(
38
			'db_query'                  => 'smf_db_query',
39
			'db_quote'                  => 'smf_db_quote',
40
			'db_fetch_assoc'            => 'mysqli_fetch_assoc',
41
			'db_fetch_row'              => 'mysqli_fetch_row',
42
			'db_free_result'            => 'mysqli_free_result',
43
			'db_insert'                 => 'smf_db_insert',
44
			'db_insert_id'              => 'smf_db_insert_id',
45
			'db_num_rows'               => 'mysqli_num_rows',
46
			'db_data_seek'              => 'mysqli_data_seek',
47
			'db_num_fields'             => 'mysqli_num_fields',
48
			'db_escape_string'          => 'addslashes',
49
			'db_unescape_string'        => 'stripslashes',
50
			'db_server_info'            => 'smf_db_get_server_info',
51
			'db_affected_rows'          => 'smf_db_affected_rows',
52
			'db_transaction'            => 'smf_db_transaction',
53
			'db_error'                  => 'mysqli_error',
54
			'db_select_db'              => 'smf_db_select',
55
			'db_title'                  => 'MySQLi',
56
			'db_sybase'                 => false,
57
			'db_case_sensitive'         => false,
58
			'db_escape_wildcard_string' => 'smf_db_escape_wildcard_string',
59
			'db_is_resource'            => 'smf_is_resource',
60
		);
61
62
	if (!empty($db_options['persist']))
63
		$db_server = 'p:' . $db_server;
64
65
	$connection = mysqli_init();
66
	
67
	$flags = MYSQLI_CLIENT_FOUND_ROWS;
68
	
69
	$success = false;
70
	
71
	if ($connection) {
72
		if (!empty($db_options['port']))
73
			$success = mysqli_real_connect($connection, $db_server, $db_user, $db_passwd, '', $db_options['port'], null, $flags);
74
		else
75
			$success = mysqli_real_connect($connection, $db_server, $db_user, $db_passwd, '', 0, null, $flags);
76
	}
77
78
	// Something's wrong, show an error if its fatal (which we assume it is)
79
	if ($success === false)
80
	{
81
		if (!empty($db_options['non_fatal']))
82
			return null;
83
		else
84
			display_db_error();
85
	}
86
87
	// Select the database, unless told not to
88
	if (empty($db_options['dont_select_db']) && !@mysqli_select_db($connection, $db_name) && empty($db_options['non_fatal']))
89
		display_db_error();
90
91
	// This makes it possible to have SMF automatically change the sql_mode and autocommit if needed.
92
	if (isset($mysql_set_mode) && $mysql_set_mode === true)
93
		$smcFunc['db_query']('', 'SET sql_mode = \'\', AUTOCOMMIT = 1',
94
		array(),
95
		false
96
	);
97
98
	return $connection;
99
}
100
101
/**
102
 * Extend the database functionality. It calls the respective file's init
103
 * to add the implementations in that file to $smcFunc array.
104
 *
105
 * @param string $type Indicates which additional file to load. ('extra', 'packages')
106
 */
107
function db_extend($type = 'extra')
108
{
109
	global $sourcedir;
110
111
	// we force the MySQL files as nothing syntactically changes with MySQLi
112
	require_once($sourcedir . '/Db' . strtoupper($type[0]) . substr($type, 1) . '-mysql.php');
113
	$initFunc = 'db_' . $type . '_init';
114
	$initFunc();
115
}
116
117
/**
118
 * Fix up the prefix so it doesn't require the database to be selected.
119
 *
120
 * @param string &$db_prefix The table prefix
121
 * @param string $db_name The database name
122
 */
123
function db_fix_prefix(&$db_prefix, $db_name)
0 ignored issues
show
Unused Code introduced by
The parameter $db_prefix is not used and could be removed.

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

Loading history...
Unused Code introduced by
The parameter $db_name is not used and could be removed.

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

Loading history...
124
{
125
	$db_prefix = is_numeric(substr($db_prefix, 0, 1)) ? $db_name . '.' . $db_prefix : '`' . $db_name . '`.' . $db_prefix;
126
}
127
128
/**
129
 * Wrap mysqli_select_db so the connection does not need to be specified
130
 *
131
 * @param string &$database The database
132
 * @param object $connection The connection object (if null, $db_connection is used)
133
 * @return bool Whether the database was selected
134
 */
135
function smf_db_select($database, $connection = null)
136
{
137
	global $db_connection;
138
	return mysqli_select_db($connection === null ? $db_connection : $connection, $database);
139
}
140
141
/**
142
 * Wrap mysqli_get_server_info so the connection does not need to be specified
143
 *
144
 * @param object $connection The connection to use (if null, $db_connection is used)
145
 * @return string The server info
146
 */
147
function smf_db_get_server_info($connection = null)
148
{
149
	global $db_connection;
150
	return mysqli_get_server_info($connection === null ? $db_connection : $connection);
151
}
152
153
/**
154
 * Callback for preg_replace_callback on the query.
155
 * It allows to replace on the fly a few pre-defined strings, for convenience ('query_see_board', 'query_wanna_see_board'), with
156
 * their current values from $user_info.
157
 * In addition, it performs checks and sanitization on the values sent to the database.
158
 *
159
 * @param array $matches The matches from preg_replace_callback
160
 * @return string The appropriate string depending on $matches[1]
161
 */
162
function smf_db_replacement__callback($matches)
163
{
164
	global $db_callback, $user_info, $db_prefix, $smcFunc;
165
166
	list ($values, $connection) = $db_callback;
167
	if (!is_object($connection))
168
		display_db_error();
169
170
	if ($matches[1] === 'db_prefix')
171
		return $db_prefix;
172
173
	if ($matches[1] === 'query_see_board')
174
		return $user_info['query_see_board'];
175
176
	if ($matches[1] === 'query_wanna_see_board')
177
		return $user_info['query_wanna_see_board'];
178
179
	if ($matches[1] === 'empty')
180
		return '\'\'';
181
182
	if (!isset($matches[2]))
183
		smf_db_error_backtrace('Invalid value inserted or no type specified.', '', E_USER_ERROR, __FILE__, __LINE__);
184
185
	if ($matches[1] === 'literal')
186
		return '\'' . mysqli_real_escape_string($connection, $matches[2]) . '\'';
187
188
	if (!isset($values[$matches[2]]))
189
		smf_db_error_backtrace('The database value you\'re trying to insert does not exist: ' . (isset($smcFunc['htmlspecialchars']) ? $smcFunc['htmlspecialchars']($matches[2]) : htmlspecialchars($matches[2])), '', E_USER_ERROR, __FILE__, __LINE__);
190
191
	$replacement = $values[$matches[2]];
192
193
	switch ($matches[1])
194
	{
195
		case 'int':
196
			if (!is_numeric($replacement) || (string) $replacement !== (string) (int) $replacement)
197
				smf_db_error_backtrace('Wrong value type sent to the database. Integer expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
198
			return (string) (int) $replacement;
199
		break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
200
201
		case 'string':
202
		case 'text':
203
			return sprintf('\'%1$s\'', mysqli_real_escape_string($connection, $replacement));
204
		break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
205
206
		case 'array_int':
207
			if (is_array($replacement))
208
			{
209
				if (empty($replacement))
210
					smf_db_error_backtrace('Database error, given array of integer values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
211
212
				foreach ($replacement as $key => $value)
213
				{
214
					if (!is_numeric($value) || (string) $value !== (string) (int) $value)
215
						smf_db_error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
216
217
					$replacement[$key] = (string) (int) $value;
218
				}
219
220
				return implode(', ', $replacement);
221
			}
222
			else
223
				smf_db_error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
224
225
		break;
226
227
		case 'array_string':
228
			if (is_array($replacement))
229
			{
230
				if (empty($replacement))
231
					smf_db_error_backtrace('Database error, given array of string values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
232
233
				foreach ($replacement as $key => $value)
234
					$replacement[$key] = sprintf('\'%1$s\'', mysqli_real_escape_string($connection, $value));
235
236
				return implode(', ', $replacement);
237
			}
238
			else
239
				smf_db_error_backtrace('Wrong value type sent to the database. Array of strings expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
240
		break;
241
242
		case 'date':
243
			if (preg_match('~^(\d{4})-([0-1]?\d)-([0-3]?\d)$~', $replacement, $date_matches) === 1)
244
				return sprintf('\'%04d-%02d-%02d\'', $date_matches[1], $date_matches[2], $date_matches[3]);
245
			else
246
				smf_db_error_backtrace('Wrong value type sent to the database. Date expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
247
		break;
248
249
		case 'time':
250
			if (preg_match('~^([0-1]?\d|2[0-3]):([0-5]\d):([0-5]\d)$~', $replacement, $time_matches) === 1)
251
				return sprintf('\'%02d:%02d:%02d\'', $time_matches[1], $time_matches[2], $time_matches[3]);
252
			else
253
				smf_db_error_backtrace('Wrong value type sent to the database. Time expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
254
		break;
255
256
		case 'float':
257
			if (!is_numeric($replacement))
258
				smf_db_error_backtrace('Wrong value type sent to the database. Floating point number expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
259
			return (string) (float) $replacement;
260
		break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
261
262
		case 'identifier':
263
			// Backticks inside identifiers are supported as of MySQL 4.1. We don't need them for SMF.
264
			return '`' . strtr($replacement, array('`' => '', '.' => '')) . '`';
265
		break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
266
267
		case 'raw':
268
			return $replacement;
269
		break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
270
271
		case 'inet':
272
			if ($replacement == 'null' || $replacement == '')
273
				return 'null';
274
			if (!isValidIP($replacement))
275
				smf_db_error_backtrace('Wrong value type sent to the database. IPv4 or IPv6 expected.(' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
276
			//we don't use the native support of mysql > 5.6.2
277
			return sprintf('unhex(\'%1$s\')', bin2hex(inet_pton($replacement)));
278
279
		case 'array_inet':
280
			if (is_array($replacement))
281
			{
282
				if (empty($replacement))
283
					smf_db_error_backtrace('Database error, given array of IPv4 or IPv6 values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
284
285
				foreach ($replacement as $key => $value)
286
				{
287
					if ($replacement == 'null' || $replacement == '')
288
						$replacement[$key] = 'null';
289
					if (!isValidIP($value))
290
						smf_db_error_backtrace('Wrong value type sent to the database. IPv4 or IPv6 expected.(' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
291
					$replacement[$key] = sprintf('unhex(\'%1$s\')', bin2hex(inet_pton($value)));
292
				}
293
294
				return implode(', ', $replacement);
295
			}
296
			else
297
				smf_db_error_backtrace('Wrong value type sent to the database. Array of IPv4 or IPv6 expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
298
		break;
299
300
		default:
301
			smf_db_error_backtrace('Undefined type used in the database query. (' . $matches[1] . ':' . $matches[2] . ')', '', false, __FILE__, __LINE__);
302
		break;
303
	}
304
}
305
306
/**
307
 * Just like the db_query, escape and quote a string, but not executing the query.
308
 *
309
 * @param string $db_string The database string
310
 * @param array $db_values An array of values to be injected into the string
311
 * @param resource $connection = null The connection to use (null to use $db_connection)
312
 * @return string The string with the values inserted
313
 */
314 View Code Duplication
function smf_db_quote($db_string, $db_values, $connection = null)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
315
{
316
	global $db_callback, $db_connection;
317
318
	// Only bother if there's something to replace.
319
	if (strpos($db_string, '{') !== false)
320
	{
321
		// This is needed by the callback function.
322
		$db_callback = array($db_values, $connection === null ? $db_connection : $connection);
323
324
		// Do the quoting and escaping
325
		$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string);
326
327
		// Clear this global variable.
328
		$db_callback = array();
329
	}
330
331
	return $db_string;
332
}
333
334
/**
335
 * Do a query.  Takes care of errors too.
336
 *
337
 * @param string $identifier An identifier. Only used in Postgres when we need to do things differently...
338
 * @param string $db_string The database string
339
 * @param array $db_values = array() The values to be inserted into the string
340
 * @param resource $connection = null The connection to use (null to use $db_connection)
341
 * @return resource|bool Returns a MySQL result resource (for SELECT queries), true (for UPDATE queries) or false if the query failed
342
 */
343
function smf_db_query($identifier, $db_string, $db_values = array(), $connection = null)
0 ignored issues
show
Unused Code introduced by
The parameter $identifier is not used and could be removed.

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

Loading history...
344
{
345
	global $db_cache, $db_count, $db_connection, $db_show_debug, $time_start;
346
	global $db_unbuffered, $db_callback, $modSettings;
347
348
	// Comments that are allowed in a query are preg_removed.
349
	static $allowed_comments_from = array(
350
		'~\s+~s',
351
		'~/\*!40001 SQL_NO_CACHE \*/~',
352
		'~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~',
353
		'~/\*!40100 ON DUPLICATE KEY UPDATE id_msg = \d+ \*/~',
354
	);
355
	static $allowed_comments_to = array(
356
		' ',
357
		'',
358
		'',
359
		'',
360
	);
361
362
	// Decide which connection to use.
363
	$connection = $connection === null ? $db_connection : $connection;
364
365
	// Get a connection if we are shutting down, sometimes the link is closed before sessions are written
366
	if (!is_object($connection))
367
	{
368
		global $db_server, $db_user, $db_passwd, $db_name, $db_show_debug, $ssi_db_user, $ssi_db_passwd;
369
370
		// Are we in SSI mode?  If so try that username and password first
371
		if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
372
		{
373
			if (empty($db_persist))
0 ignored issues
show
Bug introduced by
The variable $db_persist seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
374
				$db_connection = @mysqli_connect($db_server, $ssi_db_user, $ssi_db_passwd);
375
			else
376
				$db_connection = @mysqli_connect('p:' . $db_server, $ssi_db_user, $ssi_db_passwd);
377
		}
378
		// Fall back to the regular username and password if need be
379
		if (!$db_connection)
380
		{
381
			if (empty($db_persist))
382
				$db_connection = @mysqli_connect($db_server, $db_user, $db_passwd);
383
			else
384
				$db_connection = @mysqli_connect('p:' . $db_server, $db_user, $db_passwd);
385
		}
386
387
		if (!$db_connection || !@mysqli_select_db($db_connection, $db_name))
388
			$db_connection = false;
389
390
		$connection = $db_connection;
391
	}
392
393
	// One more query....
394
	$db_count = !isset($db_count) ? 1 : $db_count + 1;
395
396 View Code Duplication
	if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override']))
397
		smf_db_error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__);
398
399
	// Use "ORDER BY null" to prevent Mysql doing filesorts for Group By clauses without an Order By
400
	if (strpos($db_string, 'GROUP BY') !== false && strpos($db_string, 'ORDER BY') === false && preg_match('~^\s+SELECT~i', $db_string))
401
	{
402
		// Add before LIMIT
403
		if ($pos = strpos($db_string, 'LIMIT '))
404
			$db_string = substr($db_string, 0, $pos) . "\t\t\tORDER BY null\n" . substr($db_string, $pos, strlen($db_string));
405
		else
406
			// Append it.
407
			$db_string .= "\n\t\t\tORDER BY null";
408
	}
409
410 View Code Duplication
	if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false))
411
	{
412
		// Pass some values to the global space for use in the callback function.
413
		$db_callback = array($db_values, $connection);
414
415
		// Inject the values passed to this function.
416
		$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string);
417
418
		// This shouldn't be residing in global space any longer.
419
		$db_callback = array();
420
	}
421
422
	// Debugging.
423 View Code Duplication
	if (isset($db_show_debug) && $db_show_debug === true)
424
	{
425
		// Get the file and line number this function was called.
426
		list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__);
427
428
		// Initialize $db_cache if not already initialized.
429
		if (!isset($db_cache))
430
			$db_cache = array();
431
432
		if (!empty($_SESSION['debug_redirect']))
433
		{
434
			$db_cache = array_merge($_SESSION['debug_redirect'], $db_cache);
435
			$db_count = count($db_cache) + 1;
436
			$_SESSION['debug_redirect'] = array();
437
		}
438
439
		// Don't overload it.
440
		$st = microtime();
441
		$db_cache[$db_count]['q'] = $db_count < 50 ? $db_string : '...';
442
		$db_cache[$db_count]['f'] = $file;
443
		$db_cache[$db_count]['l'] = $line;
444
		$db_cache[$db_count]['s'] = array_sum(explode(' ', $st)) - array_sum(explode(' ', $time_start));
445
	}
446
447
	// First, we clean strings out of the query, reduce whitespace, lowercase, and trim - so we can check it over.
448 View Code Duplication
	if (empty($modSettings['disableQueryCheck']))
449
	{
450
		$clean = '';
451
		$old_pos = 0;
452
		$pos = -1;
453
		while (true)
454
		{
455
			$pos = strpos($db_string, '\'', $pos + 1);
456
			if ($pos === false)
457
				break;
458
			$clean .= substr($db_string, $old_pos, $pos - $old_pos);
459
460
			while (true)
461
			{
462
				$pos1 = strpos($db_string, '\'', $pos + 1);
463
				$pos2 = strpos($db_string, '\\', $pos + 1);
464
				if ($pos1 === false)
465
					break;
466
				elseif ($pos2 === false || $pos2 > $pos1)
467
				{
468
					$pos = $pos1;
469
					break;
470
				}
471
472
				$pos = $pos2 + 1;
473
			}
474
			$clean .= ' %s ';
475
476
			$old_pos = $pos + 1;
477
		}
478
		$clean .= substr($db_string, $old_pos);
479
		$clean = trim(strtolower(preg_replace($allowed_comments_from, $allowed_comments_to, $clean)));
480
481
		// Comments?  We don't use comments in our queries, we leave 'em outside!
482
		if (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false)
483
			$fail = true;
484
		// Trying to change passwords, slow us down, or something?
485
		elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[_a-z])~s', $clean) != 0)
486
			$fail = true;
487
		elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
488
			$fail = true;
489
490
		if (!empty($fail) && function_exists('log_error'))
491
			smf_db_error_backtrace('Hacking attempt...', 'Hacking attempt...' . "\n" . $db_string, E_USER_ERROR, __FILE__, __LINE__);
492
	}
493
494
	if (empty($db_unbuffered))
495
		$ret = @mysqli_query($connection, $db_string);
496
	else
497
		$ret = @mysqli_query($connection, $db_string, MYSQLI_USE_RESULT);
498
499 View Code Duplication
	if ($ret === false && empty($db_values['db_error_skip']))
500
		$ret = smf_db_error($db_string, $connection);
501
502
	// Debugging.
503 View Code Duplication
	if (isset($db_show_debug) && $db_show_debug === true)
504
		$db_cache[$db_count]['t'] = array_sum(explode(' ', microtime())) - array_sum(explode(' ', $st));
0 ignored issues
show
Bug introduced by
The variable $st does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
505
506
	return $ret;
507
}
508
509
/**
510
 * affected_rows
511
 * @param resource $connection A connection to use (if null, $db_connection is used)
512
 * @return int The number of rows affected by the last query
513
 */
514
function smf_db_affected_rows($connection = null)
515
{
516
	global $db_connection;
517
518
	return mysqli_affected_rows($connection === null ? $db_connection : $connection);
519
}
520
521
/**
522
 * Gets the ID of the most recently inserted row.
523
 *
524
 * @param string $table The table (only used for Postgres)
525
 * @param string $field = null The specific field (not used here)
526
 * @param resource $connection = null The connection (if null, $db_connection is used)
527
 * @return int The ID of the most recently inserted row
528
 */
529
function smf_db_insert_id($table, $field = null, $connection = null)
0 ignored issues
show
Unused Code introduced by
The parameter $table is not used and could be removed.

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

Loading history...
Unused Code introduced by
The parameter $field is not used and could be removed.

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

Loading history...
Unused Code introduced by
The parameter $connection is not used and could be removed.

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

Loading history...
530
{
531
	global $db_connection;
532
533
	// MySQL doesn't need the table or field information.
534
	return mysqli_insert_id($connection === null ? $db_connection : $connection);
535
}
536
537
/**
538
 * Do a transaction.
539
 *
540
 * @param string $type The step to perform (i.e. 'begin', 'commit', 'rollback')
541
 * @param resource $connection The connection to use (if null, $db_connection is used)
542
 * @return bool True if successful, false otherwise
543
 */
544 View Code Duplication
function smf_db_transaction($type = 'commit', $connection = null)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
545
{
546
	global $db_connection;
547
548
	// Decide which connection to use
549
	$connection = $connection === null ? $db_connection : $connection;
550
551
	if ($type == 'begin')
552
		return @mysqli_query($connection, 'BEGIN');
553
	elseif ($type == 'rollback')
554
		return @mysqli_query($connection, 'ROLLBACK');
555
	elseif ($type == 'commit')
556
		return @mysqli_query($connection, 'COMMIT');
557
558
	return false;
559
}
560
561
/**
562
 * Database error!
563
 * Backtrace, log, try to fix.
564
 *
565
 * @param string $db_string The DB string
566
 * @param object $connection The connection to use (if null, $db_connection is used)
567
 */
568
function smf_db_error($db_string, $connection = null)
569
{
570
	global $txt, $context, $sourcedir, $webmaster_email, $modSettings;
571
	global $db_connection, $db_last_error, $db_persist;
572
	global $db_server, $db_user, $db_passwd, $db_name, $db_show_debug, $ssi_db_user, $ssi_db_passwd;
573
	global $smcFunc;
574
575
	// Get the file and line numbers.
576
	list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__);
577
578
	// Decide which connection to use
579
	$connection = $connection === null ? $db_connection : $connection;
580
581
	// This is the error message...
582
	$query_error = mysqli_error($connection);
583
	$query_errno = mysqli_errno($connection);
584
585
	// Error numbers:
586
	//    1016: Can't open file '....MYI'
587
	//    1030: Got error ??? from table handler.
588
	//    1034: Incorrect key file for table.
589
	//    1035: Old key file for table.
590
	//    1205: Lock wait timeout exceeded.
591
	//    1213: Deadlock found.
592
	//    2006: Server has gone away.
593
	//    2013: Lost connection to server during query.
594
595
	// Log the error.
596
	if ($query_errno != 1213 && $query_errno != 1205 && function_exists('log_error'))
597
		log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n$db_string" : ''), 'database', $file, $line);
598
599
	// Database error auto fixing ;).
600
	if (function_exists('cache_get_data') && (!isset($modSettings['autoFixDatabase']) || $modSettings['autoFixDatabase'] == '1'))
601
	{
602
		// Force caching on, just for the error checking.
603
		$old_cache = @$modSettings['cache_enable'];
604
		$modSettings['cache_enable'] = '1';
605
606
		if (($temp = cache_get_data('db_last_error', 600)) !== null)
607
			$db_last_error = max(@$db_last_error, $temp);
608
609
		if (@$db_last_error < time() - 3600 * 24 * 3)
610
		{
611
			// We know there's a problem... but what?  Try to auto detect.
612
			if ($query_errno == 1030 && strpos($query_error, ' 127 ') !== false)
613
			{
614
				preg_match_all('~(?:[\n\r]|^)[^\']+?(?:FROM|JOIN|UPDATE|TABLE) ((?:[^\n\r(]+?(?:, )?)*)~s', $db_string, $matches);
615
616
				$fix_tables = array();
617
				foreach ($matches[1] as $tables)
618
				{
619
					$tables = array_unique(explode(',', $tables));
620
					foreach ($tables as $table)
621
					{
622
						// Now, it's still theoretically possible this could be an injection.  So backtick it!
623
						if (trim($table) != '')
624
							$fix_tables[] = '`' . strtr(trim($table), array('`' => '')) . '`';
625
					}
626
				}
627
628
				$fix_tables = array_unique($fix_tables);
629
			}
630
			// Table crashed.  Let's try to fix it.
631
			elseif ($query_errno == 1016)
632
			{
633
				if (preg_match('~\'([^\.\']+)~', $query_error, $match) != 0)
634
					$fix_tables = array('`' . $match[1] . '`');
635
			}
636
			// Indexes crashed.  Should be easy to fix!
637
			elseif ($query_errno == 1034 || $query_errno == 1035)
638
			{
639
				preg_match('~\'([^\']+?)\'~', $query_error, $match);
640
				$fix_tables = array('`' . $match[1] . '`');
641
			}
642
		}
643
644
		// 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...)
645
		if (!empty($fix_tables))
646
		{
647
			// Subs-Admin.php for updateSettingsFile(), Subs-Post.php for sendmail().
648
			require_once($sourcedir . '/Subs-Admin.php');
649
			require_once($sourcedir . '/Subs-Post.php');
650
651
			// Make a note of the REPAIR...
652
			cache_put_data('db_last_error', time(), 600);
653
			if (($temp = cache_get_data('db_last_error', 600)) === null)
654
				updateSettingsFile(array('db_last_error' => time()));
655
656
			// Attempt to find and repair the broken table.
657
			foreach ($fix_tables as $table)
658
				$smcFunc['db_query']('', "
659
					REPAIR TABLE $table", false, false);
660
661
			// And send off an email!
662
			sendmail($webmaster_email, $txt['database_error'], $txt['tried_to_repair'], null, 'dberror');
663
664
			$modSettings['cache_enable'] = $old_cache;
665
666
			// Try the query again...?
667
			$ret = $smcFunc['db_query']('', $db_string, false, false);
668
			if ($ret !== false)
669
				return $ret;
670
		}
671
		else
672
			$modSettings['cache_enable'] = $old_cache;
673
674
		// Check for the "lost connection" or "deadlock found" errors - and try it just one more time.
675
		if (in_array($query_errno, array(1205, 1213, 2006, 2013)))
676
		{
677
			if (in_array($query_errno, array(2006, 2013)) && $db_connection == $connection)
678
			{
679
				// Are we in SSI mode?  If so try that username and password first
680
				if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
681
				{
682
					if (empty($db_persist))
683
						$db_connection = @mysqli_connect($db_server, $ssi_db_user, $ssi_db_passwd);
684
					else
685
						$db_connection = @mysqli_connect('p:' . $db_server, $ssi_db_user, $ssi_db_passwd);
686
				}
687
				// Fall back to the regular username and password if need be
688
				if (!$db_connection)
689
				{
690
					if (empty($db_persist))
691
						$db_connection = @mysqli_connect($db_server, $db_user, $db_passwd);
692
					else
693
						$db_connection = @mysqli_connect('p:' . $db_server, $db_user, $db_passwd);
694
				}
695
696
				if (!$db_connection || !@mysqli_select_db($db_connection, $db_name))
697
					$db_connection = false;
698
			}
699
700
			if ($db_connection)
701
			{
702
				// Try a deadlock more than once more.
703
				for ($n = 0; $n < 4; $n++)
704
				{
705
					$ret = $smcFunc['db_query']('', $db_string, false, false);
706
707
					$new_errno = mysqli_errno($db_connection);
708
					if ($ret !== false || in_array($new_errno, array(1205, 1213)))
709
						break;
710
				}
711
712
				// If it failed again, shucks to be you... we're not trying it over and over.
713
				if ($ret !== false)
714
					return $ret;
0 ignored issues
show
Bug introduced by
The variable $ret does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
715
			}
716
		}
717
		// Are they out of space, perhaps?
718
		elseif ($query_errno == 1030 && (strpos($query_error, ' -1 ') !== false || strpos($query_error, ' 28 ') !== false || strpos($query_error, ' 12 ') !== false))
719
		{
720
			if (!isset($txt))
721
				$query_error .= ' - check database storage space.';
722
			else
723
			{
724
				if (!isset($txt['mysql_error_space']))
725
					loadLanguage('Errors');
726
727
				$query_error .= !isset($txt['mysql_error_space']) ? ' - check database storage space.' : $txt['mysql_error_space'];
728
			}
729
		}
730
	}
731
732
	// Nothing's defined yet... just die with it.
733
	if (empty($context) || empty($txt))
734
		die($query_error);
735
736
	// Show an error message, if possible.
737
	$context['error_title'] = $txt['database_error'];
738
	if (allowedTo('admin_forum'))
739
		$context['error_message'] = nl2br($query_error) . '<br>' . $txt['file'] . ': ' . $file . '<br>' . $txt['line'] . ': ' . $line;
740
	else
741
		$context['error_message'] = $txt['try_again'];
742
743
	if (allowedTo('admin_forum') && isset($db_show_debug) && $db_show_debug === true)
744
	{
745
		$context['error_message'] .= '<br><br>' . nl2br($db_string);
746
	}
747
748
	// It's already been logged... don't log it again.
749
	fatal_error($context['error_message'], false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
750
}
751
752
/**
753
 * Inserts data into a table
754
 *
755
 * @param string $method The insert method - can be 'replace', 'ignore' or 'insert'
756
 * @param string $table The table we're inserting the data into
757
 * @param array $columns An array of the columns we're inserting the data into. Should contain 'column' => 'datatype' pairs
758
 * @param array $data The data to insert
759
 * @param array $keys The keys for the table
760
 * @param int returnmode 0 = nothing(default), 1 = last row id, 2 = all rows id as array; every mode runs only with method = ''
761
 * @param object $connection The connection to use (if null, $db_connection is used)
762
 * @return value of the first key, behavior based on returnmode
763
 */
764
function smf_db_insert($method = 'replace', $table, $columns, $data, $keys, $returnmode = 0, $connection = null)
765
{
766
	global $smcFunc, $db_connection, $db_prefix;
767
768
	$connection = $connection === null ? $db_connection : $connection;
769
770
	// With nothing to insert, simply return.
771
	if (empty($data))
772
		return;
773
774
	// Replace the prefix holder with the actual prefix.
775
	$table = str_replace('{db_prefix}', $db_prefix, $table);
776
777
	// Inserting data as a single row can be done as a single array.
778
	if (!is_array($data[array_rand($data)]))
779
		$data = array($data);
780
781
	// Create the mold for a single row insert.
782
	$insertData = '(';
783
	foreach ($columns as $columnName => $type)
784
	{
785
		// Are we restricting the length?
786
		if (strpos($type, 'string-') !== false)
787
			$insertData .= sprintf('SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName);
788
		else
789
			$insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName);
790
	}
791
	$insertData = substr($insertData, 0, -2) . ')';
792
793
	// Create an array consisting of only the columns.
794
	$indexed_columns = array_keys($columns);
795
796
	// Here's where the variables are injected to the query.
797
	$insertRows = array();
798
	foreach ($data as $dataRow)
799
		$insertRows[] = smf_db_quote($insertData, array_combine($indexed_columns, $dataRow), $connection);
800
801
	// Determine the method of insertion.
802
	$queryTitle = $method == 'replace' ? 'REPLACE' : ($method == 'ignore' ? 'INSERT IGNORE' : 'INSERT');
803
804
	// Do the insert.
805
	$smcFunc['db_query']('', '
806
		' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`)
807
		VALUES
808
			' . implode(',
809
			', $insertRows),
810
		array(
811
			'security_override' => true,
812
			'db_error_skip' => $table === $db_prefix . 'log_errors',
813
		),
814
		$connection
815
	);
816
	
817
	if(!empty($keys) && (count($keys) > 0) && $method == '' && $returnmode > 0)
818
	{
819
		if ($returnmode == 1)
820
			$return_var = smf_db_insert_id($table, $keys[0]) + count($insertRows) - 1;
821
		else if ($returnmode == 2)
822
		{
823
			$return_var = array();
824
			$count = count($insertRows);
825
			$start = smf_db_insert_id($table, $keys[0]);
826
			for ($i = 0; $i < $count; $i++ )
827
				$return_var[] = $start + $i;
828
		}
829
		return $return_var;
0 ignored issues
show
Bug introduced by
The variable $return_var does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
830
	}
831
}
832
833
/**
834
 * This function tries to work out additional error information from a back trace.
835
 *
836
 * @param string $error_message The error message
837
 * @param string $log_message The message to log
838
 * @param string|bool $error_type What type of error this is
839
 * @param string $file The file the error occurred in
840
 * @param int $line What line of $file the code which generated the error is on
841
 * @return void|array Returns an array with the file and line if $error_type is 'return'
842
 */
843 View Code Duplication
function smf_db_error_backtrace($error_message, $log_message = '', $error_type = false, $file = null, $line = null)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
844
{
845
	if (empty($log_message))
846
		$log_message = $error_message;
847
848
	foreach (debug_backtrace() as $step)
849
	{
850
		// Found it?
851
		if (strpos($step['function'], 'query') === false && !in_array(substr($step['function'], 0, 7), array('smf_db_', 'preg_re', 'db_erro', 'call_us')) && strpos($step['function'], '__') !== 0)
852
		{
853
			$log_message .= '<br>Function: ' . $step['function'];
854
			break;
855
		}
856
857
		if (isset($step['line']))
858
		{
859
			$file = $step['file'];
860
			$line = $step['line'];
861
		}
862
	}
863
864
	// A special case - we want the file and line numbers for debugging.
865
	if ($error_type == 'return')
866
		return array($file, $line);
867
868
	// Is always a critical error.
869
	if (function_exists('log_error'))
870
		log_error($log_message, 'critical', $file, $line);
871
872
	if (function_exists('fatal_error'))
873
	{
874
		fatal_error($error_message, false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
875
876
		// Cannot continue...
877
		exit;
878
	}
879
	elseif ($error_type)
880
		trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''), $error_type);
881
	else
882
		trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''));
883
}
884
885
/**
886
 * Escape the LIKE wildcards so that they match the character and not the wildcard.
887
 *
888
 * @param string $string The string to escape
889
 * @param bool $translate_human_wildcards If true, turns human readable wildcards into SQL wildcards.
890
 * @return string The escaped string
891
 */
892 View Code Duplication
function smf_db_escape_wildcard_string($string, $translate_human_wildcards = false)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
893
{
894
	$replacements = array(
895
		'%' => '\%',
896
		'_' => '\_',
897
		'\\' => '\\\\',
898
	);
899
900
	if ($translate_human_wildcards)
901
		$replacements += array(
902
			'*' => '%',
903
		);
904
905
	return strtr($string, $replacements);
906
}
907
908
/**
909
 * Validates whether the resource is a valid mysqli instance.
910
 * Mysqli uses objects rather than resource. https://bugs.php.net/bug.php?id=42797
911
 *
912
 * @param mixed $result The string to test
913
 * @return bool True if it is, false otherwise
914
 */
915
function smf_is_resource($result)
916
{
917
	if ($result instanceof mysqli_result)
918
		return true;
919
920
	return false;
921
}
922
923
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...