Completed
Push — release-2.1 ( 9c605b...9de5bf )
by Mathias
15s
created

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

Complexity

Conditions 3
Paths 3

Size

Total Lines 24
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 1
dl 0
loc 24
rs 8.9713
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
		);
66
67
	if (!empty($db_options['persist']))
68
		$db_server = 'p:' . $db_server;
69
70
	$connection = mysqli_init();
71
72
	$flags = 2; //MYSQLI_CLIENT_FOUND_ROWS = 2
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, null, $db_options['port'], null, $flags);
79
		else
80
			$success = mysqli_real_connect($connection, $db_server, $db_user, $db_passwd, null, 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
	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\'');
97
98
	return $connection;
99
}
100
101
/**
102
 * Extend the database functionality. It calls the respective file's init
103
 * to add the implementations in that file to $smcFunc array.
104
 *
105
 * @param string $type Indicates which additional file to load. ('extra', 'packages')
106
 */
107
function db_extend($type = 'extra')
108
{
109
	global $sourcedir;
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...
110
111
	// we force the MySQL files as nothing syntactically changes with MySQLi
112
	require_once($sourcedir . '/Db' . strtoupper($type[0]) . substr($type, 1) . '-mysql.php');
113
	$initFunc = 'db_' . $type . '_init';
114
	$initFunc();
115
}
116
117
/**
118
 * Fix up the prefix so it doesn't require the database to be selected.
119
 *
120
 * @param string &$db_prefix The table prefix
121
 * @param string $db_name The database name
122
 */
123
function db_fix_prefix(&$db_prefix, $db_name)
0 ignored issues
show
Unused Code introduced by
The parameter $db_prefix is not used and could be removed.

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

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

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

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