Completed
Pull Request — release-2.1 (#5021)
by Mathias
06:05
created

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

Complexity

Conditions 2
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 1
nop 2
dl 0
loc 6
rs 10
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 2018 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
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;
34
35
	// Map some database specific functions, only do this once.
36 View Code Duplication
	if (!isset($smcFunc['db_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'          => 'smf_db_escape_string',
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
			'db_mb4'                    => false,
61
			'db_ping'                   => 'mysqli_ping',
62
			'db_fetch_all'              => 'smf_db_fetch_all',
63
			'db_error_insert'			=> 'smf_db_error_insert',
64
			'db_custom_order'			=> 'smf_db_custom_order',
65
			'db_native_replace'			=> 'smf_db_native_replace',
66
			'db_cte_support'			=> 'smf_db_cte_support',
67
		);
68
69
	if (!empty($db_options['persist']))
70
		$db_server = 'p:' . $db_server;
71
72
	// We are not going to make it very far without these.
73
	if (!function_exists('mysqli_init') || !function_exists('mysqli_real_connect'))
74
		display_db_error();
75
76
	$connection = mysqli_init();
77
78
	$flags = 2; //MYSQLI_CLIENT_FOUND_ROWS = 2
79
80
	$success = false;
81
82
	if ($connection)
83
	{
84
		if (!empty($db_options['port']))
85
			$success = mysqli_real_connect($connection, $db_server, $db_user, $db_passwd, null, $db_options['port'], null, $flags);
86
		else
87
			$success = mysqli_real_connect($connection, $db_server, $db_user, $db_passwd, null, 0, null, $flags);
88
	}
89
90
	// Something's wrong, show an error if its fatal (which we assume it is)
91
	if ($success === false)
92
	{
93
		if (!empty($db_options['non_fatal']))
94
			return null;
95
		else
96
			display_db_error();
97
	}
98
99
	// Select the database, unless told not to
100
	if (empty($db_options['dont_select_db']) && !@mysqli_select_db($connection, $db_name) && empty($db_options['non_fatal']))
101
		display_db_error();
102
103
	mysqli_query($connection, 'SET SESSION sql_mode = \'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION\'');
104
105
	if (!empty($db_options['db_mb4']))
106
		$smcFunc['db_mb4'] = (bool) $db_options['db_mb4'];
107
108
	return $connection;
109
}
110
111
/**
112
 * Extend the database functionality. It calls the respective file's init
113
 * to add the implementations in that file to $smcFunc array.
114
 *
115
 * @param string $type Indicates which additional file to load. ('extra', 'packages')
116
 */
117 View Code Duplication
function db_extend($type = 'extra')
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...
118
{
119
	global $sourcedir;
120
121
	// we force the MySQL files as nothing syntactically changes with MySQLi
122
	require_once($sourcedir . '/Db' . strtoupper($type[0]) . substr($type, 1) . '-mysql.php');
123
	$initFunc = 'db_' . $type . '_init';
124
	$initFunc();
125
}
126
127
/**
128
 * Fix up the prefix so it doesn't require the database to be selected.
129
 *
130
 * @param string &$db_prefix The table prefix
131
 * @param string $db_name The database name
132
 */
133
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...
134
{
135
	$db_prefix = is_numeric(substr($db_prefix, 0, 1)) ? $db_name . '.' . $db_prefix : '`' . $db_name . '`.' . $db_prefix;
136
}
137
138
/**
139
 * Wrap mysqli_select_db so the connection does not need to be specified
140
 *
141
 * @param string &$database The database
142
 * @param object $connection The connection object (if null, $db_connection is used)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $connection not be object|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
143
 * @return bool Whether the database was selected
144
 */
145
function smf_db_select($database, $connection = null)
146
{
147
	global $db_connection;
148
	return mysqli_select_db($connection === null ? $db_connection : $connection, $database);
149
}
150
151
/**
152
 * Wrap mysqli_get_server_info so the connection does not need to be specified
153
 *
154
 * @param object $connection The connection to use (if null, $db_connection is used)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $connection not be object|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
155
 * @return string The server info
156
 */
157
function smf_db_get_server_info($connection = null)
158
{
159
	global $db_connection;
160
	return mysqli_get_server_info($connection === null ? $db_connection : $connection);
161
}
162
163
/**
164
 * Callback for preg_replace_callback on the query.
165
 * It allows to replace on the fly a few pre-defined strings, for convenience ('query_see_board', 'query_wanna_see_board', etc), with
166
 * their current values from $user_info.
167
 * In addition, it performs checks and sanitization on the values sent to the database.
168
 *
169
 * @param array $matches The matches from preg_replace_callback
170
 * @return string The appropriate string depending on $matches[1]
171
 */
172
function smf_db_replacement__callback($matches)
173
{
174
	global $db_callback, $user_info, $db_prefix, $smcFunc;
175
176
	list ($values, $connection) = $db_callback;
177
	if (!is_object($connection))
178
		display_db_error();
179
180
	if ($matches[1] === 'db_prefix')
181
		return $db_prefix;
182
183 View Code Duplication
	if (isset($user_info[$matches[1]]) && strpos($matches[1], 'query_') !== false)
184
		return $user_info[$matches[1]];
185
186
	if ($matches[1] === 'empty')
187
		return '\'\'';
188
189
	if (!isset($matches[2]))
190
		smf_db_error_backtrace('Invalid value inserted or no type specified.', '', E_USER_ERROR, __FILE__, __LINE__);
191
192
	if ($matches[1] === 'literal')
193
		return '\'' . mysqli_real_escape_string($connection, $matches[2]) . '\'';
194
195 View Code Duplication
	if (!isset($values[$matches[2]]))
196
		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__);
197
198
	$replacement = $values[$matches[2]];
199
200
	switch ($matches[1])
201
	{
202 View Code Duplication
		case 'int':
203
			if (!is_numeric($replacement) || (string) $replacement !== (string) (int) $replacement)
204
				smf_db_error_backtrace('Wrong value type sent to the database. Integer expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
205
			return (string) (int) $replacement;
206
		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...
207
208
		case 'string':
209
		case 'text':
210
			return sprintf('\'%1$s\'', mysqli_real_escape_string($connection, $replacement));
211
		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...
212
213 View Code Duplication
		case 'array_int':
214
			if (is_array($replacement))
215
			{
216
				if (empty($replacement))
217
					smf_db_error_backtrace('Database error, given array of integer values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
218
219
				foreach ($replacement as $key => $value)
220
				{
221
					if (!is_numeric($value) || (string) $value !== (string) (int) $value)
222
						smf_db_error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
223
224
					$replacement[$key] = (string) (int) $value;
225
				}
226
227
				return implode(', ', $replacement);
228
			}
229
			else
230
				smf_db_error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
231
232
		break;
233
234 View Code Duplication
		case 'array_string':
235
			if (is_array($replacement))
236
			{
237
				if (empty($replacement))
238
					smf_db_error_backtrace('Database error, given array of string values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
239
240
				foreach ($replacement as $key => $value)
241
					$replacement[$key] = sprintf('\'%1$s\'', mysqli_real_escape_string($connection, $value));
242
243
				return implode(', ', $replacement);
244
			}
245
			else
246
				smf_db_error_backtrace('Wrong value type sent to the database. Array of strings expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
247
		break;
248
249 View Code Duplication
		case 'date':
250
			if (preg_match('~^(\d{4})-([0-1]?\d)-([0-3]?\d)$~', $replacement, $date_matches) === 1)
251
				return sprintf('\'%04d-%02d-%02d\'', $date_matches[1], $date_matches[2], $date_matches[3]);
252
			else
253
				smf_db_error_backtrace('Wrong value type sent to the database. Date expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
254
		break;
255
256 View Code Duplication
		case 'time':
257
			if (preg_match('~^([0-1]?\d|2[0-3]):([0-5]\d):([0-5]\d)$~', $replacement, $time_matches) === 1)
258
				return sprintf('\'%02d:%02d:%02d\'', $time_matches[1], $time_matches[2], $time_matches[3]);
259
			else
260
				smf_db_error_backtrace('Wrong value type sent to the database. Time expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
261
		break;
262
263 View Code Duplication
		case 'datetime':
264
			if (preg_match('~^(\d{4})-([0-1]?\d)-([0-3]?\d) ([0-1]?\d|2[0-3]):([0-5]\d):([0-5]\d)$~', $replacement, $datetime_matches) === 1)
265
				return 'str_to_date('.
266
					sprintf('\'%04d-%02d-%02d %02d:%02d:%02d\'', $datetime_matches[1], $datetime_matches[2], $datetime_matches[3], $datetime_matches[4], $datetime_matches[5] ,$datetime_matches[6]).
267
					',\'%Y-%m-%d %h:%i:%s\')';
268
			else
269
				smf_db_error_backtrace('Wrong value type sent to the database. Datetime expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
270
		break;
271
272 View Code Duplication
		case 'float':
273
			if (!is_numeric($replacement))
274
				smf_db_error_backtrace('Wrong value type sent to the database. Floating point number expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
275
			return (string) (float) $replacement;
276
		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...
277
278 View Code Duplication
		case 'identifier':
279
			// Backticks inside identifiers are supported as of MySQL 4.1. We don't need them for SMF.
280
			return '`' . strtr($replacement, array('`' => '', '.' => '`.`')) . '`';
281
		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...
282
283
		case 'raw':
284
			return $replacement;
285
		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...
286
287 View Code Duplication
		case 'inet':
288
			if ($replacement == 'null' || $replacement == '')
289
				return 'null';
290
			if (!isValidIP($replacement))
291
				smf_db_error_backtrace('Wrong value type sent to the database. IPv4 or IPv6 expected.(' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
292
			//we don't use the native support of mysql > 5.6.2
293
			return sprintf('unhex(\'%1$s\')', bin2hex(inet_pton($replacement)));
294
295 View Code Duplication
		case 'array_inet':
296
			if (is_array($replacement))
297
			{
298
				if (empty($replacement))
299
					smf_db_error_backtrace('Database error, given array of IPv4 or IPv6 values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
300
301
				foreach ($replacement as $key => $value)
302
				{
303
					if ($replacement == 'null' || $replacement == '')
304
						$replacement[$key] = 'null';
305
					if (!isValidIP($value))
306
						smf_db_error_backtrace('Wrong value type sent to the database. IPv4 or IPv6 expected.(' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
307
					$replacement[$key] = sprintf('unhex(\'%1$s\')', bin2hex(inet_pton($value)));
308
				}
309
310
				return implode(', ', $replacement);
311
			}
312
			else
313
				smf_db_error_backtrace('Wrong value type sent to the database. Array of IPv4 or IPv6 expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
314
		break;
315
316 View Code Duplication
		default:
317
			smf_db_error_backtrace('Undefined type used in the database query. (' . $matches[1] . ':' . $matches[2] . ')', '', false, __FILE__, __LINE__);
318
		break;
319
	}
320
}
321
322
/**
323
 * Just like the db_query, escape and quote a string, but not executing the query.
324
 *
325
 * @param string $db_string The database string
326
 * @param array $db_values An array of values to be injected into the string
327
 * @param resource $connection = null The connection to use (null to use $db_connection)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $connection not be resource|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
328
 * @return string The string with the values inserted
329
 */
330 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...
331
{
332
	global $db_callback, $db_connection;
333
334
	// Only bother if there's something to replace.
335
	if (strpos($db_string, '{') !== false)
336
	{
337
		// This is needed by the callback function.
338
		$db_callback = array($db_values, $connection === null ? $db_connection : $connection);
339
340
		// Do the quoting and escaping
341
		$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string);
342
343
		// Clear this global variable.
344
		$db_callback = array();
345
	}
346
347
	return $db_string;
348
}
349
350
/**
351
 * Do a query.  Takes care of errors too.
352
 *
353
 * @param string $identifier An identifier. Only used in Postgres when we need to do things differently...
354
 * @param string $db_string The database string
355
 * @param array $db_values = array() The values to be inserted into the string
356
 * @param resource $connection = null The connection to use (null to use $db_connection)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $connection not be resource|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
357
 * @return resource|bool Returns a MySQL result resource (for SELECT queries), true (for UPDATE queries) or false if the query failed
358
 */
359
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...
360
{
361
	global $db_cache, $db_count, $db_connection, $db_show_debug, $time_start;
362
	global $db_unbuffered, $db_callback, $modSettings;
363
364
	// Comments that are allowed in a query are preg_removed.
365
	static $allowed_comments_from = array(
366
		'~\s+~s',
367
		'~/\*!40001 SQL_NO_CACHE \*/~',
368
		'~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~',
369
		'~/\*!40100 ON DUPLICATE KEY UPDATE id_msg = \d+ \*/~',
370
	);
371
	static $allowed_comments_to = array(
372
		' ',
373
		'',
374
		'',
375
		'',
376
	);
377
378
	// Decide which connection to use.
379
	$connection = $connection === null ? $db_connection : $connection;
380
381
	// One more query....
382
	$db_count = !isset($db_count) ? 1 : $db_count + 1;
383
384 View Code Duplication
	if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override']))
385
		smf_db_error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__);
386
387
	// Use "ORDER BY null" to prevent Mysql doing filesorts for Group By clauses without an Order By
388
	if (strpos($db_string, 'GROUP BY') !== false && strpos($db_string, 'ORDER BY') === false && preg_match('~^\s+SELECT~i', $db_string))
389
	{
390
		// Add before LIMIT
391
		if ($pos = strpos($db_string, 'LIMIT '))
392
			$db_string = substr($db_string, 0, $pos) . "\t\t\tORDER BY null\n" . substr($db_string, $pos, strlen($db_string));
393
		else
394
			// Append it.
395
			$db_string .= "\n\t\t\tORDER BY null";
396
	}
397
398 View Code Duplication
	if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false))
399
	{
400
		// Pass some values to the global space for use in the callback function.
401
		$db_callback = array($db_values, $connection);
402
403
		// Inject the values passed to this function.
404
		$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string);
405
406
		// This shouldn't be residing in global space any longer.
407
		$db_callback = array();
408
	}
409
410
	// First, we clean strings out of the query, reduce whitespace, lowercase, and trim - so we can check it over.
411 View Code Duplication
	if (empty($modSettings['disableQueryCheck']))
412
	{
413
		$clean = '';
414
		$old_pos = 0;
415
		$pos = -1;
416
		// Remove the string escape for better runtime
417
		$db_string_1 = str_replace('\\\'','',$db_string);
418
		while (true)
419
		{
420
			$pos = strpos($db_string_1, '\'', $pos + 1);
421
			if ($pos === false)
422
				break;
423
			$clean .= substr($db_string_1, $old_pos, $pos - $old_pos);
424
425
			while (true)
426
			{
427
				$pos1 = strpos($db_string_1, '\'', $pos + 1);
428
				$pos2 = strpos($db_string_1, '\\', $pos + 1);
429
				if ($pos1 === false)
430
					break;
431
				elseif ($pos2 === false || $pos2 > $pos1)
432
				{
433
					$pos = $pos1;
434
					break;
435
				}
436
437
				$pos = $pos2 + 1;
438
			}
439
			$clean .= ' %s ';
440
441
			$old_pos = $pos + 1;
442
		}
443
		$clean .= substr($db_string_1, $old_pos);
444
		$clean = trim(strtolower(preg_replace($allowed_comments_from, $allowed_comments_to, $clean)));
445
446
		// Comments?  We don't use comments in our queries, we leave 'em outside!
447
		if (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false)
448
			$fail = true;
449
		// Trying to change passwords, slow us down, or something?
450
		elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[_a-z])~s', $clean) != 0)
451
			$fail = true;
452
		elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
453
			$fail = true;
454
455
		if (!empty($fail) && function_exists('log_error'))
456
			smf_db_error_backtrace('Hacking attempt...', 'Hacking attempt...' . "\n" . $db_string, E_USER_ERROR, __FILE__, __LINE__);
457
	}
458
459
	// Debugging.
460 View Code Duplication
	if (isset($db_show_debug) && $db_show_debug === true)
461
	{
462
		// Get the file and line number this function was called.
463
		list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__);
464
465
		// Initialize $db_cache if not already initialized.
466
		if (!isset($db_cache))
467
			$db_cache = array();
468
469
		if (!empty($_SESSION['debug_redirect']))
470
		{
471
			$db_cache = array_merge($_SESSION['debug_redirect'], $db_cache);
472
			$db_count = count($db_cache) + 1;
473
			$_SESSION['debug_redirect'] = array();
474
		}
475
476
		// Don't overload it.
477
		$db_cache[$db_count]['q'] = $db_count < 50 ? $db_string : '...';
478
		$db_cache[$db_count]['f'] = $file;
479
		$db_cache[$db_count]['l'] = $line;
480
		$db_cache[$db_count]['s'] = ($st = microtime(true)) - $time_start;
481
	}
482
483
	if (empty($db_unbuffered))
484
		$ret = @mysqli_query($connection, $db_string);
485
	else
486
		$ret = @mysqli_query($connection, $db_string, MYSQLI_USE_RESULT);
487
488 View Code Duplication
	if ($ret === false && empty($db_values['db_error_skip']))
489
		$ret = smf_db_error($db_string, $connection);
490
491
	// Debugging.
492 View Code Duplication
	if (isset($db_show_debug) && $db_show_debug === true)
493
		$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...
494
495
	return $ret;
496
}
497
498
/**
499
 * affected_rows
500
 * @param resource $connection A connection to use (if null, $db_connection is used)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $connection not be resource|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
501
 * @return int The number of rows affected by the last query
502
 */
503
function smf_db_affected_rows($connection = null)
504
{
505
	global $db_connection;
506
507
	return mysqli_affected_rows($connection === null ? $db_connection : $connection);
508
}
509
510
/**
511
 * Gets the ID of the most recently inserted row.
512
 *
513
 * @param string $table The table (only used for Postgres)
514
 * @param string $field = null The specific field (not used here)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $field not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
515
 * @param resource $connection = null The connection (if null, $db_connection is used)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $connection not be resource|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
516
 * @return int The ID of the most recently inserted row
517
 */
518
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...
519
{
520
	global $db_connection;
521
522
	// MySQL doesn't need the table or field information.
523
	return mysqli_insert_id($connection === null ? $db_connection : $connection);
524
}
525
526
/**
527
 * Do a transaction.
528
 *
529
 * @param string $type The step to perform (i.e. 'begin', 'commit', 'rollback')
530
 * @param resource $connection The connection to use (if null, $db_connection is used)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $connection not be resource|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
531
 * @return bool True if successful, false otherwise
532
 */
533 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...
534
{
535
	global $db_connection;
536
537
	// Decide which connection to use
538
	$connection = $connection === null ? $db_connection : $connection;
539
540
	if ($type == 'begin')
541
		return @mysqli_query($connection, 'BEGIN');
542
	elseif ($type == 'rollback')
543
		return @mysqli_query($connection, 'ROLLBACK');
544
	elseif ($type == 'commit')
545
		return @mysqli_query($connection, 'COMMIT');
546
547
	return false;
548
}
549
550
/**
551
 * Database error!
552
 * Backtrace, log, try to fix.
553
 *
554
 * @param string $db_string The DB string
555
 * @param object $connection The connection to use (if null, $db_connection is used)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $connection not be object|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
556
 */
557
function smf_db_error($db_string, $connection = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
558
{
559
	global $txt, $context, $sourcedir, $webmaster_email, $modSettings;
560
	global $db_connection, $db_last_error, $db_persist;
561
	global $db_server, $db_user, $db_passwd, $db_name, $db_show_debug, $ssi_db_user, $ssi_db_passwd;
562
	global $smcFunc;
563
564
	// Get the file and line numbers.
565
	list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__);
566
567
	// Decide which connection to use
568
	$connection = $connection === null ? $db_connection : $connection;
569
570
	// This is the error message...
571
	$query_error = mysqli_error($connection);
572
	$query_errno = mysqli_errno($connection);
573
574
	// Error numbers:
575
	//    1016: Can't open file '....MYI'
576
	//    1030: Got error ??? from table handler.
577
	//    1034: Incorrect key file for table.
578
	//    1035: Old key file for table.
579
	//    1205: Lock wait timeout exceeded.
580
	//    1213: Deadlock found.
581
582
	// Log the error.
583
	if ($query_errno != 1213 && $query_errno != 1205 && function_exists('log_error'))
584
		log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n$db_string" : ''), 'database', $file, $line);
585
586
	// Database error auto fixing ;).
587
	if (function_exists('cache_get_data') && (!isset($modSettings['autoFixDatabase']) || $modSettings['autoFixDatabase'] == '1'))
588
	{
589
		// Force caching on, just for the error checking.
590
		$old_cache = @$modSettings['cache_enable'];
591
		$modSettings['cache_enable'] = '1';
592
593 View Code Duplication
		if (($temp = cache_get_data('db_last_error', 600)) !== null)
594
			$db_last_error = max(@$db_last_error, $temp);
595
596
		if (@$db_last_error < time() - 3600 * 24 * 3)
597
		{
598
			// We know there's a problem... but what?  Try to auto detect.
599
			if ($query_errno == 1030 && strpos($query_error, ' 127 ') !== false)
600
			{
601
				preg_match_all('~(?:[\n\r]|^)[^\']+?(?:FROM|JOIN|UPDATE|TABLE) ((?:[^\n\r(]+?(?:, )?)*)~s', $db_string, $matches);
602
603
				$fix_tables = array();
604
				foreach ($matches[1] as $tables)
605
				{
606
					$tables = array_unique(explode(',', $tables));
607
					foreach ($tables as $table)
608
					{
609
						// Now, it's still theoretically possible this could be an injection.  So backtick it!
610
						if (trim($table) != '')
611
							$fix_tables[] = '`' . strtr(trim($table), array('`' => '')) . '`';
612
					}
613
				}
614
615
				$fix_tables = array_unique($fix_tables);
616
			}
617
			// Table crashed.  Let's try to fix it.
618 View Code Duplication
			elseif ($query_errno == 1016)
619
			{
620
				if (preg_match('~\'([^\.\']+)~', $query_error, $match) != 0)
621
					$fix_tables = array('`' . $match[1] . '`');
622
			}
623
			// Indexes crashed.  Should be easy to fix!
624 View Code Duplication
			elseif ($query_errno == 1034 || $query_errno == 1035)
625
			{
626
				preg_match('~\'([^\']+?)\'~', $query_error, $match);
627
				$fix_tables = array('`' . $match[1] . '`');
628
			}
629
		}
630
631
		// 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...)
632
		if (!empty($fix_tables))
633
		{
634
			// Subs-Admin.php for updateSettingsFile(), Subs-Post.php for sendmail().
635
			require_once($sourcedir . '/Subs-Admin.php');
636
			require_once($sourcedir . '/Subs-Post.php');
637
638
			// Make a note of the REPAIR...
639
			cache_put_data('db_last_error', time(), 600);
640
			if (($temp = cache_get_data('db_last_error', 600)) === null)
641
				updateSettingsFile(array('db_last_error' => time()));
642
643
			// Attempt to find and repair the broken table.
644
			foreach ($fix_tables as $table)
645
				$smcFunc['db_query']('', "
646
					REPAIR TABLE $table", false, false);
647
648
			// And send off an email!
649
			sendmail($webmaster_email, $txt['database_error'], $txt['tried_to_repair'], null, 'dberror');
650
651
			$modSettings['cache_enable'] = $old_cache;
652
653
			// Try the query again...?
654
			$ret = $smcFunc['db_query']('', $db_string, false, false);
655
			if ($ret !== false)
656
				return $ret;
657
		}
658
		else
659
			$modSettings['cache_enable'] = $old_cache;
660
661
		// Check for the "lost connection" or "deadlock found" errors - and try it just one more time.
662
		if (in_array($query_errno, array(1205, 1213)))
663
		{
664
			if ($db_connection)
665
			{
666
				// Try a deadlock more than once more.
667
				for ($n = 0; $n < 4; $n++)
668
				{
669
					$ret = $smcFunc['db_query']('', $db_string, false, false);
670
671
					$new_errno = mysqli_errno($db_connection);
672
					if ($ret !== false || in_array($new_errno, array(1205, 1213)))
673
						break;
674
				}
675
676
				// If it failed again, shucks to be you... we're not trying it over and over.
677
				if ($ret !== false)
678
					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...
679
			}
680
		}
681
		// Are they out of space, perhaps?
682
		elseif ($query_errno == 1030 && (strpos($query_error, ' -1 ') !== false || strpos($query_error, ' 28 ') !== false || strpos($query_error, ' 12 ') !== false))
683
		{
684
			if (!isset($txt))
685
				$query_error .= ' - check database storage space.';
686
			else
687
			{
688
				if (!isset($txt['mysql_error_space']))
689
					loadLanguage('Errors');
690
691
				$query_error .= !isset($txt['mysql_error_space']) ? ' - check database storage space.' : $txt['mysql_error_space'];
692
			}
693
		}
694
	}
695
696
	// Nothing's defined yet... just die with it.
697
	if (empty($context) || empty($txt))
698
		die($query_error);
699
700
	// Show an error message, if possible.
701
	$context['error_title'] = $txt['database_error'];
702 View Code Duplication
	if (allowedTo('admin_forum'))
703
		$context['error_message'] = nl2br($query_error) . '<br>' . $txt['file'] . ': ' . $file . '<br>' . $txt['line'] . ': ' . $line;
704
	else
705
		$context['error_message'] = $txt['try_again'];
706
707 View Code Duplication
	if (allowedTo('admin_forum') && isset($db_show_debug) && $db_show_debug === true)
708
	{
709
		$context['error_message'] .= '<br><br>' . nl2br($db_string);
710
	}
711
712
	// It's already been logged... don't log it again.
713
	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...
714
}
715
716
/**
717
 * Inserts data into a table
718
 *
719
 * @param string $method The insert method - can be 'replace', 'ignore' or 'insert'
720
 * @param string $table The table we're inserting the data into
721
 * @param array $columns An array of the columns we're inserting the data into. Should contain 'column' => 'datatype' pairs
722
 * @param array $data The data to insert
723
 * @param array $keys The keys for the table
724
 * @param int returnmode 0 = nothing(default), 1 = last row id, 2 = all rows id as array
725
 * @param object $connection The connection to use (if null, $db_connection is used)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $connection not be object|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
726
 * @return mixed value of the first key, behavior based on returnmode. null if no data.
727
 */
728
function smf_db_insert($method = 'replace', $table, $columns, $data, $keys, $returnmode = 0, $connection = null)
729
{
730
	global $smcFunc, $db_connection, $db_prefix;
731
732
	$connection = $connection === null ? $db_connection : $connection;
733
734
	$return_var = null;
735
736
	// With nothing to insert, simply return.
737
	if (empty($data))
738
		return;
739
740
	// Replace the prefix holder with the actual prefix.
741
	$table = str_replace('{db_prefix}', $db_prefix, $table);
742
743
	$with_returning = false;
744
745 View Code Duplication
	if (!empty($keys) && (count($keys) > 0) && $returnmode > 0)
746
	{
747
		$with_returning = true;
748
		if ($returnmode == 2)
749
			$return_var = array();
750
	}
751
752
	// Inserting data as a single row can be done as a single array.
753
	if (!is_array($data[array_rand($data)]))
754
		$data = array($data);
755
756
	// Create the mold for a single row insert.
757
	$insertData = '(';
758 View Code Duplication
	foreach ($columns as $columnName => $type)
759
	{
760
		// Are we restricting the length?
761
		if (strpos($type, 'string-') !== false)
762
			$insertData .= sprintf('SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName);
763
		else
764
			$insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName);
765
	}
766
	$insertData = substr($insertData, 0, -2) . ')';
767
768
	// Create an array consisting of only the columns.
769
	$indexed_columns = array_keys($columns);
770
771
	// Here's where the variables are injected to the query.
772
	$insertRows = array();
773
	foreach ($data as $dataRow)
774
		$insertRows[] = smf_db_quote($insertData, array_combine($indexed_columns, $dataRow), $connection);
775
776
	// Determine the method of insertion.
777
	$queryTitle = $method == 'replace' ? 'REPLACE' : ($method == 'ignore' ? 'INSERT IGNORE' : 'INSERT');
778
779
	if (!$with_returning || $method != 'ingore')
780
	{
781
		// Do the insert.
782
		$smcFunc['db_query']('', '
783
			' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`)
784
			VALUES
785
				' . implode(',
786
				', $insertRows),
787
			array(
788
				'security_override' => true,
789
				'db_error_skip' => $table === $db_prefix . 'log_errors',
790
			),
791
			$connection
792
		);
793
	}
794
	else //special way for ignore method with returning
795
	{
796
		$count = count($insertRows);
797
		$ai = 0;
798
		for($i = 0; $i < $count; $i++)
799
		{
800
			$old_id = $smcFunc['db_insert_id']();
0 ignored issues
show
Unused Code introduced by
$old_id is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
801
802
			$smcFunc['db_query']('', '
803
				' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`)
804
				VALUES
805
					' . $insertRows[$i],
806
				array(
807
					'security_override' => true,
808
					'db_error_skip' => $table === $db_prefix . 'log_errors',
809
				),
810
				$connection
811
			);
812
			$new_id = $smcFunc['db_insert_id']();
813
814
			if ($last_id != $new_id) //the inserted value was new
0 ignored issues
show
Bug introduced by
The variable $last_id does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
815
			{
816
				$ai = $new_id;
817
			}
818
			else	// the inserted value already exists we need to find the pk
819
			{
820
				$where_string = '';
821
				$count2 = count($indexed_columns);
822
				for ($x = 0; $x < $count2; $x++)
823
				{
824
					$where_string += key($indexed_columns[$x]) . ' = '. $insertRows[$i][$x];
825
					if (($x + 1) < $count2)
826
						$where_string += ' AND ';
827
				}
828
829
				$request = $smcFunc['db_query']('','
830
					SELECT `'. $keys[0] . '` FROM ' . $table .'
831
					WHERE ' . $where_string . ' LIMIT 1',
832
					array()
833
				);
834
835
				if ($request !== false && $smcFunc['db_num_rows']($request) == 1)
836
				{
837
					$row = $smcFunc['db_fetch_assoc']($request);
838
					$ai = $row[$keys[0]];
839
				}
840
			}
841
842
			if ($returnmode == 1)
843
				$return_var = $ai;
844
			else if ($returnmode == 2)
845
				$return_var[] = $ai;
846
		}
847
	}
848
849
850
	if ($with_returning)
851
	{
852
		if ($returnmode == 1 && empty($return_var))
853
			$return_var = smf_db_insert_id($table, $keys[0]) + count($insertRows) - 1;
854
		else if ($returnmode == 2 && empty($return_var))
855
		{
856
			$return_var = array();
857
			$count = count($insertRows);
858
			$start = smf_db_insert_id($table, $keys[0]);
859
			for ($i = 0; $i < $count; $i++ )
860
				$return_var[] = $start + $i;
861
		}
862
		return $return_var;
863
	}
864
}
865
866
/**
867
 * This function tries to work out additional error information from a back trace.
868
 *
869
 * @param string $error_message The error message
870
 * @param string $log_message The message to log
871
 * @param string|bool $error_type What type of error this is
872
 * @param string $file The file the error occurred in
0 ignored issues
show
Documentation introduced by
Should the type for parameter $file not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
873
 * @param int $line What line of $file the code which generated the error is on
0 ignored issues
show
Documentation introduced by
Should the type for parameter $line not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
874
 * @return void|array Returns an array with the file and line if $error_type is 'return'
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array|null.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
875
 */
876 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...
877
{
878
	if (empty($log_message))
879
		$log_message = $error_message;
880
881
	foreach (debug_backtrace() as $step)
882
	{
883
		// Found it?
884
		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)
885
		{
886
			$log_message .= '<br>Function: ' . $step['function'];
887
			break;
888
		}
889
890
		if (isset($step['line']))
891
		{
892
			$file = $step['file'];
893
			$line = $step['line'];
894
		}
895
	}
896
897
	// A special case - we want the file and line numbers for debugging.
898
	if ($error_type == 'return')
899
		return array($file, $line);
900
901
	// Is always a critical error.
902
	if (function_exists('log_error'))
903
		log_error($log_message, 'critical', $file, $line);
904
905
	if (function_exists('fatal_error'))
906
	{
907
		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...
908
909
		// Cannot continue...
910
		exit;
911
	}
912
	elseif ($error_type)
913
		trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''), $error_type);
914
	else
915
		trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''));
916
}
917
918
/**
919
 * Escape the LIKE wildcards so that they match the character and not the wildcard.
920
 *
921
 * @param string $string The string to escape
922
 * @param bool $translate_human_wildcards If true, turns human readable wildcards into SQL wildcards.
923
 * @return string The escaped string
924
 */
925 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...
926
{
927
	$replacements = array(
928
		'%' => '\%',
929
		'_' => '\_',
930
		'\\' => '\\\\',
931
	);
932
933
	if ($translate_human_wildcards)
934
		$replacements += array(
935
			'*' => '%',
936
		);
937
938
	return strtr($string, $replacements);
939
}
940
941
/**
942
 * Validates whether the resource is a valid mysqli instance.
943
 * Mysqli uses objects rather than resource. https://bugs.php.net/bug.php?id=42797
944
 *
945
 * @param mixed $result The string to test
946
 * @return bool True if it is, false otherwise
947
 */
948
function smf_is_resource($result)
949
{
950
	if ($result instanceof mysqli_result)
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return $result instanceof \mysqli_result;.
Loading history...
951
		return true;
952
953
	return false;
954
}
955
956
/**
957
 * Fetches all rows from a result as an array
958
 *
959
 * @param resource $request A MySQL result resource
960
 * @return array An array that contains all rows (records) in the result resource
961
 */
962
function smf_db_fetch_all($request)
963
{
964
	// Return the right row.
965
	return mysqli_fetch_all($request);
966
}
967
968
/**
969
 * Function to save errors in database in a safe way
970
 *
971
 * @param array with keys in this order id_member, log_time, ip, url, message, session, error_type, file, line
972
 * @return void
973
 */
974
function smf_db_error_insert($error_array)
975
{
976
	global  $db_prefix, $db_connection;
977
	static $mysql_error_data_prep;
978
979
	// without database we can't do anything
980
	if (empty($db_connection))
981
		return;
982
983
	if (empty($mysql_error_data_prep))
984
			$mysql_error_data_prep = mysqli_prepare($db_connection,
985
				'INSERT INTO ' . $db_prefix . 'log_errors(id_member, log_time, ip, url, message, session, error_type, file, line, backtrace)
986
													VALUES(		?,		?,		unhex(?), ?, 		?,		?,			?,		?,	?, ?)'
987
			);
988
989
	if (filter_var($error_array[2], FILTER_VALIDATE_IP) !== false)
990
		$error_array[2] = bin2hex(inet_pton($error_array[2]));
991
	else
992
		$error_array[2] = null;
993
	mysqli_stmt_bind_param($mysql_error_data_prep, 'iissssssis',
994
		$error_array[0], $error_array[1], $error_array[2], $error_array[3], $error_array[4], $error_array[5], $error_array[6],
995
		$error_array[7], $error_array[8], $error_array[9]);
996
	mysqli_stmt_execute ($mysql_error_data_prep);
997
}
998
999
/**
1000
 * Function which constructs an optimize custom order string
1001
 * as an improved alternative to find_in_set()
1002
 *
1003
 * @param string $field name
1004
 * @param array $array_values Field values sequenced in array via order priority. Must cast to int.
1005
 * @param boolean $desc default false
1006
 * @return string case field when ... then ... end
1007
 */
1008 View Code Duplication
function smf_db_custom_order($field, $array_values, $desc = 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...
1009
{
1010
	$return = 'CASE '. $field . ' ';
1011
	$count = count($array_values);
1012
	$then = ($desc ? ' THEN -' : ' THEN ');
1013
1014
	for ($i = 0; $i < $count; $i++)
1015
		$return .= 'WHEN ' . (int) $array_values[$i] . $then . $i . ' ';
1016
1017
	$return .= 'END';
1018
	return $return;
1019
}
1020
1021
/**
1022
 * Function which return the information if the database supports native replace inserts
1023
 *
1024
 * @return boolean true or false
1025
 */
1026
function smf_db_native_replace()
1027
{
1028
	return true;
1029
}
1030
1031
/**
1032
 * Function which return the information if the database supports cte with recursive
1033
 *
1034
 * @return boolean true or false
1035
 */
1036
function smf_db_cte_support()
1037
{
1038
	global $smcFunc;
1039
	static $return;
1040
1041
	if (isset($return))
1042
		return $return;
1043
1044
	db_extend('extra');
1045
1046
	$version = $smcFunc['db_get_version']();
1047
1048
	if (strpos(strtolower($version), 'mariadb') !== false)
1049
		$return = version_compare($version, '10.2.2', '>=');
1050
	else //mysql
1051
		$return = version_compare($version, '8.0.1', '>=');
1052
1053
	return $return;
1054
}
1055
1056
/**
1057
 * Function which return the escaped string
1058
 * 
1059
 * @param string the unescaped text
1060
 * @param resource $connection = null The connection to use (null to use $db_connection)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $connection not be resource|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1061
 * @return string escaped string
1062
 */
1063
function smf_db_escape_string($string, $connection = null)
1064
{
1065
	global $db_connection;
1066
1067
	return mysqli_real_escape_string($connection === null ? $db_connection : $connection, $string);
1068
}
1069
1070
?>