Completed
Push — release-2.1 ( e3ceef...dd179b )
by Colin
07:50
created

Subs-Db-mysql.php ➔ smf_db_native_replace()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 4
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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