Completed
Push — release-2.1 ( eb2022...3b2b58 )
by Colin
18:08 queued 08:32
created

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

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 8

Duplication

Lines 12
Ratio 100 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 4
nop 3
dl 12
loc 12
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
			'db_custom_order'			=> 'smf_db_custom_order',
65
		);
66
67
	if (!empty($db_options['persist']))
68
		$db_server = 'p:' . $db_server;
69
70
	$connection = mysqli_init();
71
72
	$flags = MYSQLI_CLIENT_FOUND_ROWS;
73
74
	$success = false;
75
76
	if ($connection) {
77
		if (!empty($db_options['port']))
78
			$success = mysqli_real_connect($connection, $db_server, $db_user, $db_passwd, '', $db_options['port'], null, $flags);
79
		else
80
			$success = mysqli_real_connect($connection, $db_server, $db_user, $db_passwd, '', 0, null, $flags);
81
	}
82
83
	// Something's wrong, show an error if its fatal (which we assume it is)
84
	if ($success === false)
85
	{
86
		if (!empty($db_options['non_fatal']))
87
			return null;
88
		else
89
			display_db_error();
90
	}
91
92
	// Select the database, unless told not to
93
	if (empty($db_options['dont_select_db']) && !@mysqli_select_db($connection, $db_name) && empty($db_options['non_fatal']))
94
		display_db_error();
95
96
	$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\'',
97
		array(),
98
		false
99
	);
100
101
	return $connection;
102
}
103
104
/**
105
 * Extend the database functionality. It calls the respective file's init
106
 * to add the implementations in that file to $smcFunc array.
107
 *
108
 * @param string $type Indicates which additional file to load. ('extra', 'packages')
109
 */
110
function db_extend($type = 'extra')
111
{
112
	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...
113
114
	// we force the MySQL files as nothing syntactically changes with MySQLi
115
	require_once($sourcedir . '/Db' . strtoupper($type[0]) . substr($type, 1) . '-mysql.php');
116
	$initFunc = 'db_' . $type . '_init';
117
	$initFunc();
118
}
119
120
/**
121
 * Fix up the prefix so it doesn't require the database to be selected.
122
 *
123
 * @param string &$db_prefix The table prefix
124
 * @param string $db_name The database name
125
 */
126
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...
127
{
128
	$db_prefix = is_numeric(substr($db_prefix, 0, 1)) ? $db_name . '.' . $db_prefix : '`' . $db_name . '`.' . $db_prefix;
129
}
130
131
/**
132
 * Wrap mysqli_select_db so the connection does not need to be specified
133
 *
134
 * @param string &$database The database
135
 * @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...
136
 * @return bool Whether the database was selected
137
 */
138
function smf_db_select($database, $connection = null)
139
{
140
	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...
141
	return mysqli_select_db($connection === null ? $db_connection : $connection, $database);
142
}
143
144
/**
145
 * Wrap mysqli_get_server_info so the connection does not need to be specified
146
 *
147
 * @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...
148
 * @return string The server info
149
 */
150
function smf_db_get_server_info($connection = null)
151
{
152
	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...
153
	return mysqli_get_server_info($connection === null ? $db_connection : $connection);
154
}
155
156
/**
157
 * Callback for preg_replace_callback on the query.
158
 * It allows to replace on the fly a few pre-defined strings, for convenience ('query_see_board', 'query_wanna_see_board', etc), with
159
 * their current values from $user_info.
160
 * In addition, it performs checks and sanitization on the values sent to the database.
161
 *
162
 * @param array $matches The matches from preg_replace_callback
163
 * @return string The appropriate string depending on $matches[1]
164
 */
165
function smf_db_replacement__callback($matches)
166
{
167
	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...
168
169
	list ($values, $connection) = $db_callback;
170
	if (!is_object($connection))
171
		display_db_error();
172
173
	if ($matches[1] === 'db_prefix')
174
		return $db_prefix;
175
176
	if (isset($user_info[$matches[1]]) && strpos($matches[1], 'query_') !== false)
177
		return $user_info[$matches[1]];
178
179
	if ($matches[1] === 'empty')
180
		return '\'\'';
181
182
	if (!isset($matches[2]))
183
		smf_db_error_backtrace('Invalid value inserted or no type specified.', '', E_USER_ERROR, __FILE__, __LINE__);
184
185
	if ($matches[1] === 'literal')
186
		return '\'' . mysqli_real_escape_string($connection, $matches[2]) . '\'';
187
188
	if (!isset($values[$matches[2]]))
189
		smf_db_error_backtrace('The database value you\'re trying to insert does not exist: ' . (isset($smcFunc['htmlspecialchars']) ? $smcFunc['htmlspecialchars']($matches[2]) : htmlspecialchars($matches[2])), '', E_USER_ERROR, __FILE__, __LINE__);
190
191
	$replacement = $values[$matches[2]];
192
193
	switch ($matches[1])
194
	{
195
		case 'int':
196
			if (!is_numeric($replacement) || (string) $replacement !== (string) (int) $replacement)
197
				smf_db_error_backtrace('Wrong value type sent to the database. Integer expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
198
			return (string) (int) $replacement;
199
		break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

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

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

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

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

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

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

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

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

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

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

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

Loading history...
270
271
		case 'identifier':
272
			// Backticks inside identifiers are supported as of MySQL 4.1. We don't need them for SMF.
273
			return '`' . strtr($replacement, array('`' => '', '.' => '`.`')) . '`';
274
		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...
275
276
		case 'raw':
277
			return $replacement;
278
		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...
279
280
		case 'inet':
281
			if ($replacement == 'null' || $replacement == '')
282
				return 'null';
283
			if (!isValidIP($replacement))
284
				smf_db_error_backtrace('Wrong value type sent to the database. IPv4 or IPv6 expected.(' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
285
			//we don't use the native support of mysql > 5.6.2
286
			return sprintf('unhex(\'%1$s\')', bin2hex(inet_pton($replacement)));
287
288
		case 'array_inet':
289
			if (is_array($replacement))
290
			{
291
				if (empty($replacement))
292
					smf_db_error_backtrace('Database error, given array of IPv4 or IPv6 values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
293
294
				foreach ($replacement as $key => $value)
295
				{
296
					if ($replacement == 'null' || $replacement == '')
297
						$replacement[$key] = 'null';
298
					if (!isValidIP($value))
299
						smf_db_error_backtrace('Wrong value type sent to the database. IPv4 or IPv6 expected.(' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
300
					$replacement[$key] = sprintf('unhex(\'%1$s\')', bin2hex(inet_pton($value)));
301
				}
302
303
				return implode(', ', $replacement);
304
			}
305
			else
306
				smf_db_error_backtrace('Wrong value type sent to the database. Array of IPv4 or IPv6 expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
307
		break;
308
309
		default:
310
			smf_db_error_backtrace('Undefined type used in the database query. (' . $matches[1] . ':' . $matches[2] . ')', '', false, __FILE__, __LINE__);
311
		break;
312
	}
313
}
314
315
/**
316
 * Just like the db_query, escape and quote a string, but not executing the query.
317
 *
318
 * @param string $db_string The database string
319
 * @param array $db_values An array of values to be injected into the string
320
 * @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...
321
 * @return string The string with the values inserted
322
 */
323 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...
324
{
325
	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...
326
327
	// Only bother if there's something to replace.
328
	if (strpos($db_string, '{') !== false)
329
	{
330
		// This is needed by the callback function.
331
		$db_callback = array($db_values, $connection === null ? $db_connection : $connection);
332
333
		// Do the quoting and escaping
334
		$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string);
335
336
		// Clear this global variable.
337
		$db_callback = array();
338
	}
339
340
	return $db_string;
341
}
342
343
/**
344
 * Do a query.  Takes care of errors too.
345
 *
346
 * @param string $identifier An identifier. Only used in Postgres when we need to do things differently...
347
 * @param string $db_string The database string
348
 * @param array $db_values = array() The values to be inserted into the string
349
 * @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...
350
 * @return resource|bool Returns a MySQL result resource (for SELECT queries), true (for UPDATE queries) or false if the query failed
351
 */
352
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...
353
{
354
	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...
355
	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...
356
357
	// Comments that are allowed in a query are preg_removed.
358
	static $allowed_comments_from = array(
359
		'~\s+~s',
360
		'~/\*!40001 SQL_NO_CACHE \*/~',
361
		'~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~',
362
		'~/\*!40100 ON DUPLICATE KEY UPDATE id_msg = \d+ \*/~',
363
	);
364
	static $allowed_comments_to = array(
365
		' ',
366
		'',
367
		'',
368
		'',
369
	);
370
371
	// Decide which connection to use.
372
	$connection = $connection === null ? $db_connection : $connection;
373
374
	// Get a connection if we are shutting down, sometimes the link is closed before sessions are written
375
	if (!is_object($connection))
376
	{
377
		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...
378
379
		// Are we in SSI mode?  If so try that username and password first
380
		if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
381
		{
382
			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...
383
				$db_connection = @mysqli_connect($db_server, $ssi_db_user, $ssi_db_passwd);
384
			else
385
				$db_connection = @mysqli_connect('p:' . $db_server, $ssi_db_user, $ssi_db_passwd);
386
		}
387
		// Fall back to the regular username and password if need be
388
		if (!$db_connection)
389
		{
390
			if (empty($db_persist))
391
				$db_connection = @mysqli_connect($db_server, $db_user, $db_passwd);
392
			else
393
				$db_connection = @mysqli_connect('p:' . $db_server, $db_user, $db_passwd);
394
		}
395
396
		if (!$db_connection || !@mysqli_select_db($db_connection, $db_name))
397
			$db_connection = false;
398
399
		$connection = $db_connection;
400
	}
401
402
	// One more query....
403
	$db_count = !isset($db_count) ? 1 : $db_count + 1;
404
405 View Code Duplication
	if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override']))
406
		smf_db_error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__);
407
408
	// Use "ORDER BY null" to prevent Mysql doing filesorts for Group By clauses without an Order By
409
	if (strpos($db_string, 'GROUP BY') !== false && strpos($db_string, 'ORDER BY') === false && preg_match('~^\s+SELECT~i', $db_string))
410
	{
411
		// Add before LIMIT
412
		if ($pos = strpos($db_string, 'LIMIT '))
413
			$db_string = substr($db_string, 0, $pos) . "\t\t\tORDER BY null\n" . substr($db_string, $pos, strlen($db_string));
414
		else
415
			// Append it.
416
			$db_string .= "\n\t\t\tORDER BY null";
417
	}
418
419 View Code Duplication
	if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false))
420
	{
421
		// Pass some values to the global space for use in the callback function.
422
		$db_callback = array($db_values, $connection);
423
424
		// Inject the values passed to this function.
425
		$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string);
426
427
		// This shouldn't be residing in global space any longer.
428
		$db_callback = array();
429
	}
430
431
	// Debugging.
432
	if (isset($db_show_debug) && $db_show_debug === true)
433
	{
434
		// Get the file and line number this function was called.
435
		list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__);
436
437
		// Initialize $db_cache if not already initialized.
438
		if (!isset($db_cache))
439
			$db_cache = array();
440
441
		if (!empty($_SESSION['debug_redirect']))
442
		{
443
			$db_cache = array_merge($_SESSION['debug_redirect'], $db_cache);
444
			$db_count = count($db_cache) + 1;
445
			$_SESSION['debug_redirect'] = array();
446
		}
447
448
		// Don't overload it.
449
		$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...
450
		$db_cache[$db_count]['q'] = $db_count < 50 ? $db_string : '...';
451
		$db_cache[$db_count]['f'] = $file;
452
		$db_cache[$db_count]['l'] = $line;
453
		$db_cache[$db_count]['s'] = $st - $time_start;
454
	}
455
456
	// First, we clean strings out of the query, reduce whitespace, lowercase, and trim - so we can check it over.
457 View Code Duplication
	if (empty($modSettings['disableQueryCheck']))
458
	{
459
		$clean = '';
460
		$old_pos = 0;
461
		$pos = -1;
462
		// Remove the string escape for better runtime
463
		$db_string_1 = str_replace('\\\'','',$db_string);
464
		while (true)
465
		{
466
			$pos = strpos($db_string_1, '\'', $pos + 1);
467
			if ($pos === false)
468
				break;
469
			$clean .= substr($db_string_1, $old_pos, $pos - $old_pos);
470
471
			while (true)
472
			{
473
				$pos1 = strpos($db_string_1, '\'', $pos + 1);
474
				$pos2 = strpos($db_string_1, '\\', $pos + 1);
475
				if ($pos1 === false)
476
					break;
477
				elseif ($pos2 === false || $pos2 > $pos1)
478
				{
479
					$pos = $pos1;
480
					break;
481
				}
482
483
				$pos = $pos2 + 1;
484
			}
485
			$clean .= ' %s ';
486
487
			$old_pos = $pos + 1;
488
		}
489
		$clean .= substr($db_string_1, $old_pos);
490
		$clean = trim(strtolower(preg_replace($allowed_comments_from, $allowed_comments_to, $clean)));
491
492
		// Comments?  We don't use comments in our queries, we leave 'em outside!
493
		if (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false)
494
			$fail = true;
495
		// Trying to change passwords, slow us down, or something?
496
		elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[_a-z])~s', $clean) != 0)
497
			$fail = true;
498
		elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
499
			$fail = true;
500
501
		if (!empty($fail) && function_exists('log_error'))
502
			smf_db_error_backtrace('Hacking attempt...', 'Hacking attempt...' . "\n" . $db_string, E_USER_ERROR, __FILE__, __LINE__);
503
	}
504
505
	if (empty($db_unbuffered))
506
		$ret = @mysqli_query($connection, $db_string);
507
	else
508
		$ret = @mysqli_query($connection, $db_string, MYSQLI_USE_RESULT);
509
510 View Code Duplication
	if ($ret === false && empty($db_values['db_error_skip']))
511
		$ret = smf_db_error($db_string, $connection);
512
513
	// Debugging.
514 View Code Duplication
	if (isset($db_show_debug) && $db_show_debug === true)
515
		$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...
516
517
	return $ret;
518
}
519
520
/**
521
 * affected_rows
522
 * @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...
523
 * @return int The number of rows affected by the last query
524
 */
525
function smf_db_affected_rows($connection = null)
526
{
527
	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...
528
529
	return mysqli_affected_rows($connection === null ? $db_connection : $connection);
530
}
531
532
/**
533
 * Gets the ID of the most recently inserted row.
534
 *
535
 * @param string $table The table (only used for Postgres)
536
 * @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...
537
 * @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...
538
 * @return int The ID of the most recently inserted row
539
 */
540
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...
541
{
542
	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...
543
544
	// MySQL doesn't need the table or field information.
545
	return mysqli_insert_id($connection === null ? $db_connection : $connection);
546
}
547
548
/**
549
 * Do a transaction.
550
 *
551
 * @param string $type The step to perform (i.e. 'begin', 'commit', 'rollback')
552
 * @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...
553
 * @return bool True if successful, false otherwise
554
 */
555 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...
556
{
557
	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...
558
559
	// Decide which connection to use
560
	$connection = $connection === null ? $db_connection : $connection;
561
562
	if ($type == 'begin')
563
		return @mysqli_query($connection, 'BEGIN');
564
	elseif ($type == 'rollback')
565
		return @mysqli_query($connection, 'ROLLBACK');
566
	elseif ($type == 'commit')
567
		return @mysqli_query($connection, 'COMMIT');
568
569
	return false;
570
}
571
572
/**
573
 * Database error!
574
 * Backtrace, log, try to fix.
575
 *
576
 * @param string $db_string The DB string
577
 * @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...
578
 */
579
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...
580
{
581
	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...
582
	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...
583
	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...
584
	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...
585
586
	// Get the file and line numbers.
587
	list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__);
588
589
	// Decide which connection to use
590
	$connection = $connection === null ? $db_connection : $connection;
591
592
	// This is the error message...
593
	$query_error = mysqli_error($connection);
594
	$query_errno = mysqli_errno($connection);
595
596
	// Error numbers:
597
	//    1016: Can't open file '....MYI'
598
	//    1030: Got error ??? from table handler.
599
	//    1034: Incorrect key file for table.
600
	//    1035: Old key file for table.
601
	//    1205: Lock wait timeout exceeded.
602
	//    1213: Deadlock found.
603
	//    2006: Server has gone away.
604
	//    2013: Lost connection to server during query.
605
606
	// Log the error.
607
	if ($query_errno != 1213 && $query_errno != 1205 && function_exists('log_error'))
608
		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...
609
610
	// Database error auto fixing ;).
611
	if (function_exists('cache_get_data') && (!isset($modSettings['autoFixDatabase']) || $modSettings['autoFixDatabase'] == '1'))
612
	{
613
		// Force caching on, just for the error checking.
614
		$old_cache = @$modSettings['cache_enable'];
615
		$modSettings['cache_enable'] = '1';
616
617
		if (($temp = cache_get_data('db_last_error', 600)) !== null)
618
			$db_last_error = max(@$db_last_error, $temp);
619
620
		if (@$db_last_error < time() - 3600 * 24 * 3)
621
		{
622
			// We know there's a problem... but what?  Try to auto detect.
623
			if ($query_errno == 1030 && strpos($query_error, ' 127 ') !== false)
624
			{
625
				preg_match_all('~(?:[\n\r]|^)[^\']+?(?:FROM|JOIN|UPDATE|TABLE) ((?:[^\n\r(]+?(?:, )?)*)~s', $db_string, $matches);
626
627
				$fix_tables = array();
628
				foreach ($matches[1] as $tables)
629
				{
630
					$tables = array_unique(explode(',', $tables));
631
					foreach ($tables as $table)
632
					{
633
						// Now, it's still theoretically possible this could be an injection.  So backtick it!
634
						if (trim($table) != '')
635
							$fix_tables[] = '`' . strtr(trim($table), array('`' => '')) . '`';
636
					}
637
				}
638
639
				$fix_tables = array_unique($fix_tables);
640
			}
641
			// Table crashed.  Let's try to fix it.
642
			elseif ($query_errno == 1016)
643
			{
644
				if (preg_match('~\'([^\.\']+)~', $query_error, $match) != 0)
645
					$fix_tables = array('`' . $match[1] . '`');
646
			}
647
			// Indexes crashed.  Should be easy to fix!
648
			elseif ($query_errno == 1034 || $query_errno == 1035)
649
			{
650
				preg_match('~\'([^\']+?)\'~', $query_error, $match);
651
				$fix_tables = array('`' . $match[1] . '`');
652
			}
653
		}
654
655
		// 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...)
656
		if (!empty($fix_tables))
657
		{
658
			// Subs-Admin.php for updateSettingsFile(), Subs-Post.php for sendmail().
659
			require_once($sourcedir . '/Subs-Admin.php');
660
			require_once($sourcedir . '/Subs-Post.php');
661
662
			// Make a note of the REPAIR...
663
			cache_put_data('db_last_error', time(), 600);
664
			if (($temp = cache_get_data('db_last_error', 600)) === null)
665
				updateSettingsFile(array('db_last_error' => time()));
666
667
			// Attempt to find and repair the broken table.
668
			foreach ($fix_tables as $table)
669
				$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...
670
					REPAIR TABLE $table", false, false);
671
672
			// And send off an email!
673
			sendmail($webmaster_email, $txt['database_error'], $txt['tried_to_repair'], null, 'dberror');
674
675
			$modSettings['cache_enable'] = $old_cache;
676
677
			// Try the query again...?
678
			$ret = $smcFunc['db_query']('', $db_string, false, false);
679
			if ($ret !== false)
680
				return $ret;
681
		}
682
		else
683
			$modSettings['cache_enable'] = $old_cache;
684
685
		// Check for the "lost connection" or "deadlock found" errors - and try it just one more time.
686
		if (in_array($query_errno, array(1205, 1213, 2006, 2013)))
687
		{
688
			if (in_array($query_errno, array(2006, 2013)) && $db_connection == $connection)
689
			{
690
				// Are we in SSI mode?  If so try that username and password first
691
				if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
692
				{
693
					if (empty($db_persist))
694
						$db_connection = @mysqli_connect($db_server, $ssi_db_user, $ssi_db_passwd);
695
					else
696
						$db_connection = @mysqli_connect('p:' . $db_server, $ssi_db_user, $ssi_db_passwd);
697
				}
698
				// Fall back to the regular username and password if need be
699
				if (!$db_connection)
700
				{
701
					if (empty($db_persist))
702
						$db_connection = @mysqli_connect($db_server, $db_user, $db_passwd);
703
					else
704
						$db_connection = @mysqli_connect('p:' . $db_server, $db_user, $db_passwd);
705
				}
706
707
				if (!$db_connection || !@mysqli_select_db($db_connection, $db_name))
708
					$db_connection = false;
709
			}
710
711
			if ($db_connection)
712
			{
713
				// Try a deadlock more than once more.
714
				for ($n = 0; $n < 4; $n++)
715
				{
716
					$ret = $smcFunc['db_query']('', $db_string, false, false);
717
718
					$new_errno = mysqli_errno($db_connection);
719
					if ($ret !== false || in_array($new_errno, array(1205, 1213)))
720
						break;
721
				}
722
723
				// If it failed again, shucks to be you... we're not trying it over and over.
724
				if ($ret !== false)
725
					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...
726
			}
727
		}
728
		// Are they out of space, perhaps?
729
		elseif ($query_errno == 1030 && (strpos($query_error, ' -1 ') !== false || strpos($query_error, ' 28 ') !== false || strpos($query_error, ' 12 ') !== false))
730
		{
731
			if (!isset($txt))
732
				$query_error .= ' - check database storage space.';
733
			else
734
			{
735
				if (!isset($txt['mysql_error_space']))
736
					loadLanguage('Errors');
737
738
				$query_error .= !isset($txt['mysql_error_space']) ? ' - check database storage space.' : $txt['mysql_error_space'];
739
			}
740
		}
741
	}
742
743
	// Nothing's defined yet... just die with it.
744
	if (empty($context) || empty($txt))
745
		die($query_error);
746
747
	// Show an error message, if possible.
748
	$context['error_title'] = $txt['database_error'];
749
	if (allowedTo('admin_forum'))
750
		$context['error_message'] = nl2br($query_error) . '<br>' . $txt['file'] . ': ' . $file . '<br>' . $txt['line'] . ': ' . $line;
751
	else
752
		$context['error_message'] = $txt['try_again'];
753
754
	if (allowedTo('admin_forum') && isset($db_show_debug) && $db_show_debug === true)
755
	{
756
		$context['error_message'] .= '<br><br>' . nl2br($db_string);
757
	}
758
759
	// It's already been logged... don't log it again.
760
	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...
761
}
762
763
/**
764
 * Inserts data into a table
765
 *
766
 * @param string $method The insert method - can be 'replace', 'ignore' or 'insert'
767
 * @param string $table The table we're inserting the data into
768
 * @param array $columns An array of the columns we're inserting the data into. Should contain 'column' => 'datatype' pairs
769
 * @param array $data The data to insert
770
 * @param array $keys The keys for the table
771
 * @param int returnmode 0 = nothing(default), 1 = last row id, 2 = all rows id as array
772
 * @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...
773
 * @return mixed value of the first key, behavior based on returnmode. null if no data.
774
 */
775
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...
776
{
777
	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...
778
779
	$connection = $connection === null ? $db_connection : $connection;
780
	
781
	$return_var = null;
782
783
	// With nothing to insert, simply return.
784
	if (empty($data))
785
		return;
786
787
	// Replace the prefix holder with the actual prefix.
788
	$table = str_replace('{db_prefix}', $db_prefix, $table);
789
	
790
	$with_returning = false;
791
	
792 View Code Duplication
	if (!empty($keys) && (count($keys) > 0) && $returnmode > 0)
793
	{
794
		$with_returning = true;
795
		if ($returnmode == 2)
796
			$return_var = array();
797
	}
798
799
	// Inserting data as a single row can be done as a single array.
800
	if (!is_array($data[array_rand($data)]))
801
		$data = array($data);
802
803
	// Create the mold for a single row insert.
804
	$insertData = '(';
805
	foreach ($columns as $columnName => $type)
806
	{
807
		// Are we restricting the length?
808
		if (strpos($type, 'string-') !== false)
809
			$insertData .= sprintf('SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName);
810
		else
811
			$insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName);
812
	}
813
	$insertData = substr($insertData, 0, -2) . ')';
814
815
	// Create an array consisting of only the columns.
816
	$indexed_columns = array_keys($columns);
817
818
	// Here's where the variables are injected to the query.
819
	$insertRows = array();
820
	foreach ($data as $dataRow)
821
		$insertRows[] = smf_db_quote($insertData, array_combine($indexed_columns, $dataRow), $connection);
822
823
	// Determine the method of insertion.
824
	$queryTitle = $method == 'replace' ? 'REPLACE' : ($method == 'ignore' ? 'INSERT IGNORE' : 'INSERT');
825
826
	if (!$with_returning || $method != 'ingore')
827
	{
828
		// Do the insert.
829
		$smcFunc['db_query']('', '
830
			' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`)
831
			VALUES
832
				' . implode(',
833
				', $insertRows),
834
			array(
835
				'security_override' => true,
836
				'db_error_skip' => $table === $db_prefix . 'log_errors',
837
			),
838
			$connection
839
		);
840
	}
841
	else //special way for ignore method with returning
842
	{
843
		$count = count($insertRows);
844
		$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...
845
		for($i = 0; $i < $count; $i++)
846
		{
847
			$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...
848
			
849
			$smcFunc['db_query']('', '
850
				' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`)
851
				VALUES
852
					' . $insertRows[$i],
853
				array(
854
					'security_override' => true,
855
					'db_error_skip' => $table === $db_prefix . 'log_errors',
856
				),
857
				$connection
858
			);
859
			$new_id = $smcFunc['db_insert_id']();
860
			
861
			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...
862
			{
863
				$ai = $new_id;
864
			}
865
			else	// the inserted value already exists we need to find the pk
866
			{
867
				$where_string = '';
868
				$count2 = count($indexed_columns);
869
				for ($x = 0; $x < $count2; $x++)
870
				{
871
					$where_string += key($indexed_columns[$x]) . ' = '. $insertRows[$i][$x];
872
					if (($x + 1) < $count2)
873
						$where_string += ' AND ';
874
				}
875
876
				$request = $smcFunc['db_query']('','
877
					SELECT `'. $keys[0] . '` FROM ' . $table .'
878
					WHERE ' . $where_string . ' LIMIT 1',
879
					array()
880
				);
881
				
882
				if ($request !== false && $smcFunc['db_num_rows']($request) == 1)
883
				{
884
					$row = $smcFunc['db_fetch_assoc']($request);
885
					$ai = $row[$keys[0]];
886
				}
887
			}
888
			
889
			if ($returnmode == 1)
890
				$return_var = $ai;
891
			else if ($returnmode == 2)
892
				$return_var[] = $ai;
893
		}
894
	}
895
	
896
897
	if ($with_returning)
898
	{
899
		if ($returnmode == 1 && empty($return_var))
900
			$return_var = smf_db_insert_id($table, $keys[0]) + count($insertRows) - 1;
901
		else if ($returnmode == 2 && empty($return_var))
902
		{
903
			$return_var = array();
904
			$count = count($insertRows);
905
			$start = smf_db_insert_id($table, $keys[0]);
906
			for ($i = 0; $i < $count; $i++ )
907
				$return_var[] = $start + $i;
908
		}
909
		return $return_var;
910
	}
911
}
912
913
/**
914
 * This function tries to work out additional error information from a back trace.
915
 *
916
 * @param string $error_message The error message
917
 * @param string $log_message The message to log
918
 * @param string|bool $error_type What type of error this is
919
 * @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...
920
 * @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...
921
 * @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...
922
 */
923 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...
924
{
925
	if (empty($log_message))
926
		$log_message = $error_message;
927
928
	foreach (debug_backtrace() as $step)
929
	{
930
		// Found it?
931
		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)
932
		{
933
			$log_message .= '<br>Function: ' . $step['function'];
934
			break;
935
		}
936
937
		if (isset($step['line']))
938
		{
939
			$file = $step['file'];
940
			$line = $step['line'];
941
		}
942
	}
943
944
	// A special case - we want the file and line numbers for debugging.
945
	if ($error_type == 'return')
946
		return array($file, $line);
947
948
	// Is always a critical error.
949
	if (function_exists('log_error'))
950
		log_error($log_message, 'critical', $file, $line);
951
952
	if (function_exists('fatal_error'))
953
	{
954
		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...
955
956
		// Cannot continue...
957
		exit;
958
	}
959
	elseif ($error_type)
960
		trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''), $error_type);
961
	else
962
		trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''));
963
}
964
965
/**
966
 * Escape the LIKE wildcards so that they match the character and not the wildcard.
967
 *
968
 * @param string $string The string to escape
969
 * @param bool $translate_human_wildcards If true, turns human readable wildcards into SQL wildcards.
970
 * @return string The escaped string
971
 */
972 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...
973
{
974
	$replacements = array(
975
		'%' => '\%',
976
		'_' => '\_',
977
		'\\' => '\\\\',
978
	);
979
980
	if ($translate_human_wildcards)
981
		$replacements += array(
982
			'*' => '%',
983
		);
984
985
	return strtr($string, $replacements);
986
}
987
988
/**
989
 * Validates whether the resource is a valid mysqli instance.
990
 * Mysqli uses objects rather than resource. https://bugs.php.net/bug.php?id=42797
991
 *
992
 * @param mixed $result The string to test
993
 * @return bool True if it is, false otherwise
994
 */
995
function smf_is_resource($result)
996
{
997
	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...
998
		return true;
999
1000
	return false;
1001
}
1002
1003
/**
1004
 * Fetches all rows from a result as an array 
1005
 *
1006
 * @param resource $request A MySQL result resource
1007
 * @return array An array that contains all rows (records) in the result resource
1008
 */
1009
function smf_db_fetch_all($request)
1010
{
1011
	// Return the right row.
1012
	return mysqli_fetch_all($request);
1013
}
1014
1015
/**
1016
 * Function to save errors in database in a safe way
1017
 *
1018
 * @param array with keys in this order id_member, log_time, ip, url, message, session, error_type, file, line
1019
 * @return void
1020
 */
1021
function smf_db_error_insert($error_array)
1022
{
1023
	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...
1024
	static $mysql_error_data_prep;
1025
1026
	if (empty($mysql_error_data_prep))
1027
			$mysql_error_data_prep = mysqli_prepare($db_connection,
1028
				'INSERT INTO ' . $db_prefix . 'log_errors(id_member, log_time, ip, url, message, session, error_type, file, line)
1029
													VALUES(		?,		?,		unhex(?), ?, 		?,		?,			?,		?,	?)'
1030
			);
1031
1032
	if (filter_var($error_array[2], FILTER_VALIDATE_IP) !== false)
1033
		$error_array[2] = bin2hex(inet_pton($error_array[2]));
1034
	else
1035
		$error_array[2] = null;
1036
	mysqli_stmt_bind_param($mysql_error_data_prep, 'iissssssi', 
1037
		$error_array[0], $error_array[1], $error_array[2], $error_array[3], $error_array[4], $error_array[5], $error_array[6],
1038
		$error_array[7], $error_array[8]);
1039
	mysqli_stmt_execute ($mysql_error_data_prep);
1040
}
1041
1042
/**
1043
 * Function which constructs an optimize custom order string
1044
 * as an improved alternative to find_in_set()
1045
 *
1046
 * @param string $field name
1047
 * @param array $array_values Field values sequenced in array via order priority. Must cast to int.
1048
 * @param boolean $desc default false
1049
 * @return string case field when ... then ... end
1050
 */
1051 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...
1052
{
1053
	$return = 'CASE '. $field . ' ';
1054
	$count = count($array_values);
1055
	$then = ($desc ? ' THEN -' : ' THEN ');
1056
1057
	for ($i = 0; $i < $count; $i++)
1058
		$return .= 'WHEN ' . (int) $array_values[$i] . $then . $i . ' ';
1059
1060
	$return .= 'END';
1061
	return $return;
1062
}
1063
1064
?>