Completed
Push — release-2.1 ( e6c696...22bfba )
by Mathias
07:04
created

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

Complexity

Conditions 5
Paths 8

Size

Total Lines 16
Code Lines 10

Duplication

Lines 16
Ratio 100 %

Importance

Changes 0
Metric Value
cc 5
eloc 10
nc 8
nop 2
dl 16
loc 16
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 2016 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 3
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, $mysql_set_mode;
34
35
	// Map some database specific functions, only do this once.
36 View Code Duplication
	if (!isset($smcFunc['db_fetch_assoc']) || $smcFunc['db_fetch_assoc'] != 'mysql_fetch_assoc')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
37
		$smcFunc += array(
38
			'db_query' => 'smf_db_query',
39
			'db_quote' => 'smf_db_quote',
40
			'db_fetch_assoc' => 'mysql_fetch_assoc',
41
			'db_fetch_row' => 'mysql_fetch_row',
42
			'db_free_result' => 'mysql_free_result',
43
			'db_insert' => 'smf_db_insert',
44
			'db_insert_id' => 'smf_db_insert_id',
45
			'db_num_rows' => 'mysql_num_rows',
46
			'db_data_seek' => 'mysql_data_seek',
47
			'db_num_fields' => 'mysql_num_fields',
48
			'db_escape_string' => 'addslashes',
49
			'db_unescape_string' => 'stripslashes',
50
			'db_server_info' => 'mysql_get_server_info',
51
			'db_affected_rows' => 'smf_db_affected_rows',
52
			'db_transaction' => 'smf_db_transaction',
53
			'db_error' => 'mysql_error',
54
			'db_select_db' => 'mysql_select_db',
55
			'db_title' => 'MySQL',
56
			'db_sybase' => false,
57
			'db_case_sensitive' => false,
58
			'db_escape_wildcard_string' => 'smf_db_escape_wildcard_string',
59
			'db_is_resource' => 'is_resource',
60
		);
61
62
	if (!empty($db_options['port']))
63
		$db_server .= ':' . $db_options['port'];
64
	
65
	$flags = 2; //#define CLIENT_FOUND_ROWS 2 /* Found instead of affected rows */
66
67
	if (!empty($db_options['persist']))
68
		$connection = @mysql_pconnect($db_server, $db_user, $db_passwd, $flags);
69
	else
70
		$connection = @mysql_connect($db_server, $db_user, $db_passwd, false, $flags);
71
72
	// Something's wrong, show an error if its fatal (which we assume it is)
73
	if (!$connection)
74
	{
75
		if (!empty($db_options['non_fatal']))
76
			return null;
77
		else
78
			display_db_error();
79
	}
80
81
	// Select the database, unless told not to
82 View Code Duplication
	if (empty($db_options['dont_select_db']) && !@mysql_select_db($db_name, $connection) && empty($db_options['non_fatal']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
83
		display_db_error();
84
85
	// This makes it possible to have SMF automatically change the sql_mode and autocommit if needed.
86 View Code Duplication
	if (isset($mysql_set_mode) && $mysql_set_mode === true)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
87
		$smcFunc['db_query']('', 'SET sql_mode = \'\', AUTOCOMMIT = 1',
88
		array(),
89
		false
90
	);
91
92
	return $connection;
93
}
94
95
/**
96
 * Extend the database functionality. It calls the respective file's init
97
 * to add the implementations in that file to $smcFunc array.
98
 *
99
 * @param string $type Indicates which additional file to load. ('extra', 'packages')
100
 */
101 View Code Duplication
function db_extend($type = 'extra')
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
102
{
103
	global $sourcedir, $db_type;
104
105
	require_once($sourcedir . '/Db' . strtoupper($type[0]) . substr($type, 1) . '-' . $db_type . '.php');
106
	$initFunc = 'db_' . $type . '_init';
107
	$initFunc();
108
}
109
110
/**
111
 * Fix up the prefix so it doesn't require the database to be selected.
112
 *
113
 * @param string &$db_prefix The table prefix
114
 * @param string $db_name The database name
115
 */
116
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...
117
{
118
	$db_prefix = is_numeric(substr($db_prefix, 0, 1)) ? $db_name . '.' . $db_prefix : '`' . $db_name . '`.' . $db_prefix;
119
}
120
121
/**
122
 * Callback for preg_replace_callback on the query.
123
 * It allows to replace on the fly a few pre-defined strings, for convenience ('query_see_board', 'query_wanna_see_board'), with
124
 * their current values from $user_info.
125
 * In addition, it performs checks and sanitization on the values sent to the database.
126
 *
127
 * @param array $matches The matches from preg_replace_callback
128
 * @return string The appropriate string depending on $matches[1]
129
 */
130 View Code Duplication
function smf_db_replacement__callback($matches)
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...
131
{
132
	global $db_callback, $user_info, $db_prefix, $smcFunc;
133
134
	list ($values, $connection) = $db_callback;
135
136
	// Connection gone???  This should *never* happen at this point, yet it does :'(
137
	if (!is_resource($connection))
138
		display_db_error();
139
140
	if ($matches[1] === 'db_prefix')
141
		return $db_prefix;
142
143
	if ($matches[1] === 'query_see_board')
144
		return $user_info['query_see_board'];
145
146
	if ($matches[1] === 'query_wanna_see_board')
147
		return $user_info['query_wanna_see_board'];
148
149
	if ($matches[1] === 'empty')
150
		return '\'\'';
151
152
	if (!isset($matches[2]))
153
		smf_db_error_backtrace('Invalid value inserted or no type specified.', '', E_USER_ERROR, __FILE__, __LINE__);
154
155
	if ($matches[1] === 'literal')
156
		return '\'' . mysql_real_escape_string($matches[2], $connection) . '\'';
157
158
	if (!isset($values[$matches[2]]))
159
		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__);
160
161
	$replacement = $values[$matches[2]];
162
163
	switch ($matches[1])
164
	{
165
		case 'int':
166
			if (!is_numeric($replacement) || (string) $replacement !== (string) (int) $replacement)
167
				smf_db_error_backtrace('Wrong value type sent to the database. Integer expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
168
			return (string) (int) $replacement;
169
		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...
170
171
		case 'string':
172
		case 'text':
173
			return sprintf('\'%1$s\'', mysql_real_escape_string($replacement, $connection));
174
		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...
175
176
		case 'array_int':
177
			if (is_array($replacement))
178
			{
179
				if (empty($replacement))
180
					smf_db_error_backtrace('Database error, given array of integer values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
181
182
				foreach ($replacement as $key => $value)
183
				{
184
					if (!is_numeric($value) || (string) $value !== (string) (int) $value)
185
						smf_db_error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
186
187
					$replacement[$key] = (string) (int) $value;
188
				}
189
190
				return implode(', ', $replacement);
191
			}
192
			else
193
				smf_db_error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
194
195
		break;
196
197
		case 'array_string':
198
			if (is_array($replacement))
199
			{
200
				if (empty($replacement))
201
					smf_db_error_backtrace('Database error, given array of string values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
202
203
				foreach ($replacement as $key => $value)
204
					$replacement[$key] = sprintf('\'%1$s\'', mysql_real_escape_string($value, $connection));
205
206
				return implode(', ', $replacement);
207
			}
208
			else
209
				smf_db_error_backtrace('Wrong value type sent to the database. Array of strings expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
210
		break;
211
212
		case 'date':
213
			if (preg_match('~^(\d{4})-([0-1]?\d)-([0-3]?\d)$~', $replacement, $date_matches) === 1)
214
				return sprintf('\'%04d-%02d-%02d\'', $date_matches[1], $date_matches[2], $date_matches[3]);
215
			else
216
				smf_db_error_backtrace('Wrong value type sent to the database. Date expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
217
		break;
218
219
		case 'time':
220
			if (preg_match('~^([0-1]?\d|2[0-3]):([0-5]\d):([0-5]\d)$~', $replacement, $time_matches) === 1)
221
				return sprintf('\'%02d:%02d:%02d\'', $time_matches[1], $time_matches[2], $time_matches[3]);
222
			else
223
				smf_db_error_backtrace('Wrong value type sent to the database. Time expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
224
		break;
225
226
		case 'float':
227
			if (!is_numeric($replacement))
228
				smf_db_error_backtrace('Wrong value type sent to the database. Floating point number expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
229
			return (string) (float) $replacement;
230
		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...
231
232
		case 'identifier':
233
			// Backticks inside identifiers are supported as of MySQL 4.1. We don't need them for SMF.
234
			return '`' . strtr($replacement, array('`' => '', '.' => '')) . '`';
235
		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...
236
237
		case 'raw':
238
			return $replacement;
239
		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...
240
241
		case 'inet':
242
			if ($replacement == 'null' || $replacement == '')
243
				return 'null';
244
			if (!isValidIP($replacement))
245
				smf_db_error_backtrace('Wrong value type sent to the database. IPv4 or IPv6 expected.(' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
246
			//we don't use the native support of mysql > 5.6.2
247
			return sprintf('unhex(\'%1$s\')', bin2hex(inet_pton($replacement)));
248
249
		case 'array_inet':
250
			if (is_array($replacement))
251
			{
252
				if (empty($replacement))
253
					smf_db_error_backtrace('Database error, given array of IPv4 or IPv6 values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
254
255
				foreach ($replacement as $key => $value)
256
				{
257
					if ($replacement == 'null' || $replacement == '')
258
						$replacement[$key] = 'null';
259
					if (!isValidIP($value))
260
						smf_db_error_backtrace('Wrong value type sent to the database. IPv4 or IPv6 expected.(' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
261
					$replacement[$key] =  sprintf('unhex(\'%1$s\')', bin2hex(inet_pton($value)));
262
				}
263
264
				return implode(', ', $replacement);
265
			}
266
			else
267
				smf_db_error_backtrace('Wrong value type sent to the database. Array of IPv4 or IPv6 expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
268
		break;
269
270
		default:
271
			smf_db_error_backtrace('Undefined type used in the database query. (' . $matches[1] . ':' . $matches[2] . ')', '', false, __FILE__, __LINE__);
272
		break;
273
	}
274
}
275
276
/**
277
 * Just like the db_query, escape and quote a string, but not executing the query.
278
 *
279
 * @param string $db_string The database string
280
 * @param array $db_values An array of values to be injected into the string
281
 * @param resource $connection = null The connection to use (null to use $db_connection)
282
 * @return string The string with the values inserted
283
 */
284 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...
285
{
286
	global $db_callback, $db_connection;
287
288
	// Only bother if there's something to replace.
289
	if (strpos($db_string, '{') !== false)
290
	{
291
		// This is needed by the callback function.
292
		$db_callback = array($db_values, $connection === null ? $db_connection : $connection);
293
294
		// Do the quoting and escaping
295
		$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string);
296
297
		// Clear this global variable.
298
		$db_callback = array();
299
	}
300
301
	return $db_string;
302
}
303
304
/**
305
 * Do a query.  Takes care of errors too.
306
 *
307
 * @param string $identifier An identifier. Only used in Postgres when we need to do things differently...
308
 * @param string $db_string The database string
309
 * @param array $db_values = array() The values to be inserted into the string
310
 * @param resource $connection = null The connection to use (null to use $db_connection)
311
 * @return resource|bool Returns a MySQL result resource (for SELECT queries), true (for UPDATE queries) or false if the query failed
312
 */
313
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...
314
{
315
	global $db_cache, $db_count, $db_connection, $db_show_debug, $time_start;
316
	global $db_unbuffered, $db_callback, $modSettings;
317
318
	// Comments that are allowed in a query are preg_removed.
319
	static $allowed_comments_from = array(
320
		'~\s+~s',
321
		'~/\*!40001 SQL_NO_CACHE \*/~',
322
		'~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~',
323
		'~/\*!40100 ON DUPLICATE KEY UPDATE id_msg = \d+ \*/~',
324
	);
325
	static $allowed_comments_to = array(
326
		' ',
327
		'',
328
		'',
329
		'',
330
	);
331
332
	// Decide which connection to use.
333
	$connection = $connection === null ? $db_connection : $connection;
334
335
	// One more query....
336
	$db_count = !isset($db_count) ? 1 : $db_count + 1;
337
338 View Code Duplication
	if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
339
		smf_db_error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__);
340
341
	// Use "ORDER BY null" to prevent Mysql doing filesorts for Group By clauses without an Order By
342 View Code Duplication
	if (strpos($db_string, 'GROUP BY') !== false && strpos($db_string, 'ORDER BY') === false && preg_match('~^\s+SELECT~i', $db_string))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
343
	{
344
		// Add before LIMIT
345
		if ($pos = strpos($db_string, 'LIMIT '))
346
			$db_string = substr($db_string, 0, $pos) . "\t\t\tORDER BY null\n" . substr($db_string, $pos, strlen($db_string));
347
		else
348
			// Append it.
349
			$db_string .= "\n\t\t\tORDER BY null";
350
	}
351
352 View Code Duplication
	if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
353
	{
354
		// Pass some values to the global space for use in the callback function.
355
		$db_callback = array($db_values, $connection);
356
357
		// Inject the values passed to this function.
358
		$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string);
359
360
		// This shouldn't be residing in global space any longer.
361
		$db_callback = array();
362
	}
363
364
	// Debugging.
365 View Code Duplication
	if (isset($db_show_debug) && $db_show_debug === true)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
366
	{
367
		// Get the file and line number this function was called.
368
		list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__);
369
370
		// Initialize $db_cache if not already initialized.
371
		if (!isset($db_cache))
372
			$db_cache = array();
373
374
		if (!empty($_SESSION['debug_redirect']))
375
		{
376
			$db_cache = array_merge($_SESSION['debug_redirect'], $db_cache);
377
			$db_count = count($db_cache) + 1;
378
			$_SESSION['debug_redirect'] = array();
379
		}
380
381
		// Don't overload it.
382
		$st = microtime();
383
		$db_cache[$db_count]['q'] = $db_count < 50 ? $db_string : '...';
384
		$db_cache[$db_count]['f'] = $file;
385
		$db_cache[$db_count]['l'] = $line;
386
		$db_cache[$db_count]['s'] = array_sum(explode(' ', $st)) - array_sum(explode(' ', $time_start));
387
	}
388
389
	// First, we clean strings out of the query, reduce whitespace, lowercase, and trim - so we can check it over.
390 View Code Duplication
	if (empty($modSettings['disableQueryCheck']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
391
	{
392
		$clean = '';
393
		$old_pos = 0;
394
		$pos = -1;
395
		while (true)
396
		{
397
			$pos = strpos($db_string, '\'', $pos + 1);
398
			if ($pos === false)
399
				break;
400
			$clean .= substr($db_string, $old_pos, $pos - $old_pos);
401
402
			while (true)
403
			{
404
				$pos1 = strpos($db_string, '\'', $pos + 1);
405
				$pos2 = strpos($db_string, '\\', $pos + 1);
406
				if ($pos1 === false)
407
					break;
408
				elseif ($pos2 === false || $pos2 > $pos1)
409
				{
410
					$pos = $pos1;
411
					break;
412
				}
413
414
				$pos = $pos2 + 1;
415
			}
416
			$clean .= ' %s ';
417
418
			$old_pos = $pos + 1;
419
		}
420
		$clean .= substr($db_string, $old_pos);
421
		$clean = trim(strtolower(preg_replace($allowed_comments_from, $allowed_comments_to, $clean)));
422
423
		// Comments?  We don't use comments in our queries, we leave 'em outside!
424
		if (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false)
425
			$fail = true;
426
		// Trying to change passwords, slow us down, or something?
427
		elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[_a-z])~s', $clean) != 0)
428
			$fail = true;
429
		elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
430
			$fail = true;
431
432
		if (!empty($fail) && function_exists('log_error'))
433
			smf_db_error_backtrace('Hacking attempt...', 'Hacking attempt...' . "\n" . $db_string, E_USER_ERROR, __FILE__, __LINE__);
434
	}
435
436
	if (empty($db_unbuffered))
437
		$ret = @mysql_query($db_string, $connection);
438
	else
439
		$ret = @mysql_unbuffered_query($db_string, $connection);
440
441 View Code Duplication
	if ($ret === false && empty($db_values['db_error_skip']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
442
		$ret = smf_db_error($db_string, $connection);
443
444
	// Debugging.
445 View Code Duplication
	if (isset($db_show_debug) && $db_show_debug === true)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
446
		$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...
447
448
	return $ret;
449
}
450
451
/**
452
 * affected_rows
453
 * @param resource $connection A connection to use (if null, $db_connection is used)
454
 * @return int The number of rows affected by the last query
455
 */
456
function smf_db_affected_rows($connection = null)
457
{
458
	global $db_connection;
459
460
	return mysql_affected_rows($connection === null ? $db_connection : $connection);
461
}
462
463
/**
464
 * Gets the ID of the most recently inserted row.
465
 *
466
 * @param string $table The table (only used for Postgres)
467
 * @param string $field = null The specific field (not used here)
468
 * @param resource $connection = null The connection (if null, $db_connection is used)
469
 * @return int The ID of the most recently inserted row
470
 */
471 View Code Duplication
function smf_db_insert_id($table, $field = null, $connection = null)
0 ignored issues
show
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...
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...
472
{
473
	global $db_connection, $db_prefix;
474
475
	$table = str_replace('{db_prefix}', $db_prefix, $table);
0 ignored issues
show
Unused Code introduced by
$table 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...
476
477
	// MySQL doesn't need the table or field information.
478
	return mysql_insert_id($connection === null ? $db_connection : $connection);
479
}
480
481
/**
482
 * Do a transaction.
483
 *
484
 * @param string $type The step to perform (i.e. 'begin', 'commit', 'rollback')
485
 * @param resource $connection The connection to use (if null, $db_connection is used)
486
 * @return bool True if successful, false otherwise
487
 */
488 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...
489
{
490
	global $db_connection;
491
492
	// Decide which connection to use
493
	$connection = $connection === null ? $db_connection : $connection;
494
495
	if ($type == 'begin')
496
		return @mysql_query('BEGIN', $connection);
497
	elseif ($type == 'rollback')
498
		return @mysql_query('ROLLBACK', $connection);
499
	elseif ($type == 'commit')
500
		return @mysql_query('COMMIT', $connection);
501
502
	return false;
503
}
504
505
/**
506
 * Database error!
507
 * Backtrace, log, try to fix.
508
 *
509
 * @param string $db_string The DB string
510
 * @param resource $connection The connection to use (if null, $db_connection is used)
511
 */
512 View Code Duplication
function smf_db_error($db_string, $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...
513
{
514
	global $txt, $context, $sourcedir, $webmaster_email, $modSettings;
515
	global $db_connection, $db_last_error, $db_persist;
516
	global $db_server, $db_user, $db_passwd, $db_name, $db_show_debug, $ssi_db_user, $ssi_db_passwd;
517
	global $smcFunc;
518
519
	// Get the file and line numbers.
520
	list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__);
521
522
	// Decide which connection to use
523
	$connection = $connection === null ? $db_connection : $connection;
524
525
	// This is the error message...
526
	$query_error = mysql_error($connection);
527
	$query_errno = mysql_errno($connection);
528
529
	// Error numbers:
530
	//    1016: Can't open file '....MYI'
531
	//    1030: Got error ??? from table handler.
532
	//    1034: Incorrect key file for table.
533
	//    1035: Old key file for table.
534
	//    1205: Lock wait timeout exceeded.
535
	//    1213: Deadlock found.
536
	//    2006: Server has gone away.
537
	//    2013: Lost connection to server during query.
538
539
	// Log the error.
540
	if ($query_errno != 1213 && $query_errno != 1205 && function_exists('log_error'))
541
		log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n$db_string" : ''), 'database', $file, $line);
542
543
	// Database error auto fixing ;).
544
	if (function_exists('cache_get_data') && (!isset($modSettings['autoFixDatabase']) || $modSettings['autoFixDatabase'] == '1'))
545
	{
546
		// Force caching on, just for the error checking.
547
		$old_cache = @$modSettings['cache_enable'];
548
		$modSettings['cache_enable'] = '1';
549
550
		if (($temp = cache_get_data('db_last_error', 600)) !== null)
551
			$db_last_error = max(@$db_last_error, $temp);
552
553
		if (@$db_last_error < time() - 3600 * 24 * 3)
554
		{
555
			// We know there's a problem... but what?  Try to auto detect.
556
			if ($query_errno == 1030 && strpos($query_error, ' 127 ') !== false)
557
			{
558
				preg_match_all('~(?:[\n\r]|^)[^\']+?(?:FROM|JOIN|UPDATE|TABLE) ((?:[^\n\r(]+?(?:, )?)*)~s', $db_string, $matches);
559
560
				$fix_tables = array();
561
				foreach ($matches[1] as $tables)
562
				{
563
					$tables = array_unique(explode(',', $tables));
564
					foreach ($tables as $table)
565
					{
566
						// Now, it's still theoretically possible this could be an injection.  So backtick it!
567
						if (trim($table) != '')
568
							$fix_tables[] = '`' . strtr(trim($table), array('`' => '')) . '`';
569
					}
570
				}
571
572
				$fix_tables = array_unique($fix_tables);
573
			}
574
			// Table crashed.  Let's try to fix it.
575
			elseif ($query_errno == 1016)
576
			{
577
				if (preg_match('~\'([^\.\']+)~', $query_error, $match) != 0)
578
					$fix_tables = array('`' . $match[1] . '`');
579
			}
580
			// Indexes crashed.  Should be easy to fix!
581
			elseif ($query_errno == 1034 || $query_errno == 1035)
582
			{
583
				preg_match('~\'([^\']+?)\'~', $query_error, $match);
584
				$fix_tables = array('`' . $match[1] . '`');
585
			}
586
		}
587
588
		// 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...)
589
		if (!empty($fix_tables))
590
		{
591
			// Subs-Admin.php for updateSettingsFile(), Subs-Post.php for sendmail().
592
			require_once($sourcedir . '/Subs-Admin.php');
593
			require_once($sourcedir . '/Subs-Post.php');
594
595
			// Make a note of the REPAIR...
596
			cache_put_data('db_last_error', time(), 600);
597
			if (($temp = cache_get_data('db_last_error', 600)) === null)
598
				updateSettingsFile(array('db_last_error' => time()));
599
600
			// Attempt to find and repair the broken table.
601
			foreach ($fix_tables as $table)
602
				$smcFunc['db_query']('', "
603
					REPAIR TABLE $table", false, false);
604
605
			// And send off an email!
606
			sendmail($webmaster_email, $txt['database_error'], $txt['tried_to_repair'], null, 'dberror');
607
608
			$modSettings['cache_enable'] = $old_cache;
609
610
			// Try the query again...?
611
			$ret = $smcFunc['db_query']('', $db_string, false, false);
612
			if ($ret !== false)
613
				return $ret;
614
		}
615
		else
616
			$modSettings['cache_enable'] = $old_cache;
617
618
		// Check for the "lost connection" or "deadlock found" errors - and try it just one more time.
619
		if (in_array($query_errno, array(1205, 1213, 2006, 2013)))
620
		{
621
			if (in_array($query_errno, array(2006, 2013)) && $db_connection == $connection)
622
			{
623
				// Are we in SSI mode?  If so try that username and password first
624
				if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
625
				{
626
					if (empty($db_persist))
627
						$db_connection = @mysql_connect($db_server, $ssi_db_user, $ssi_db_passwd);
628
					else
629
						$db_connection = @mysql_pconnect($db_server, $ssi_db_user, $ssi_db_passwd);
630
				}
631
				// Fall back to the regular username and password if need be
632
				if (!$db_connection)
633
				{
634
					if (empty($db_persist))
635
						$db_connection = @mysql_connect($db_server, $db_user, $db_passwd);
636
					else
637
						$db_connection = @mysql_pconnect($db_server, $db_user, $db_passwd);
638
				}
639
640
				if (!$db_connection || !@mysql_select_db($db_name, $db_connection))
641
					$db_connection = false;
642
			}
643
644
			if ($db_connection)
645
			{
646
				// Try a deadlock more than once more.
647
				for ($n = 0; $n < 4; $n++)
648
				{
649
					$ret = $smcFunc['db_query']('', $db_string, false, false);
650
651
					$new_errno = mysql_errno($db_connection);
652
					if ($ret !== false || in_array($new_errno, array(1205, 1213)))
653
						break;
654
				}
655
656
				// If it failed again, shucks to be you... we're not trying it over and over.
657
				if ($ret !== false)
658
					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...
659
			}
660
		}
661
		// Are they out of space, perhaps?
662
		elseif ($query_errno == 1030 && (strpos($query_error, ' -1 ') !== false || strpos($query_error, ' 28 ') !== false || strpos($query_error, ' 12 ') !== false))
663
		{
664
			if (!isset($txt))
665
				$query_error .= ' - check database storage space.';
666
			else
667
			{
668
				if (!isset($txt['mysql_error_space']))
669
					loadLanguage('Errors');
670
671
				$query_error .= !isset($txt['mysql_error_space']) ? ' - check database storage space.' : $txt['mysql_error_space'];
672
			}
673
		}
674
	}
675
676
	// Nothing's defined yet... just die with it.
677
	if (empty($context) || empty($txt))
678
		die($query_error);
679
680
	// Show an error message, if possible.
681
	$context['error_title'] = $txt['database_error'];
682
	if (allowedTo('admin_forum'))
683
		$context['error_message'] = nl2br($query_error) . '<br>' . $txt['file'] . ': ' . $file . '<br>' . $txt['line'] . ': ' . $line;
684
	else
685
		$context['error_message'] = $txt['try_again'];
686
687
	if (allowedTo('admin_forum') && isset($db_show_debug) && $db_show_debug === true)
688
	{
689
		$context['error_message'] .= '<br><br>' . nl2br($db_string);
690
	}
691
692
	// It's already been logged... don't log it again.
693
	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...
694
}
695
696
/**
697
 * Inserts data into a table
698
 *
699
 * @param string $method The insert method - can be 'replace', 'ignore' or 'insert'
700
 * @param string $table The table we're inserting the data into
701
 * @param array $columns An array of the columns we're inserting the data into. Should contain 'column' => 'datatype' pairs
702
 * @param array $data The data to insert
703
 * @param array $keys The keys for the table
704
 * @param bool $disable_trans Whether to disable transactions
705
 * @param resource $connection The connection to use (if null, $db_connection is used)
706
 */
707 View Code Duplication
function smf_db_insert($method = 'replace', $table, $columns, $data, $keys, $disable_trans = false, $connection = null)
0 ignored issues
show
Unused Code introduced by
The parameter $keys 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 $disable_trans 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...
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...
708
{
709
	global $smcFunc, $db_connection, $db_prefix;
710
711
	$connection = $connection === null ? $db_connection : $connection;
712
713
	// With nothing to insert, simply return.
714
	if (empty($data))
715
		return;
716
717
	// Replace the prefix holder with the actual prefix.
718
	$table = str_replace('{db_prefix}', $db_prefix, $table);
719
720
	// Inserting data as a single row can be done as a single array.
721
	if (!is_array($data[array_rand($data)]))
722
		$data = array($data);
723
724
	// Create the mold for a single row insert.
725
	$insertData = '(';
726
	foreach ($columns as $columnName => $type)
727
	{
728
		// Are we restricting the length?
729
		if (strpos($type, 'string-') !== false)
730
			$insertData .= sprintf('SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName);
731
		else
732
			$insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName);
733
	}
734
	$insertData = substr($insertData, 0, -2) . ')';
735
736
	// Create an array consisting of only the columns.
737
	$indexed_columns = array_keys($columns);
738
739
	// Here's where the variables are injected to the query.
740
	$insertRows = array();
741
	foreach ($data as $dataRow)
742
		$insertRows[] = smf_db_quote($insertData, array_combine($indexed_columns, $dataRow), $connection);
743
744
	// Determine the method of insertion.
745
	$queryTitle = $method == 'replace' ? 'REPLACE' : ($method == 'ignore' ? 'INSERT IGNORE' : 'INSERT');
746
747
	// Do the insert.
748
	$smcFunc['db_query']('', '
749
		' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`)
750
		VALUES
751
			' . implode(',
752
			', $insertRows),
753
		array(
754
			'security_override' => true,
755
			'db_error_skip' => $table === $db_prefix . 'log_errors',
756
		),
757
		$connection
758
	);
759
}
760
761
/**
762
 * This function tries to work out additional error information from a back trace.
763
 *
764
 * @param string $error_message The error message
765
 * @param string $log_message The message to log
766
 * @param string|bool $error_type What type of error this is
767
 * @param string $file The file the error occurred in
768
 * @param int $line What line of $file the code which generated the error is on
769
 * @return void|array Returns an array with the file and line if $error_type is 'return'
770
 */
771 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...
772
{
773
	if (empty($log_message))
774
		$log_message = $error_message;
775
776
	foreach (debug_backtrace() as $step)
777
	{
778
		// Found it?
779
		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)
780
		{
781
			$log_message .= '<br>Function: ' . $step['function'];
782
			break;
783
		}
784
785
		if (isset($step['line']))
786
		{
787
			$file = $step['file'];
788
			$line = $step['line'];
789
		}
790
	}
791
792
	// A special case - we want the file and line numbers for debugging.
793
	if ($error_type == 'return')
794
		return array($file, $line);
795
796
	// Is always a critical error.
797
	if (function_exists('log_error'))
798
		log_error($log_message, 'critical', $file, $line);
799
800
	if (function_exists('fatal_error'))
801
	{
802
		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...
803
804
		// Cannot continue...
805
		exit;
806
	}
807
	elseif ($error_type)
808
		trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''), $error_type);
809
	else
810
		trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''));
811
}
812
813
/**
814
 * Escape the LIKE wildcards so that they match the character and not the wildcard.
815
 *
816
 * @param string $string The string to escape
817
 * @param bool $translate_human_wildcards If true, turns human readable wildcards into SQL wildcards.
818
 * @return string The escaped string
819
 */
820 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...
821
{
822
	$replacements = array(
823
		'%' => '\%',
824
		'_' => '\_',
825
		'\\' => '\\\\',
826
	);
827
828
	if ($translate_human_wildcards)
829
		$replacements += array(
830
			'*' => '%',
831
		);
832
833
	return strtr($string, $replacements);
834
}
835
836
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...