Completed
Push — release-2.1 ( e75011...db7a83 )
by Michael
13:29 queued 04:23
created

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

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 1
dl 0
loc 20
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 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;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
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'          => '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
			'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
		);
65
66
	if (!empty($db_options['persist']))
67
		$db_server = 'p:' . $db_server;
68
69
	$connection = mysqli_init();
70
71
	$flags = MYSQLI_CLIENT_FOUND_ROWS;
72
73
	$success = false;
74
75
	if ($connection) {
76
		if (!empty($db_options['port']))
77
			$success = mysqli_real_connect($connection, $db_server, $db_user, $db_passwd, '', $db_options['port'], null, $flags);
78
		else
79
			$success = mysqli_real_connect($connection, $db_server, $db_user, $db_passwd, '', 0, null, $flags);
80
	}
81
82
	// Something's wrong, show an error if its fatal (which we assume it is)
83
	if ($success === false)
84
	{
85
		if (!empty($db_options['non_fatal']))
86
			return null;
87
		else
88
			display_db_error();
89
	}
90
91
	// Select the database, unless told not to
92
	if (empty($db_options['dont_select_db']) && !@mysqli_select_db($connection, $db_name) && empty($db_options['non_fatal']))
93
		display_db_error();
94
95
	$smcFunc['db_query']('', '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\'',
96
		array(),
97
		false
98
	);
99
100
	return $connection;
101
}
102
103
/**
104
 * Extend the database functionality. It calls the respective file's init
105
 * to add the implementations in that file to $smcFunc array.
106
 *
107
 * @param string $type Indicates which additional file to load. ('extra', 'packages')
108
 */
109
function db_extend($type = 'extra')
110
{
111
	global $sourcedir;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
112
113
	// we force the MySQL files as nothing syntactically changes with MySQLi
114
	require_once($sourcedir . '/Db' . strtoupper($type[0]) . substr($type, 1) . '-mysql.php');
115
	$initFunc = 'db_' . $type . '_init';
116
	$initFunc();
117
}
118
119
/**
120
 * Fix up the prefix so it doesn't require the database to be selected.
121
 *
122
 * @param string &$db_prefix The table prefix
123
 * @param string $db_name The database name
124
 */
125
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...
126
{
127
	$db_prefix = is_numeric(substr($db_prefix, 0, 1)) ? $db_name . '.' . $db_prefix : '`' . $db_name . '`.' . $db_prefix;
128
}
129
130
/**
131
 * Wrap mysqli_select_db so the connection does not need to be specified
132
 *
133
 * @param string &$database The database
134
 * @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...
135
 * @return bool Whether the database was selected
136
 */
137
function smf_db_select($database, $connection = null)
138
{
139
	global $db_connection;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
140
	return mysqli_select_db($connection === null ? $db_connection : $connection, $database);
141
}
142
143
/**
144
 * Wrap mysqli_get_server_info so the connection does not need to be specified
145
 *
146
 * @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...
147
 * @return string The server info
148
 */
149
function smf_db_get_server_info($connection = null)
150
{
151
	global $db_connection;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
152
	return mysqli_get_server_info($connection === null ? $db_connection : $connection);
153
}
154
155
/**
156
 * Callback for preg_replace_callback on the query.
157
 * It allows to replace on the fly a few pre-defined strings, for convenience ('query_see_board', 'query_wanna_see_board', etc), with
158
 * their current values from $user_info.
159
 * In addition, it performs checks and sanitization on the values sent to the database.
160
 *
161
 * @param array $matches The matches from preg_replace_callback
162
 * @return string The appropriate string depending on $matches[1]
163
 */
164
function smf_db_replacement__callback($matches)
165
{
166
	global $db_callback, $user_info, $db_prefix, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

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

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
325
326
	// Only bother if there's something to replace.
327
	if (strpos($db_string, '{') !== false)
328
	{
329
		// This is needed by the callback function.
330
		$db_callback = array($db_values, $connection === null ? $db_connection : $connection);
331
332
		// Do the quoting and escaping
333
		$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string);
334
335
		// Clear this global variable.
336
		$db_callback = array();
337
	}
338
339
	return $db_string;
340
}
341
342
/**
343
 * Do a query.  Takes care of errors too.
344
 *
345
 * @param string $identifier An identifier. Only used in Postgres when we need to do things differently...
346
 * @param string $db_string The database string
347
 * @param array $db_values = array() The values to be inserted into the string
348
 * @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...
349
 * @return resource|bool Returns a MySQL result resource (for SELECT queries), true (for UPDATE queries) or false if the query failed
350
 */
351
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...
352
{
353
	global $db_cache, $db_count, $db_connection, $db_show_debug, $time_start;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
354
	global $db_unbuffered, $db_callback, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
355
356
	// Comments that are allowed in a query are preg_removed.
357
	static $allowed_comments_from = array(
358
		'~\s+~s',
359
		'~/\*!40001 SQL_NO_CACHE \*/~',
360
		'~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~',
361
		'~/\*!40100 ON DUPLICATE KEY UPDATE id_msg = \d+ \*/~',
362
	);
363
	static $allowed_comments_to = array(
364
		' ',
365
		'',
366
		'',
367
		'',
368
	);
369
370
	// Decide which connection to use.
371
	$connection = $connection === null ? $db_connection : $connection;
372
373
	// Get a connection if we are shutting down, sometimes the link is closed before sessions are written
374
	if (!is_object($connection))
375
	{
376
		global $db_server, $db_user, $db_passwd, $db_name, $db_show_debug, $ssi_db_user, $ssi_db_passwd;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
377
378
		// Are we in SSI mode?  If so try that username and password first
379
		if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
380
		{
381
			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...
382
				$db_connection = @mysqli_connect($db_server, $ssi_db_user, $ssi_db_passwd);
383
			else
384
				$db_connection = @mysqli_connect('p:' . $db_server, $ssi_db_user, $ssi_db_passwd);
385
		}
386
		// Fall back to the regular username and password if need be
387
		if (!$db_connection)
388
		{
389
			if (empty($db_persist))
390
				$db_connection = @mysqli_connect($db_server, $db_user, $db_passwd);
391
			else
392
				$db_connection = @mysqli_connect('p:' . $db_server, $db_user, $db_passwd);
393
		}
394
395
		if (!$db_connection || !@mysqli_select_db($db_connection, $db_name))
396
			$db_connection = false;
397
398
		$connection = $db_connection;
399
	}
400
401
	// One more query....
402
	$db_count = !isset($db_count) ? 1 : $db_count + 1;
403
404 View Code Duplication
	if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override']))
405
		smf_db_error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__);
406
407
	// Use "ORDER BY null" to prevent Mysql doing filesorts for Group By clauses without an Order By
408
	if (strpos($db_string, 'GROUP BY') !== false && strpos($db_string, 'ORDER BY') === false && preg_match('~^\s+SELECT~i', $db_string))
409
	{
410
		// Add before LIMIT
411
		if ($pos = strpos($db_string, 'LIMIT '))
412
			$db_string = substr($db_string, 0, $pos) . "\t\t\tORDER BY null\n" . substr($db_string, $pos, strlen($db_string));
413
		else
414
			// Append it.
415
			$db_string .= "\n\t\t\tORDER BY null";
416
	}
417
418 View Code Duplication
	if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false))
419
	{
420
		// Pass some values to the global space for use in the callback function.
421
		$db_callback = array($db_values, $connection);
422
423
		// Inject the values passed to this function.
424
		$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string);
425
426
		// This shouldn't be residing in global space any longer.
427
		$db_callback = array();
428
	}
429
430
	// Debugging.
431
	if (isset($db_show_debug) && $db_show_debug === true)
432
	{
433
		// Get the file and line number this function was called.
434
		list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__);
435
436
		// Initialize $db_cache if not already initialized.
437
		if (!isset($db_cache))
438
			$db_cache = array();
439
440
		if (!empty($_SESSION['debug_redirect']))
441
		{
442
			$db_cache = array_merge($_SESSION['debug_redirect'], $db_cache);
443
			$db_count = count($db_cache) + 1;
444
			$_SESSION['debug_redirect'] = array();
445
		}
446
447
		// Don't overload it.
448
		$st = microtime(true);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $st. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
449
		$db_cache[$db_count]['q'] = $db_count < 50 ? $db_string : '...';
450
		$db_cache[$db_count]['f'] = $file;
451
		$db_cache[$db_count]['l'] = $line;
452
		$db_cache[$db_count]['s'] = $st - $time_start;
453
	}
454
455
	// First, we clean strings out of the query, reduce whitespace, lowercase, and trim - so we can check it over.
456 View Code Duplication
	if (empty($modSettings['disableQueryCheck']))
457
	{
458
		$clean = '';
459
		$old_pos = 0;
460
		$pos = -1;
461
		while (true)
462
		{
463
			$pos = strpos($db_string, '\'', $pos + 1);
464
			if ($pos === false)
465
				break;
466
			$clean .= substr($db_string, $old_pos, $pos - $old_pos);
467
468
			while (true)
469
			{
470
				$pos1 = strpos($db_string, '\'', $pos + 1);
471
				$pos2 = strpos($db_string, '\\', $pos + 1);
472
				if ($pos1 === false)
473
					break;
474
				elseif ($pos2 === false || $pos2 > $pos1)
475
				{
476
					$pos = $pos1;
477
					break;
478
				}
479
480
				$pos = $pos2 + 1;
481
			}
482
			$clean .= ' %s ';
483
484
			$old_pos = $pos + 1;
485
		}
486
		$clean .= substr($db_string, $old_pos);
487
		$clean = trim(strtolower(preg_replace($allowed_comments_from, $allowed_comments_to, $clean)));
488
489
		// Comments?  We don't use comments in our queries, we leave 'em outside!
490
		if (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false)
491
			$fail = true;
492
		// Trying to change passwords, slow us down, or something?
493
		elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[_a-z])~s', $clean) != 0)
494
			$fail = true;
495
		elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
496
			$fail = true;
497
498
		if (!empty($fail) && function_exists('log_error'))
499
			smf_db_error_backtrace('Hacking attempt...', 'Hacking attempt...' . "\n" . $db_string, E_USER_ERROR, __FILE__, __LINE__);
500
	}
501
502
	if (empty($db_unbuffered))
503
		$ret = @mysqli_query($connection, $db_string);
504
	else
505
		$ret = @mysqli_query($connection, $db_string, MYSQLI_USE_RESULT);
506
507 View Code Duplication
	if ($ret === false && empty($db_values['db_error_skip']))
508
		$ret = smf_db_error($db_string, $connection);
509
510
	// Debugging.
511 View Code Duplication
	if (isset($db_show_debug) && $db_show_debug === true)
512
		$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...
513
514
	return $ret;
515
}
516
517
/**
518
 * affected_rows
519
 * @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...
520
 * @return int The number of rows affected by the last query
521
 */
522
function smf_db_affected_rows($connection = null)
523
{
524
	global $db_connection;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
525
526
	return mysqli_affected_rows($connection === null ? $db_connection : $connection);
527
}
528
529
/**
530
 * Gets the ID of the most recently inserted row.
531
 *
532
 * @param string $table The table (only used for Postgres)
533
 * @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...
534
 * @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...
535
 * @return int The ID of the most recently inserted row
536
 */
537
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...
538
{
539
	global $db_connection;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
540
541
	// MySQL doesn't need the table or field information.
542
	return mysqli_insert_id($connection === null ? $db_connection : $connection);
543
}
544
545
/**
546
 * Do a transaction.
547
 *
548
 * @param string $type The step to perform (i.e. 'begin', 'commit', 'rollback')
549
 * @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...
550
 * @return bool True if successful, false otherwise
551
 */
552 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...
553
{
554
	global $db_connection;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
555
556
	// Decide which connection to use
557
	$connection = $connection === null ? $db_connection : $connection;
558
559
	if ($type == 'begin')
560
		return @mysqli_query($connection, 'BEGIN');
561
	elseif ($type == 'rollback')
562
		return @mysqli_query($connection, 'ROLLBACK');
563
	elseif ($type == 'commit')
564
		return @mysqli_query($connection, 'COMMIT');
565
566
	return false;
567
}
568
569
/**
570
 * Database error!
571
 * Backtrace, log, try to fix.
572
 *
573
 * @param string $db_string The DB string
574
 * @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...
575
 */
576
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...
577
{
578
	global $txt, $context, $sourcedir, $webmaster_email, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
579
	global $db_connection, $db_last_error, $db_persist;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
580
	global $db_server, $db_user, $db_passwd, $db_name, $db_show_debug, $ssi_db_user, $ssi_db_passwd;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
581
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
582
583
	// Get the file and line numbers.
584
	list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__);
585
586
	// Decide which connection to use
587
	$connection = $connection === null ? $db_connection : $connection;
588
589
	// This is the error message...
590
	$query_error = mysqli_error($connection);
591
	$query_errno = mysqli_errno($connection);
592
593
	// Error numbers:
594
	//    1016: Can't open file '....MYI'
595
	//    1030: Got error ??? from table handler.
596
	//    1034: Incorrect key file for table.
597
	//    1035: Old key file for table.
598
	//    1205: Lock wait timeout exceeded.
599
	//    1213: Deadlock found.
600
	//    2006: Server has gone away.
601
	//    2013: Lost connection to server during query.
602
603
	// Log the error.
604
	if ($query_errno != 1213 && $query_errno != 1205 && function_exists('log_error'))
605
		log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n$db_string" : ''), 'database', $file, $line);
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $db_string instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
606
607
	// Database error auto fixing ;).
608
	if (function_exists('cache_get_data') && (!isset($modSettings['autoFixDatabase']) || $modSettings['autoFixDatabase'] == '1'))
609
	{
610
		// Force caching on, just for the error checking.
611
		$old_cache = @$modSettings['cache_enable'];
612
		$modSettings['cache_enable'] = '1';
613
614
		if (($temp = cache_get_data('db_last_error', 600)) !== null)
615
			$db_last_error = max(@$db_last_error, $temp);
616
617
		if (@$db_last_error < time() - 3600 * 24 * 3)
618
		{
619
			// We know there's a problem... but what?  Try to auto detect.
620
			if ($query_errno == 1030 && strpos($query_error, ' 127 ') !== false)
621
			{
622
				preg_match_all('~(?:[\n\r]|^)[^\']+?(?:FROM|JOIN|UPDATE|TABLE) ((?:[^\n\r(]+?(?:, )?)*)~s', $db_string, $matches);
623
624
				$fix_tables = array();
625
				foreach ($matches[1] as $tables)
626
				{
627
					$tables = array_unique(explode(',', $tables));
628
					foreach ($tables as $table)
629
					{
630
						// Now, it's still theoretically possible this could be an injection.  So backtick it!
631
						if (trim($table) != '')
632
							$fix_tables[] = '`' . strtr(trim($table), array('`' => '')) . '`';
633
					}
634
				}
635
636
				$fix_tables = array_unique($fix_tables);
637
			}
638
			// Table crashed.  Let's try to fix it.
639
			elseif ($query_errno == 1016)
640
			{
641
				if (preg_match('~\'([^\.\']+)~', $query_error, $match) != 0)
642
					$fix_tables = array('`' . $match[1] . '`');
643
			}
644
			// Indexes crashed.  Should be easy to fix!
645
			elseif ($query_errno == 1034 || $query_errno == 1035)
646
			{
647
				preg_match('~\'([^\']+?)\'~', $query_error, $match);
648
				$fix_tables = array('`' . $match[1] . '`');
649
			}
650
		}
651
652
		// 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...)
653
		if (!empty($fix_tables))
654
		{
655
			// Subs-Admin.php for updateSettingsFile(), Subs-Post.php for sendmail().
656
			require_once($sourcedir . '/Subs-Admin.php');
657
			require_once($sourcedir . '/Subs-Post.php');
658
659
			// Make a note of the REPAIR...
660
			cache_put_data('db_last_error', time(), 600);
661
			if (($temp = cache_get_data('db_last_error', 600)) === null)
662
				updateSettingsFile(array('db_last_error' => time()));
663
664
			// Attempt to find and repair the broken table.
665
			foreach ($fix_tables as $table)
666
				$smcFunc['db_query']('', "
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $table instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
667
					REPAIR TABLE $table", false, false);
668
669
			// And send off an email!
670
			sendmail($webmaster_email, $txt['database_error'], $txt['tried_to_repair'], null, 'dberror');
671
672
			$modSettings['cache_enable'] = $old_cache;
673
674
			// Try the query again...?
675
			$ret = $smcFunc['db_query']('', $db_string, false, false);
676
			if ($ret !== false)
677
				return $ret;
678
		}
679
		else
680
			$modSettings['cache_enable'] = $old_cache;
681
682
		// Check for the "lost connection" or "deadlock found" errors - and try it just one more time.
683
		if (in_array($query_errno, array(1205, 1213, 2006, 2013)))
684
		{
685
			if (in_array($query_errno, array(2006, 2013)) && $db_connection == $connection)
686
			{
687
				// Are we in SSI mode?  If so try that username and password first
688
				if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
689
				{
690
					if (empty($db_persist))
691
						$db_connection = @mysqli_connect($db_server, $ssi_db_user, $ssi_db_passwd);
692
					else
693
						$db_connection = @mysqli_connect('p:' . $db_server, $ssi_db_user, $ssi_db_passwd);
694
				}
695
				// Fall back to the regular username and password if need be
696
				if (!$db_connection)
697
				{
698
					if (empty($db_persist))
699
						$db_connection = @mysqli_connect($db_server, $db_user, $db_passwd);
700
					else
701
						$db_connection = @mysqli_connect('p:' . $db_server, $db_user, $db_passwd);
702
				}
703
704
				if (!$db_connection || !@mysqli_select_db($db_connection, $db_name))
705
					$db_connection = false;
706
			}
707
708
			if ($db_connection)
709
			{
710
				// Try a deadlock more than once more.
711
				for ($n = 0; $n < 4; $n++)
712
				{
713
					$ret = $smcFunc['db_query']('', $db_string, false, false);
714
715
					$new_errno = mysqli_errno($db_connection);
716
					if ($ret !== false || in_array($new_errno, array(1205, 1213)))
717
						break;
718
				}
719
720
				// If it failed again, shucks to be you... we're not trying it over and over.
721
				if ($ret !== false)
722
					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...
723
			}
724
		}
725
		// Are they out of space, perhaps?
726
		elseif ($query_errno == 1030 && (strpos($query_error, ' -1 ') !== false || strpos($query_error, ' 28 ') !== false || strpos($query_error, ' 12 ') !== false))
727
		{
728
			if (!isset($txt))
729
				$query_error .= ' - check database storage space.';
730
			else
731
			{
732
				if (!isset($txt['mysql_error_space']))
733
					loadLanguage('Errors');
734
735
				$query_error .= !isset($txt['mysql_error_space']) ? ' - check database storage space.' : $txt['mysql_error_space'];
736
			}
737
		}
738
	}
739
740
	// Nothing's defined yet... just die with it.
741
	if (empty($context) || empty($txt))
742
		die($query_error);
743
744
	// Show an error message, if possible.
745
	$context['error_title'] = $txt['database_error'];
746
	if (allowedTo('admin_forum'))
747
		$context['error_message'] = nl2br($query_error) . '<br>' . $txt['file'] . ': ' . $file . '<br>' . $txt['line'] . ': ' . $line;
748
	else
749
		$context['error_message'] = $txt['try_again'];
750
751
	if (allowedTo('admin_forum') && isset($db_show_debug) && $db_show_debug === true)
752
	{
753
		$context['error_message'] .= '<br><br>' . nl2br($db_string);
754
	}
755
756
	// It's already been logged... don't log it again.
757
	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...
758
}
759
760
/**
761
 * Inserts data into a table
762
 *
763
 * @param string $method The insert method - can be 'replace', 'ignore' or 'insert'
764
 * @param string $table The table we're inserting the data into
765
 * @param array $columns An array of the columns we're inserting the data into. Should contain 'column' => 'datatype' pairs
766
 * @param array $data The data to insert
767
 * @param array $keys The keys for the table
768
 * @param int returnmode 0 = nothing(default), 1 = last row id, 2 = all rows id as array
769
 * @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...
770
 * @return mixed value of the first key, behavior based on returnmode. null if no data.
771
 */
772
function smf_db_insert($method = 'replace', $table, $columns, $data, $keys, $returnmode = 0, $connection = null)
0 ignored issues
show
Coding Style introduced by
Parameters which have default values should be placed at the end.

If you place a parameter with a default value before a parameter with a default value, the default value of the first parameter will never be used as it will always need to be passed anyway:

// $a must always be passed; it's default value is never used.
function someFunction($a = 5, $b) { }
Loading history...
773
{
774
	global $smcFunc, $db_connection, $db_prefix;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
775
776
	$connection = $connection === null ? $db_connection : $connection;
777
	
778
	$return_var = null;
779
780
	// With nothing to insert, simply return.
781
	if (empty($data))
782
		return;
783
784
	// Replace the prefix holder with the actual prefix.
785
	$table = str_replace('{db_prefix}', $db_prefix, $table);
786
	
787
	$with_returning = false;
788
	
789 View Code Duplication
	if (!empty($keys) && (count($keys) > 0) && $returnmode > 0)
790
	{
791
		$with_returning = true;
792
		if ($returnmode == 2)
793
			$return_var = array();
794
	}
795
796
	// Inserting data as a single row can be done as a single array.
797
	if (!is_array($data[array_rand($data)]))
798
		$data = array($data);
799
800
	// Create the mold for a single row insert.
801
	$insertData = '(';
802
	foreach ($columns as $columnName => $type)
803
	{
804
		// Are we restricting the length?
805
		if (strpos($type, 'string-') !== false)
806
			$insertData .= sprintf('SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName);
807
		else
808
			$insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName);
809
	}
810
	$insertData = substr($insertData, 0, -2) . ')';
811
812
	// Create an array consisting of only the columns.
813
	$indexed_columns = array_keys($columns);
814
815
	// Here's where the variables are injected to the query.
816
	$insertRows = array();
817
	foreach ($data as $dataRow)
818
		$insertRows[] = smf_db_quote($insertData, array_combine($indexed_columns, $dataRow), $connection);
819
820
	// Determine the method of insertion.
821
	$queryTitle = $method == 'replace' ? 'REPLACE' : ($method == 'ignore' ? 'INSERT IGNORE' : 'INSERT');
822
823
	if (!$with_returning || $method != 'ingore')
824
	{
825
		// Do the insert.
826
		$smcFunc['db_query']('', '
827
			' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`)
828
			VALUES
829
				' . implode(',
830
				', $insertRows),
831
			array(
832
				'security_override' => true,
833
				'db_error_skip' => $table === $db_prefix . 'log_errors',
834
			),
835
			$connection
836
		);
837
	}
838
	else //special way for ignore method with returning
839
	{
840
		$count = count($insertRows);
841
		$ai = 0;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ai. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
842
		for($i = 0; $i < $count; $i++)
843
		{
844
			$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...
845
			
846
			$smcFunc['db_query']('', '
847
				' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`)
848
				VALUES
849
					' . $insertRows[$i],
850
				array(
851
					'security_override' => true,
852
					'db_error_skip' => $table === $db_prefix . 'log_errors',
853
				),
854
				$connection
855
			);
856
			$new_id = $smcFunc['db_insert_id']();
857
			
858
			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...
859
			{
860
				$ai = $new_id;
861
			}
862
			else	// the inserted value already exists we need to find the pk
863
			{
864
				$where_string = '';
865
				$count2 = count($indexed_columns);
866
				for ($x = 0; $x < $count2; $x++)
867
				{
868
					$where_string += key($indexed_columns[$x]) . ' = '. $insertRows[$i][$x];
869
					if (($x + 1) < $count2)
870
						$where_string += ' AND ';
871
				}
872
873
				$request = $smcFunc['db_query']('','
874
					SELECT `'. $keys[0] . '` FROM ' . $table .'
875
					WHERE ' . $where_string . ' LIMIT 1',
876
					array()
877
				);
878
				
879
				if ($request !== false && $smcFunc['db_num_rows']($request) == 1)
880
				{
881
					$row = $smcFunc['db_fetch_assoc']($request);
882
					$ai = $row[$keys[0]];
883
				}
884
			}
885
			
886
			if ($returnmode == 1)
887
				$return_var = $ai;
888
			else if ($returnmode == 2)
889
				$return_var[] = $ai;
890
		}
891
	}
892
	
893
894
	if ($with_returning)
895
	{
896
		if ($returnmode == 1 && empty($return_var))
897
			$return_var = smf_db_insert_id($table, $keys[0]) + count($insertRows) - 1;
898
		else if ($returnmode == 2 && empty($return_var))
899
		{
900
			$return_var = array();
901
			$count = count($insertRows);
902
			$start = smf_db_insert_id($table, $keys[0]);
903
			for ($i = 0; $i < $count; $i++ )
904
				$return_var[] = $start + $i;
905
		}
906
		return $return_var;
907
	}
908
}
909
910
/**
911
 * This function tries to work out additional error information from a back trace.
912
 *
913
 * @param string $error_message The error message
914
 * @param string $log_message The message to log
915
 * @param string|bool $error_type What type of error this is
916
 * @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...
917
 * @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...
918
 * @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...
919
 */
920 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...
921
{
922
	if (empty($log_message))
923
		$log_message = $error_message;
924
925
	foreach (debug_backtrace() as $step)
926
	{
927
		// Found it?
928
		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)
929
		{
930
			$log_message .= '<br>Function: ' . $step['function'];
931
			break;
932
		}
933
934
		if (isset($step['line']))
935
		{
936
			$file = $step['file'];
937
			$line = $step['line'];
938
		}
939
	}
940
941
	// A special case - we want the file and line numbers for debugging.
942
	if ($error_type == 'return')
943
		return array($file, $line);
944
945
	// Is always a critical error.
946
	if (function_exists('log_error'))
947
		log_error($log_message, 'critical', $file, $line);
948
949
	if (function_exists('fatal_error'))
950
	{
951
		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...
952
953
		// Cannot continue...
954
		exit;
955
	}
956
	elseif ($error_type)
957
		trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''), $error_type);
958
	else
959
		trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''));
960
}
961
962
/**
963
 * Escape the LIKE wildcards so that they match the character and not the wildcard.
964
 *
965
 * @param string $string The string to escape
966
 * @param bool $translate_human_wildcards If true, turns human readable wildcards into SQL wildcards.
967
 * @return string The escaped string
968
 */
969 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...
970
{
971
	$replacements = array(
972
		'%' => '\%',
973
		'_' => '\_',
974
		'\\' => '\\\\',
975
	);
976
977
	if ($translate_human_wildcards)
978
		$replacements += array(
979
			'*' => '%',
980
		);
981
982
	return strtr($string, $replacements);
983
}
984
985
/**
986
 * Validates whether the resource is a valid mysqli instance.
987
 * Mysqli uses objects rather than resource. https://bugs.php.net/bug.php?id=42797
988
 *
989
 * @param mixed $result The string to test
990
 * @return bool True if it is, false otherwise
991
 */
992
function smf_is_resource($result)
993
{
994
	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...
995
		return true;
996
997
	return false;
998
}
999
1000
/**
1001
 * Fetches all rows from a result as an array 
1002
 *
1003
 * @param resource $request A MySQL result resource
1004
 * @return array An array that contains all rows (records) in the result resource
1005
 */
1006
function smf_db_fetch_all($request)
1007
{
1008
	// Return the right row.
1009
	return mysqli_fetch_all($request);
1010
}
1011
1012
/**
1013
 * Function to save errors in database in a safe way
1014
 *
1015
 * @param array with keys in this order id_member, log_time, ip, url, message, session, error_type, file, line
1016
 * @return void
1017
 */
1018
function smf_db_error_insert($error_array)
1019
{
1020
	global  $db_prefix, $db_connection;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1021
	static $mysql_error_data_prep;
1022
1023
	if (empty($mysql_error_data_prep))
1024
			$mysql_error_data_prep = mysqli_prepare($db_connection,
1025
				'INSERT INTO ' . $db_prefix . 'log_errors(id_member, log_time, ip, url, message, session, error_type, file, line)
1026
													VALUES(		?,		?,		unhex(?), ?, 		?,		?,			?,		?,	?)'
1027
			);
1028
1029
	if (filter_var($error_array[2], FILTER_VALIDATE_IP) !== false)
1030
		$error_array[2] = bin2hex(inet_pton($error_array[2]));
1031
	else
1032
		$error_array[2] = null;
1033
	mysqli_stmt_bind_param($mysql_error_data_prep, 'iissssssi', 
1034
		$error_array[0], $error_array[1], $error_array[2], $error_array[3], $error_array[4], $error_array[5], $error_array[6],
1035
		$error_array[7], $error_array[8]);
1036
	mysqli_stmt_execute ($mysql_error_data_prep);
1037
}
1038
1039
?>