Completed
Pull Request — patch_1-1-4 (#3202)
by Spuds
15:49
created

Database_MySQL::db()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 2
cts 2
cp 1
crap 1
1
<?php
2
3
/**
4
 * This file has all the main functions in it that relate to the mysql database.
5
 *
6
 * @name      ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
9
 *
10
 * This file contains code covered by:
11
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
12
 * license:  	BSD, See included LICENSE.TXT for terms and conditions.
13
 *
14
 * copyright:	2004-2011, GreyWyvern - All rights reserved.
15
 * license:  	BSD, See included LICENSE.TXT for terms and conditions.
16
 *
17
 * @version 1.1.2
18
 *
19
 */
20
21
// Let's define the name of the class so that we will be able to use it in the instantiations
22
if (!defined('DB_TYPE'))
23
	define('DB_TYPE', 'MySQL');
24
25
/**
26
 * SQL database class, implements database class to control mysql functions
27
 */
28
class Database_MySQL extends Database_Abstract
29
{
30
	/**
31
	 * Holds current instance of the class
32
	 *
33
	 * @var Database_MySQL
34
	 */
35
	private static $_db = null;
36
37
	/**
38
	 * Initializes a database connection.
39
	 * It returns the connection, if successful.
40
	 *
41
	 * @param string $db_server
42
	 * @param string $db_name
43
	 * @param string $db_user
44
	 * @param string $db_passwd
45
	 * @param string $db_prefix
46
	 * @param mixed[] $db_options
47
	 *
48
	 * @return mysqli|null
49
	 * @throws Elk_Exception
50
	 */
51
	public static function 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...
52
	{
53
		global $mysql_set_mode;
54
55
		// Initialize the instance... if not done already!
56
		if (self::$_db === null)
57
			self::$_db = new self();
58
59
		// Non-standard port
60 View Code Duplication
		if (!empty($db_options['port']))
61
			$db_port = (int) $db_options['port'];
62
		else
63
			$db_port = 0;
64
65
		// Select the database. Maybe.
66
		if (empty($db_options['dont_select_db']))
67
			$connection = @mysqli_connect((!empty($db_options['persist']) ? 'p:' : '') . $db_server, $db_user, $db_passwd, $db_name, $db_port);
68
		else
69
			$connection = @mysqli_connect((!empty($db_options['persist']) ? 'p:' : '') . $db_server, $db_user, $db_passwd, '', $db_port);
70
71
		// Something's wrong, show an error if its fatal (which we assume it is)
72 View Code Duplication
		if (!$connection)
73
		{
74
			if (!empty($db_options['non_fatal']))
75
				return null;
76
			else
77
				Errors::instance()->display_db_error();
78
		}
79
80
		self::$_db->_connection = $connection;
81
82
		// This makes it possible to automatically change the sql_mode and autocommit if needed.
83
		if (isset($mysql_set_mode) && $mysql_set_mode === true)
84
			self::$_db->query('', 'SET sql_mode = \'\', AUTOCOMMIT = 1',
85
			array(),
86
			false
87
		);
88
89
		// Few databases still have not set UTF-8 as their default input charset
90
		self::$_db->query('', '
91
			SET NAMES UTF8',
92
			array(
93
			)
94
		);
95
96
		return $connection;
97
	}
98
99
	/**
100
	 * Fix up the prefix so it doesn't require the database to be selected.
101
	 *
102
	 * @param string $db_prefix
103
	 * @param string $db_name
104
	 *
105
	 * @return string
106
	 */
107
	public function fix_prefix($db_prefix, $db_name)
108
	{
109
		$db_prefix = is_numeric(substr($db_prefix, 0, 1)) ? $db_name . '.' . $db_prefix : '`' . $db_name . '`.' . $db_prefix;
110
111
		return $db_prefix;
112
	}
113
114
	/**
115
	 * Do a query.  Takes care of errors too.
116
	 *
117
	 * @param string $identifier
118
	 * @param string $db_string
119
	 * @param mixed[]|false $db_values = array()
120
	 * @param mysqli_result|false|null $connection = null
121
	 * @throws Elk_Exception
122
	 */
123 39
	public function query($identifier, $db_string, $db_values = array(), $connection = null)
124
	{
125 39
		global $db_show_debug, $time_start, $modSettings;
126
127
		// Comments that are allowed in a query are preg_removed.
128
		static $allowed_comments_from = array(
129
			'~\s+~s',
130
			'~/\*!40001 SQL_NO_CACHE \*/~',
131
			'~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~',
132
			'~/\*!40100 ON DUPLICATE KEY UPDATE id_msg = \d+ \*/~',
133 39
		);
134
		static $allowed_comments_to = array(
135
			' ',
136
			'',
137
			'',
138
			'',
139 39
		);
140
141
		// Decide which connection to use.
142 39
		$connection = $connection === null ? $this->_connection : $connection;
143
144
		// One more query....
145 39
		$this->_query_count++;
146
147 39 View Code Duplication
		if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override']))
148 39
			$this->error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__);
149
150
		// Use "ORDER BY null" to prevent Mysql doing filesorts for Group By clauses without an Order By
151 39
		if (strpos($db_string, 'GROUP BY') !== false && strpos($db_string, 'ORDER BY') === false && strpos($db_string, 'INSERT INTO') === false)
152 39
		{
153
			// Add before LIMIT
154 7
			if ($pos = strpos($db_string, 'LIMIT '))
155 7
				$db_string = substr($db_string, 0, $pos) . "\t\t\tORDER BY null\n" . substr($db_string, $pos, strlen($db_string));
156
			else
157
				// Append it.
158 6
				$db_string .= "\n\t\t\tORDER BY null";
159 7
		}
160
161 39 View Code Duplication
		if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false))
162 39
		{
163
			// Store these values for use in the callback function.
164 39
			$this->_db_callback_values = $db_values;
0 ignored issues
show
Documentation Bug introduced by
It seems like $db_values can also be of type false. However, the property $_db_callback_values is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
165 39
			$this->_db_callback_connection = $connection;
0 ignored issues
show
Documentation Bug introduced by
It seems like $connection can also be of type false. However, the property $_db_callback_connection is declared as type resource|object. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
166
167
			// Inject the values passed to this function.
168 39
			$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', array($this, 'replacement__callback'), $db_string);
169
170
			// No need for them any longer.
171 39
			$this->_db_callback_values = array();
172 39
			$this->_db_callback_connection = null;
173 39
		}
174
175
		// Debugging.
176 39 View Code Duplication
		if ($db_show_debug === true)
177 39
		{
178
			$debug = Debug::instance();
179
180
			// Get the file and line number this function was called.
181
			list ($file, $line) = $this->error_backtrace('', '', 'return', __FILE__, __LINE__);
182
183
			if (!empty($_SESSION['debug_redirect']))
184
			{
185
				$debug->merge_db($_SESSION['debug_redirect']);
186
				// @todo this may be off by 1
187
				$this->_query_count += count($_SESSION['debug_redirect']);
188
				$_SESSION['debug_redirect'] = array();
189
			}
190
191
			// Don't overload it.
192
			$st = microtime(true);
193
			$db_cache = array();
194
			$db_cache['q'] = $this->_query_count < 50 ? $db_string : '...';
195
			$db_cache['f'] = $file;
196
			$db_cache['l'] = $line;
197
			$db_cache['s'] = $st - $time_start;
198
		}
199
200
		// First, we clean strings out of the query, reduce whitespace, lowercase, and trim - so we can check it over.
201 39
		if (empty($modSettings['disableQueryCheck']))
202 39
		{
203 39
			$clean = '';
204 39
			$old_pos = 0;
205 39
			$pos = -1;
206 39 View Code Duplication
			while (true)
207
			{
208 39
				$pos = strpos($db_string, '\'', $pos + 1);
209 39
				if ($pos === false)
210 39
					break;
211 33
				$clean .= substr($db_string, $old_pos, $pos - $old_pos);
212
213 33
				while (true)
214
				{
215 33
					$pos1 = strpos($db_string, '\'', $pos + 1);
216 33
					$pos2 = strpos($db_string, '\\', $pos + 1);
217 33
					if ($pos1 === false)
218 33
						break;
219 33
					elseif ($pos2 === false || $pos2 > $pos1)
220
					{
221 33
						$pos = $pos1;
222 33
						break;
223
					}
224
225 3
					$pos = $pos2 + 1;
226 3
				}
227
228 33
				$clean .= ' %s ';
229 33
				$old_pos = $pos + 1;
230 33
			}
231
232 39
			$clean .= substr($db_string, $old_pos);
233 39
			$clean = trim(strtolower(preg_replace($allowed_comments_from, $allowed_comments_to, $clean)));
234
235
			// Comments?  We don't use comments in our queries, we leave 'em outside!
236 39 View Code Duplication
			if (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false)
237 39
				$fail = true;
238
			// Trying to change passwords, slow us down, or something?
239 39
			elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[_a-z])~s', $clean) != 0)
240
				$fail = true;
241 39
			elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
242
				$fail = true;
243
244 39
			if (!empty($fail) && function_exists('log_error'))
245 39
				$this->error_backtrace('Hacking attempt...', 'Hacking attempt...' . "\n" . $db_string, E_USER_ERROR, __FILE__, __LINE__);
246 39
		}
247
248 39
		if ($this->_unbuffered === false)
249 39
			$ret = @mysqli_query($connection, $db_string);
0 ignored issues
show
Security SQL Injection introduced by
$db_string can contain request data and is used in sql context(s) leading to a potential security vulnerability.

13 paths for user data to reach this point

  1. Path: Read from $_POST, and $user_info is assigned in sources/controllers/Post.controller.php on line 924
  1. Read from $_POST, and $user_info is assigned
    in sources/controllers/Post.controller.php on line 924
  2. $msgOptions is assigned
    in sources/controllers/Post.controller.php on line 1032
  3. $msgOptions is passed to modifyPost()
    in sources/controllers/Post.controller.php on line 1039
  4. $messages_columns is assigned
    in sources/subs/Post.subs.php on line 446
  5. $messages_columns is passed through implode()
    in sources/subs/Post.subs.php on line 486
  6. ``' UPDATE {db_prefix}messages SET ' . implode(', ', $messages_columns) . ' WHERE id_msg = {int:id_msg}'`` is passed to Database_MySQL::query()
    in sources/subs/Post.subs.php on line 484
  2. Path: Read from $_REQUEST, and $_REQUEST['params'] is passed to Search::searchParamsFromString() in sources/controllers/Search.controller.php on line 166
  1. Read from $_REQUEST, and $_REQUEST['params'] is passed to Search::searchParamsFromString()
    in sources/controllers/Search.controller.php on line 166
  2. $string is passed through str_replace(), and str_replace(array('-', '_', '.'), array('+', '/', '='), $string) is decoded by base64_decode(), and $temp_params is assigned
    in sources/subs/Search/Search.php on line 647
  3. !empty($temp_params2) ? $temp_params2 : $temp_params is passed through explode(), and $temp_params is assigned
    in sources/subs/Search/Search.php on line 651
  4. $data is assigned
    in sources/subs/Search/Search.php on line 653
  5. $data is passed through explode(), and explode('|\'|', $data) is passed through array_pad(), and $v is assigned
    in sources/subs/Search/Search.php on line 655
  6. Search::$_search_params is assigned
    in sources/subs/Search/Search.php on line 656
  7. Tainted property Search::$_search_params is read, and $this->_search_params['brd'] is passed through implode(), and Search::$_boardQuery is assigned
    in sources/subs/Search/Search.php on line 912
  8. Tainted property Search::$_boardQuery is read, and $subject_query is assigned
    in sources/subs/Search/Search.php on line 1678
  9. ``($this->_db->support_ignore() ? ' INSERT IGNORE INTO {db_prefix}' . ($this->_createTemporary ? 'tmp_' : '') . 'log_search_topics (' . ($this->_createTemporary ? '' : 'id_search, ') . 'id_topic)' : '') . ' SELECT ' . ($this->_createTemporary ? '' : $id_search . ', ') . 't.id_topic FROM ' . $subject_query['from'] . (empty($subject_query['inner_join']) ? '' : ' INNER JOIN ' . implode(' INNER JOIN ', array_unique($subject_query['inner_join']))) . (empty($subject_query['left_join']) ? '' : ' LEFT JOIN ' . implode(' LEFT JOIN ', array_unique($subject_query['left_join']))) . ' WHERE ' . implode(' AND ', array_unique($subject_query['where'])) . (empty($modSettings['search_max_results']) ? '' : ' LIMIT ' . ($modSettings['search_max_results'] - $numSubjectResults))`` is passed to DbSearch_MySQL::search_query()
    in sources/subs/Search/Search.php on line 1701
  10. $db_string is passed to Database_MySQL::query()
    in sources/database/DbSearch-mysql.php on line 50
  3. Path: Read from $_REQUEST, and $_REQUEST['params'] is passed to Search::searchParamsFromString() in sources/controllers/Search.controller.php on line 302
  1. Read from $_REQUEST, and $_REQUEST['params'] is passed to Search::searchParamsFromString()
    in sources/controllers/Search.controller.php on line 302
  2. $string is passed through str_replace(), and str_replace(array('-', '_', '.'), array('+', '/', '='), $string) is decoded by base64_decode(), and $temp_params is assigned
    in sources/subs/Search/Search.php on line 647
  3. !empty($temp_params2) ? $temp_params2 : $temp_params is passed through explode(), and $temp_params is assigned
    in sources/subs/Search/Search.php on line 651
  4. $data is assigned
    in sources/subs/Search/Search.php on line 653
  5. $data is passed through explode(), and explode('|\'|', $data) is passed through array_pad(), and $v is assigned
    in sources/subs/Search/Search.php on line 655
  6. Search::$_search_params is assigned
    in sources/subs/Search/Search.php on line 656
  7. Tainted property Search::$_search_params is read, and $this->_search_params['brd'] is passed through implode(), and Search::$_boardQuery is assigned
    in sources/subs/Search/Search.php on line 912
  8. Tainted property Search::$_boardQuery is read, and $subject_query is assigned
    in sources/subs/Search/Search.php on line 1678
  9. ``($this->_db->support_ignore() ? ' INSERT IGNORE INTO {db_prefix}' . ($this->_createTemporary ? 'tmp_' : '') . 'log_search_topics (' . ($this->_createTemporary ? '' : 'id_search, ') . 'id_topic)' : '') . ' SELECT ' . ($this->_createTemporary ? '' : $id_search . ', ') . 't.id_topic FROM ' . $subject_query['from'] . (empty($subject_query['inner_join']) ? '' : ' INNER JOIN ' . implode(' INNER JOIN ', array_unique($subject_query['inner_join']))) . (empty($subject_query['left_join']) ? '' : ' LEFT JOIN ' . implode(' LEFT JOIN ', array_unique($subject_query['left_join']))) . ' WHERE ' . implode(' AND ', array_unique($subject_query['where'])) . (empty($modSettings['search_max_results']) ? '' : ' LIMIT ' . ($modSettings['search_max_results'] - $numSubjectResults))`` is passed to DbSearch_MySQL::search_query()
    in sources/subs/Search/Search.php on line 1701
  10. $db_string is passed to Database_MySQL::query()
    in sources/database/DbSearch-mysql.php on line 50
  4. Path: Read from $_REQUEST, and $_REQUEST is passed to Search::mergeSearchParams() in sources/controllers/Search.controller.php on line 304
  1. Read from $_REQUEST, and $_REQUEST is passed to Search::mergeSearchParams()
    in sources/controllers/Search.controller.php on line 304
  2. Search::$_search_params is assigned
    in sources/subs/Search/Search.php on line 742
  3. Tainted property Search::$_search_params is read, and $this->_search_params['brd'] is passed through implode(), and Search::$_boardQuery is assigned
    in sources/subs/Search/Search.php on line 912
  4. Tainted property Search::$_boardQuery is read, and $subject_query is assigned
    in sources/subs/Search/Search.php on line 1678
  5. ``($this->_db->support_ignore() ? ' INSERT IGNORE INTO {db_prefix}' . ($this->_createTemporary ? 'tmp_' : '') . 'log_search_topics (' . ($this->_createTemporary ? '' : 'id_search, ') . 'id_topic)' : '') . ' SELECT ' . ($this->_createTemporary ? '' : $id_search . ', ') . 't.id_topic FROM ' . $subject_query['from'] . (empty($subject_query['inner_join']) ? '' : ' INNER JOIN ' . implode(' INNER JOIN ', array_unique($subject_query['inner_join']))) . (empty($subject_query['left_join']) ? '' : ' LEFT JOIN ' . implode(' LEFT JOIN ', array_unique($subject_query['left_join']))) . ' WHERE ' . implode(' AND ', array_unique($subject_query['where'])) . (empty($modSettings['search_max_results']) ? '' : ' LIMIT ' . ($modSettings['search_max_results'] - $numSubjectResults))`` is passed to DbSearch_MySQL::search_query()
    in sources/subs/Search/Search.php on line 1701
  6. $db_string is passed to Database_MySQL::query()
    in sources/database/DbSearch-mysql.php on line 50
  5. Path: Read from $_GET in sources/subs/Search/Search.php on line 966
  1. Read from $_GET
    in sources/subs/Search/Search.php on line 966
  2. Data is passed through htmlspecialchars_decode()
    in vendor/sources/Subs.php on line 569
  3. Data is passed through str_replace()
    in vendor/sources/Subs.php on line 570
  4. Search::$_search_params is assigned
    in sources/subs/Search/Search.php on line 966
  5. Tainted property Search::$_search_params is read, and $subject_query is assigned
    in sources/subs/Search/Search.php on line 1176
  6. $subject_query is assigned
    in sources/subs/Search/Search.php on line 1207
  7. $subject_query is assigned
    in sources/subs/Search/Search.php on line 1215
  8. $subject_query is passed to Search::_build_search_results_log()
    in sources/subs/Search/Search.php on line 1226
  9. ``($this->_db->support_ignore() ? ' INSERT IGNORE INTO {db_prefix}log_search_results (' . implode(', ', array_keys($main_query['select'])) . ')' : '') . ' SELECT ' . implode(', ', $main_query['select']) . ' FROM ' . $main_query['from'] . (!empty($main_query['inner_join']) ? ' INNER JOIN ' . implode(' INNER JOIN ', array_unique($main_query['inner_join'])) : '') . (!empty($main_query['left_join']) ? ' LEFT JOIN ' . implode(' LEFT JOIN ', array_unique($main_query['left_join'])) : '') . (!empty($main_query['where']) ? ' WHERE ' : '') . implode(' AND ', array_unique($main_query['where'])) . (!empty($main_query['group_by']) ? ' GROUP BY ' . implode(', ', array_unique($main_query['group_by'])) : '') . (!empty($main_query['parameters']['limit']) ? ' LIMIT {int:limit}' : '')`` is passed to DbSearch_MySQL::search_query()
    in sources/subs/Search/Search.php on line 1925
  10. $db_string is passed to Database_MySQL::query()
    in sources/database/DbSearch-mysql.php on line 50
  6. Path: Read from $_POST, and Search::$_search_params is assigned in sources/subs/Search/Search.php on line 970
  1. Read from $_POST, and Search::$_search_params is assigned
    in sources/subs/Search/Search.php on line 970
  2. Tainted property Search::$_search_params is read, and $subject_query is assigned
    in sources/subs/Search/Search.php on line 1176
  3. $subject_query is assigned
    in sources/subs/Search/Search.php on line 1207
  4. $subject_query is assigned
    in sources/subs/Search/Search.php on line 1215
  5. $subject_query is passed to Search::_build_search_results_log()
    in sources/subs/Search/Search.php on line 1226
  6. ``($this->_db->support_ignore() ? ' INSERT IGNORE INTO {db_prefix}log_search_results (' . implode(', ', array_keys($main_query['select'])) . ')' : '') . ' SELECT ' . implode(', ', $main_query['select']) . ' FROM ' . $main_query['from'] . (!empty($main_query['inner_join']) ? ' INNER JOIN ' . implode(' INNER JOIN ', array_unique($main_query['inner_join'])) : '') . (!empty($main_query['left_join']) ? ' LEFT JOIN ' . implode(' LEFT JOIN ', array_unique($main_query['left_join'])) : '') . (!empty($main_query['where']) ? ' WHERE ' : '') . implode(' AND ', array_unique($main_query['where'])) . (!empty($main_query['group_by']) ? ' GROUP BY ' . implode(', ', array_unique($main_query['group_by'])) : '') . (!empty($main_query['parameters']['limit']) ? ' LIMIT {int:limit}' : '')`` is passed to DbSearch_MySQL::search_query()
    in sources/subs/Search/Search.php on line 1925
  7. $db_string is passed to Database_MySQL::query()
    in sources/database/DbSearch-mysql.php on line 50
  7. Path: Read from $_POST, and $_POST is passed to Data_Validator::is_valid() in sources/controllers/Post.controller.php on line 883
  1. Read from $_POST, and $_POST is passed to Data_Validator::is_valid()
    in sources/controllers/Post.controller.php on line 883
  2. $data is passed to Data_Validator::validate()
    in sources/subs/DataValidator.class.php on line 147
  3. Data_Validator::$_data is assigned
    in sources/subs/DataValidator.class.php on line 249
  4. Tainted property Data_Validator::$_data is read
    in sources/subs/DataValidator.class.php on line 281
  5. Data_Validator::validation_data() returns tainted data
    in sources/subs/HttpReq.class.php on line 359
  6. HttpReq::cleanValue() returns tainted data, and HttpReq::$_param is assigned
    in sources/subs/HttpReq.class.php on line 219
  7. Tainted property HttpReq::$_param is read
    in sources/subs/HttpReq.class.php on line 278
  8. HttpReq::getQuery() returns tainted data, and $sort is assigned
    in sources/controllers/Memberlist.controller.php on line 407
  9. $field is assigned
    in sources/controllers/Memberlist.controller.php on line 501
  10. $customJoin is assigned
    in sources/controllers/Memberlist.controller.php on line 505
  11. $customJoin is passed through array_unique(), and array_unique($customJoin) is passed to ml_searchMembers()
    in sources/controllers/Memberlist.controller.php on line 518
  12. $customJoin is passed through implode()
    in sources/subs/Memberlist.subs.php on line 237
  13. ``' SELECT COUNT(*) FROM {db_prefix}members AS mem LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:regular_id_group} THEN mem.id_post_group ELSE mem.id_group END) ' . (empty($customJoin) ? '' : implode(' ', $customJoin)) . ' WHERE (' . $where . ') AND mem.is_activated = {int:is_activated}'`` is passed to Database_MySQL::query()
    in sources/subs/Memberlist.subs.php on line 232
  8. Path: Fetching key HTTP_CLIENT_IP from $_SERVER, and $_SERVER['HTTP_CLIENT_IP'] is passed through explode(), and explode('.', $_SERVER['HTTP_CLIENT_IP']) is passed through array_reverse(), and array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP'])) is passed through implode(), and Request::$_ban_ip is assigned in sources/Request.php on line 207
  1. Fetching key HTTP_CLIENT_IP from $_SERVER, and $_SERVER['HTTP_CLIENT_IP'] is passed through explode(), and explode('.', $_SERVER['HTTP_CLIENT_IP']) is passed through array_reverse(), and array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP'])) is passed through implode(), and Request::$_ban_ip is assigned
    in sources/Request.php on line 207
  2. Tainted property Request::$_ban_ip is read
    in sources/Request.php on line 100
  3. Request::ban_ip() returns tainted data, and $regOptions is assigned
    in sources/controllers/Register.controller.php on line 519
  4. $regOptions is passed to registerMember()
    in sources/controllers/Register.controller.php on line 520
  5. $regOptions is assigned
    in sources/subs/Members.subs.php on line 502
  6. $regOptions is assigned
    in sources/subs/Members.subs.php on line 586
  7. $regOptions is assigned
    in sources/subs/Members.subs.php on line 628
  8. $var is assigned
    in sources/subs/Members.subs.php on line 677
  9. $column_names is assigned
    in sources/subs/Members.subs.php on line 687
  10. $column_names is passed to Database_MySQL::insert()
    in sources/subs/Members.subs.php on line 694
  11. $columns is passed through array_keys(), and $indexed_columns is assigned
    in sources/database/Db-mysql.class.php on line 729
  12. $indexed_columns is passed through implode()
    in sources/database/Db-mysql.class.php on line 745
  13. ``' ' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`) VALUES ' . implode(', ', $insertRows)`` is passed to Database_MySQL::query()
    in sources/database/Db-mysql.class.php on line 744
  9. Path: Fetching key HTTP_CLIENT_IP from $_SERVER, and Request::$_ban_ip is assigned in sources/Request.php on line 209
  1. Fetching key HTTP_CLIENT_IP from $_SERVER, and Request::$_ban_ip is assigned
    in sources/Request.php on line 209
  2. Tainted property Request::$_ban_ip is read
    in sources/Request.php on line 100
  3. Request::ban_ip() returns tainted data, and $regOptions is assigned
    in sources/controllers/Register.controller.php on line 519
  4. $regOptions is passed to registerMember()
    in sources/controllers/Register.controller.php on line 520
  5. $regOptions is assigned
    in sources/subs/Members.subs.php on line 502
  6. $regOptions is assigned
    in sources/subs/Members.subs.php on line 586
  7. $regOptions is assigned
    in sources/subs/Members.subs.php on line 628
  8. $var is assigned
    in sources/subs/Members.subs.php on line 677
  9. $column_names is assigned
    in sources/subs/Members.subs.php on line 687
  10. $column_names is passed to Database_MySQL::insert()
    in sources/subs/Members.subs.php on line 694
  11. $columns is passed through array_keys(), and $indexed_columns is assigned
    in sources/database/Db-mysql.class.php on line 729
  12. $indexed_columns is passed through implode()
    in sources/database/Db-mysql.class.php on line 745
  13. ``' ' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`) VALUES ' . implode(', ', $insertRows)`` is passed to Database_MySQL::query()
    in sources/database/Db-mysql.class.php on line 744
  10. Path: Fetching key HTTP_CLIENT_IP from $_SERVER, and $_SERVER['HTTP_CLIENT_IP'] is passed through explode(), and explode('.', $_SERVER['HTTP_CLIENT_IP']) is passed through array_reverse(), and array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP'])) is passed through implode(), and Request::$_ban_ip is assigned in sources/Request.php on line 216
  1. Fetching key HTTP_CLIENT_IP from $_SERVER, and $_SERVER['HTTP_CLIENT_IP'] is passed through explode(), and explode('.', $_SERVER['HTTP_CLIENT_IP']) is passed through array_reverse(), and array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP'])) is passed through implode(), and Request::$_ban_ip is assigned
    in sources/Request.php on line 216
  2. Tainted property Request::$_ban_ip is read
    in sources/Request.php on line 100
  3. Request::ban_ip() returns tainted data, and $regOptions is assigned
    in sources/controllers/Register.controller.php on line 519
  4. $regOptions is passed to registerMember()
    in sources/controllers/Register.controller.php on line 520
  5. $regOptions is assigned
    in sources/subs/Members.subs.php on line 502
  6. $regOptions is assigned
    in sources/subs/Members.subs.php on line 586
  7. $regOptions is assigned
    in sources/subs/Members.subs.php on line 628
  8. $var is assigned
    in sources/subs/Members.subs.php on line 677
  9. $column_names is assigned
    in sources/subs/Members.subs.php on line 687
  10. $column_names is passed to Database_MySQL::insert()
    in sources/subs/Members.subs.php on line 694
  11. $columns is passed through array_keys(), and $indexed_columns is assigned
    in sources/database/Db-mysql.class.php on line 729
  12. $indexed_columns is passed through implode()
    in sources/database/Db-mysql.class.php on line 745
  13. ``' ' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`) VALUES ' . implode(', ', $insertRows)`` is passed to Database_MySQL::query()
    in sources/database/Db-mysql.class.php on line 744
  11. Path: Fetching key HTTP_CLIENT_IP from $_SERVER, and Request::$_ban_ip is assigned in sources/Request.php on line 218
  1. Fetching key HTTP_CLIENT_IP from $_SERVER, and Request::$_ban_ip is assigned
    in sources/Request.php on line 218
  2. Tainted property Request::$_ban_ip is read
    in sources/Request.php on line 100
  3. Request::ban_ip() returns tainted data, and $regOptions is assigned
    in sources/controllers/Register.controller.php on line 519
  4. $regOptions is passed to registerMember()
    in sources/controllers/Register.controller.php on line 520
  5. $regOptions is assigned
    in sources/subs/Members.subs.php on line 502
  6. $regOptions is assigned
    in sources/subs/Members.subs.php on line 586
  7. $regOptions is assigned
    in sources/subs/Members.subs.php on line 628
  8. $var is assigned
    in sources/subs/Members.subs.php on line 677
  9. $column_names is assigned
    in sources/subs/Members.subs.php on line 687
  10. $column_names is passed to Database_MySQL::insert()
    in sources/subs/Members.subs.php on line 694
  11. $columns is passed through array_keys(), and $indexed_columns is assigned
    in sources/database/Db-mysql.class.php on line 729
  12. $indexed_columns is passed through implode()
    in sources/database/Db-mysql.class.php on line 745
  13. ``' ' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`) VALUES ' . implode(', ', $insertRows)`` is passed to Database_MySQL::query()
    in sources/database/Db-mysql.class.php on line 744
  12. Path: Fetching key HTTP_X_FORWARDED_FOR from $_SERVER, and $_SERVER['HTTP_X_FORWARDED_FOR'] is passed through explode(), and explode(', ', $_SERVER['HTTP_X_FORWARDED_FOR']) is passed through array_reverse(), and $ips is assigned in sources/Request.php on line 225
  1. Fetching key HTTP_X_FORWARDED_FOR from $_SERVER, and $_SERVER['HTTP_X_FORWARDED_FOR'] is passed through explode(), and explode(', ', $_SERVER['HTTP_X_FORWARDED_FOR']) is passed through array_reverse(), and $ips is assigned
    in sources/Request.php on line 225
  2. $ip is assigned
    in sources/Request.php on line 228
  3. $ip is passed through trim(), and Request::$_ban_ip is assigned
    in sources/Request.php on line 235
  4. Tainted property Request::$_ban_ip is read
    in sources/Request.php on line 100
  5. Request::ban_ip() returns tainted data, and $regOptions is assigned
    in sources/controllers/Register.controller.php on line 519
  6. $regOptions is passed to registerMember()
    in sources/controllers/Register.controller.php on line 520
  7. $regOptions is assigned
    in sources/subs/Members.subs.php on line 502
  8. $regOptions is assigned
    in sources/subs/Members.subs.php on line 586
  9. $regOptions is assigned
    in sources/subs/Members.subs.php on line 628
  10. $var is assigned
    in sources/subs/Members.subs.php on line 677
  11. $column_names is assigned
    in sources/subs/Members.subs.php on line 687
  12. $column_names is passed to Database_MySQL::insert()
    in sources/subs/Members.subs.php on line 694
  13. $columns is passed through array_keys(), and $indexed_columns is assigned
    in sources/database/Db-mysql.class.php on line 729
  14. $indexed_columns is passed through implode()
    in sources/database/Db-mysql.class.php on line 745
  15. ``' ' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`) VALUES ' . implode(', ', $insertRows)`` is passed to Database_MySQL::query()
    in sources/database/Db-mysql.class.php on line 744
  13. Path: Fetching key HTTP_X_FORWARDED_FOR from $_SERVER, and Request::$_ban_ip is assigned in sources/Request.php on line 241
  1. Fetching key HTTP_X_FORWARDED_FOR from $_SERVER, and Request::$_ban_ip is assigned
    in sources/Request.php on line 241
  2. Tainted property Request::$_ban_ip is read
    in sources/Request.php on line 100
  3. Request::ban_ip() returns tainted data, and $regOptions is assigned
    in sources/controllers/Register.controller.php on line 519
  4. $regOptions is passed to registerMember()
    in sources/controllers/Register.controller.php on line 520
  5. $regOptions is assigned
    in sources/subs/Members.subs.php on line 502
  6. $regOptions is assigned
    in sources/subs/Members.subs.php on line 586
  7. $regOptions is assigned
    in sources/subs/Members.subs.php on line 628
  8. $var is assigned
    in sources/subs/Members.subs.php on line 677
  9. $column_names is assigned
    in sources/subs/Members.subs.php on line 687
  10. $column_names is passed to Database_MySQL::insert()
    in sources/subs/Members.subs.php on line 694
  11. $columns is passed through array_keys(), and $indexed_columns is assigned
    in sources/database/Db-mysql.class.php on line 729
  12. $indexed_columns is passed through implode()
    in sources/database/Db-mysql.class.php on line 745
  13. ``' ' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`) VALUES ' . implode(', ', $insertRows)`` is passed to Database_MySQL::query()
    in sources/database/Db-mysql.class.php on line 744

Preventing SQL Injection

There are two options to prevent SQL injection. Generally, it is recommended to use parameter binding:

$stmt = mysqli_prepare("SELECT * FROM users WHERE name = ?");
$stmt->bind_param("s", $taintedUserName);

An alternative – although generally not recommended – is to escape your data manually:

$mysqli = new mysqli('localhost', 'user', 'pass', 'dbname');

$escaped = $mysqli->real_escape_string($taintedUserName);
$mysqli->query("SELECT * FROM users WHERE name = '".$escaped."'");

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
250
		else
251
			$ret = @mysqli_query($connection, $db_string, MYSQLI_USE_RESULT);
0 ignored issues
show
Security SQL Injection introduced by
$db_string can contain request data and is used in sql context(s) leading to a potential security vulnerability.

13 paths for user data to reach this point

  1. Path: Read from $_POST, and $user_info is assigned in sources/controllers/Post.controller.php on line 924
  1. Read from $_POST, and $user_info is assigned
    in sources/controllers/Post.controller.php on line 924
  2. $msgOptions is assigned
    in sources/controllers/Post.controller.php on line 1032
  3. $msgOptions is passed to modifyPost()
    in sources/controllers/Post.controller.php on line 1039
  4. $messages_columns is assigned
    in sources/subs/Post.subs.php on line 446
  5. $messages_columns is passed through implode()
    in sources/subs/Post.subs.php on line 486
  6. ``' UPDATE {db_prefix}messages SET ' . implode(', ', $messages_columns) . ' WHERE id_msg = {int:id_msg}'`` is passed to Database_MySQL::query()
    in sources/subs/Post.subs.php on line 484
  2. Path: Read from $_REQUEST, and $_REQUEST['params'] is passed to Search::searchParamsFromString() in sources/controllers/Search.controller.php on line 166
  1. Read from $_REQUEST, and $_REQUEST['params'] is passed to Search::searchParamsFromString()
    in sources/controllers/Search.controller.php on line 166
  2. $string is passed through str_replace(), and str_replace(array('-', '_', '.'), array('+', '/', '='), $string) is decoded by base64_decode(), and $temp_params is assigned
    in sources/subs/Search/Search.php on line 647
  3. !empty($temp_params2) ? $temp_params2 : $temp_params is passed through explode(), and $temp_params is assigned
    in sources/subs/Search/Search.php on line 651
  4. $data is assigned
    in sources/subs/Search/Search.php on line 653
  5. $data is passed through explode(), and explode('|\'|', $data) is passed through array_pad(), and $v is assigned
    in sources/subs/Search/Search.php on line 655
  6. Search::$_search_params is assigned
    in sources/subs/Search/Search.php on line 656
  7. Tainted property Search::$_search_params is read, and $this->_search_params['brd'] is passed through implode(), and Search::$_boardQuery is assigned
    in sources/subs/Search/Search.php on line 912
  8. Tainted property Search::$_boardQuery is read, and $subject_query is assigned
    in sources/subs/Search/Search.php on line 1678
  9. ``($this->_db->support_ignore() ? ' INSERT IGNORE INTO {db_prefix}' . ($this->_createTemporary ? 'tmp_' : '') . 'log_search_topics (' . ($this->_createTemporary ? '' : 'id_search, ') . 'id_topic)' : '') . ' SELECT ' . ($this->_createTemporary ? '' : $id_search . ', ') . 't.id_topic FROM ' . $subject_query['from'] . (empty($subject_query['inner_join']) ? '' : ' INNER JOIN ' . implode(' INNER JOIN ', array_unique($subject_query['inner_join']))) . (empty($subject_query['left_join']) ? '' : ' LEFT JOIN ' . implode(' LEFT JOIN ', array_unique($subject_query['left_join']))) . ' WHERE ' . implode(' AND ', array_unique($subject_query['where'])) . (empty($modSettings['search_max_results']) ? '' : ' LIMIT ' . ($modSettings['search_max_results'] - $numSubjectResults))`` is passed to DbSearch_MySQL::search_query()
    in sources/subs/Search/Search.php on line 1701
  10. $db_string is passed to Database_MySQL::query()
    in sources/database/DbSearch-mysql.php on line 50
  3. Path: Read from $_REQUEST, and $_REQUEST['params'] is passed to Search::searchParamsFromString() in sources/controllers/Search.controller.php on line 302
  1. Read from $_REQUEST, and $_REQUEST['params'] is passed to Search::searchParamsFromString()
    in sources/controllers/Search.controller.php on line 302
  2. $string is passed through str_replace(), and str_replace(array('-', '_', '.'), array('+', '/', '='), $string) is decoded by base64_decode(), and $temp_params is assigned
    in sources/subs/Search/Search.php on line 647
  3. !empty($temp_params2) ? $temp_params2 : $temp_params is passed through explode(), and $temp_params is assigned
    in sources/subs/Search/Search.php on line 651
  4. $data is assigned
    in sources/subs/Search/Search.php on line 653
  5. $data is passed through explode(), and explode('|\'|', $data) is passed through array_pad(), and $v is assigned
    in sources/subs/Search/Search.php on line 655
  6. Search::$_search_params is assigned
    in sources/subs/Search/Search.php on line 656
  7. Tainted property Search::$_search_params is read, and $this->_search_params['brd'] is passed through implode(), and Search::$_boardQuery is assigned
    in sources/subs/Search/Search.php on line 912
  8. Tainted property Search::$_boardQuery is read, and $subject_query is assigned
    in sources/subs/Search/Search.php on line 1678
  9. ``($this->_db->support_ignore() ? ' INSERT IGNORE INTO {db_prefix}' . ($this->_createTemporary ? 'tmp_' : '') . 'log_search_topics (' . ($this->_createTemporary ? '' : 'id_search, ') . 'id_topic)' : '') . ' SELECT ' . ($this->_createTemporary ? '' : $id_search . ', ') . 't.id_topic FROM ' . $subject_query['from'] . (empty($subject_query['inner_join']) ? '' : ' INNER JOIN ' . implode(' INNER JOIN ', array_unique($subject_query['inner_join']))) . (empty($subject_query['left_join']) ? '' : ' LEFT JOIN ' . implode(' LEFT JOIN ', array_unique($subject_query['left_join']))) . ' WHERE ' . implode(' AND ', array_unique($subject_query['where'])) . (empty($modSettings['search_max_results']) ? '' : ' LIMIT ' . ($modSettings['search_max_results'] - $numSubjectResults))`` is passed to DbSearch_MySQL::search_query()
    in sources/subs/Search/Search.php on line 1701
  10. $db_string is passed to Database_MySQL::query()
    in sources/database/DbSearch-mysql.php on line 50
  4. Path: Read from $_REQUEST, and $_REQUEST is passed to Search::mergeSearchParams() in sources/controllers/Search.controller.php on line 304
  1. Read from $_REQUEST, and $_REQUEST is passed to Search::mergeSearchParams()
    in sources/controllers/Search.controller.php on line 304
  2. Search::$_search_params is assigned
    in sources/subs/Search/Search.php on line 742
  3. Tainted property Search::$_search_params is read, and $this->_search_params['brd'] is passed through implode(), and Search::$_boardQuery is assigned
    in sources/subs/Search/Search.php on line 912
  4. Tainted property Search::$_boardQuery is read, and $subject_query is assigned
    in sources/subs/Search/Search.php on line 1678
  5. ``($this->_db->support_ignore() ? ' INSERT IGNORE INTO {db_prefix}' . ($this->_createTemporary ? 'tmp_' : '') . 'log_search_topics (' . ($this->_createTemporary ? '' : 'id_search, ') . 'id_topic)' : '') . ' SELECT ' . ($this->_createTemporary ? '' : $id_search . ', ') . 't.id_topic FROM ' . $subject_query['from'] . (empty($subject_query['inner_join']) ? '' : ' INNER JOIN ' . implode(' INNER JOIN ', array_unique($subject_query['inner_join']))) . (empty($subject_query['left_join']) ? '' : ' LEFT JOIN ' . implode(' LEFT JOIN ', array_unique($subject_query['left_join']))) . ' WHERE ' . implode(' AND ', array_unique($subject_query['where'])) . (empty($modSettings['search_max_results']) ? '' : ' LIMIT ' . ($modSettings['search_max_results'] - $numSubjectResults))`` is passed to DbSearch_MySQL::search_query()
    in sources/subs/Search/Search.php on line 1701
  6. $db_string is passed to Database_MySQL::query()
    in sources/database/DbSearch-mysql.php on line 50
  5. Path: Read from $_GET in sources/subs/Search/Search.php on line 966
  1. Read from $_GET
    in sources/subs/Search/Search.php on line 966
  2. Data is passed through htmlspecialchars_decode()
    in vendor/sources/Subs.php on line 569
  3. Data is passed through str_replace()
    in vendor/sources/Subs.php on line 570
  4. Search::$_search_params is assigned
    in sources/subs/Search/Search.php on line 966
  5. Tainted property Search::$_search_params is read, and $subject_query is assigned
    in sources/subs/Search/Search.php on line 1176
  6. $subject_query is assigned
    in sources/subs/Search/Search.php on line 1207
  7. $subject_query is assigned
    in sources/subs/Search/Search.php on line 1215
  8. $subject_query is passed to Search::_build_search_results_log()
    in sources/subs/Search/Search.php on line 1226
  9. ``($this->_db->support_ignore() ? ' INSERT IGNORE INTO {db_prefix}log_search_results (' . implode(', ', array_keys($main_query['select'])) . ')' : '') . ' SELECT ' . implode(', ', $main_query['select']) . ' FROM ' . $main_query['from'] . (!empty($main_query['inner_join']) ? ' INNER JOIN ' . implode(' INNER JOIN ', array_unique($main_query['inner_join'])) : '') . (!empty($main_query['left_join']) ? ' LEFT JOIN ' . implode(' LEFT JOIN ', array_unique($main_query['left_join'])) : '') . (!empty($main_query['where']) ? ' WHERE ' : '') . implode(' AND ', array_unique($main_query['where'])) . (!empty($main_query['group_by']) ? ' GROUP BY ' . implode(', ', array_unique($main_query['group_by'])) : '') . (!empty($main_query['parameters']['limit']) ? ' LIMIT {int:limit}' : '')`` is passed to DbSearch_MySQL::search_query()
    in sources/subs/Search/Search.php on line 1925
  10. $db_string is passed to Database_MySQL::query()
    in sources/database/DbSearch-mysql.php on line 50
  6. Path: Read from $_POST, and Search::$_search_params is assigned in sources/subs/Search/Search.php on line 970
  1. Read from $_POST, and Search::$_search_params is assigned
    in sources/subs/Search/Search.php on line 970
  2. Tainted property Search::$_search_params is read, and $subject_query is assigned
    in sources/subs/Search/Search.php on line 1176
  3. $subject_query is assigned
    in sources/subs/Search/Search.php on line 1207
  4. $subject_query is assigned
    in sources/subs/Search/Search.php on line 1215
  5. $subject_query is passed to Search::_build_search_results_log()
    in sources/subs/Search/Search.php on line 1226
  6. ``($this->_db->support_ignore() ? ' INSERT IGNORE INTO {db_prefix}log_search_results (' . implode(', ', array_keys($main_query['select'])) . ')' : '') . ' SELECT ' . implode(', ', $main_query['select']) . ' FROM ' . $main_query['from'] . (!empty($main_query['inner_join']) ? ' INNER JOIN ' . implode(' INNER JOIN ', array_unique($main_query['inner_join'])) : '') . (!empty($main_query['left_join']) ? ' LEFT JOIN ' . implode(' LEFT JOIN ', array_unique($main_query['left_join'])) : '') . (!empty($main_query['where']) ? ' WHERE ' : '') . implode(' AND ', array_unique($main_query['where'])) . (!empty($main_query['group_by']) ? ' GROUP BY ' . implode(', ', array_unique($main_query['group_by'])) : '') . (!empty($main_query['parameters']['limit']) ? ' LIMIT {int:limit}' : '')`` is passed to DbSearch_MySQL::search_query()
    in sources/subs/Search/Search.php on line 1925
  7. $db_string is passed to Database_MySQL::query()
    in sources/database/DbSearch-mysql.php on line 50
  7. Path: Read from $_POST, and $_POST is passed to Data_Validator::is_valid() in sources/controllers/Post.controller.php on line 883
  1. Read from $_POST, and $_POST is passed to Data_Validator::is_valid()
    in sources/controllers/Post.controller.php on line 883
  2. $data is passed to Data_Validator::validate()
    in sources/subs/DataValidator.class.php on line 147
  3. Data_Validator::$_data is assigned
    in sources/subs/DataValidator.class.php on line 249
  4. Tainted property Data_Validator::$_data is read
    in sources/subs/DataValidator.class.php on line 281
  5. Data_Validator::validation_data() returns tainted data
    in sources/subs/HttpReq.class.php on line 359
  6. HttpReq::cleanValue() returns tainted data, and HttpReq::$_param is assigned
    in sources/subs/HttpReq.class.php on line 219
  7. Tainted property HttpReq::$_param is read
    in sources/subs/HttpReq.class.php on line 278
  8. HttpReq::getQuery() returns tainted data, and $sort is assigned
    in sources/controllers/Memberlist.controller.php on line 407
  9. $field is assigned
    in sources/controllers/Memberlist.controller.php on line 501
  10. $customJoin is assigned
    in sources/controllers/Memberlist.controller.php on line 505
  11. $customJoin is passed through array_unique(), and array_unique($customJoin) is passed to ml_searchMembers()
    in sources/controllers/Memberlist.controller.php on line 518
  12. $customJoin is passed through implode()
    in sources/subs/Memberlist.subs.php on line 237
  13. ``' SELECT COUNT(*) FROM {db_prefix}members AS mem LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:regular_id_group} THEN mem.id_post_group ELSE mem.id_group END) ' . (empty($customJoin) ? '' : implode(' ', $customJoin)) . ' WHERE (' . $where . ') AND mem.is_activated = {int:is_activated}'`` is passed to Database_MySQL::query()
    in sources/subs/Memberlist.subs.php on line 232
  8. Path: Fetching key HTTP_CLIENT_IP from $_SERVER, and $_SERVER['HTTP_CLIENT_IP'] is passed through explode(), and explode('.', $_SERVER['HTTP_CLIENT_IP']) is passed through array_reverse(), and array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP'])) is passed through implode(), and Request::$_ban_ip is assigned in sources/Request.php on line 207
  1. Fetching key HTTP_CLIENT_IP from $_SERVER, and $_SERVER['HTTP_CLIENT_IP'] is passed through explode(), and explode('.', $_SERVER['HTTP_CLIENT_IP']) is passed through array_reverse(), and array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP'])) is passed through implode(), and Request::$_ban_ip is assigned
    in sources/Request.php on line 207
  2. Tainted property Request::$_ban_ip is read
    in sources/Request.php on line 100
  3. Request::ban_ip() returns tainted data, and $regOptions is assigned
    in sources/controllers/Register.controller.php on line 519
  4. $regOptions is passed to registerMember()
    in sources/controllers/Register.controller.php on line 520
  5. $regOptions is assigned
    in sources/subs/Members.subs.php on line 502
  6. $regOptions is assigned
    in sources/subs/Members.subs.php on line 586
  7. $regOptions is assigned
    in sources/subs/Members.subs.php on line 628
  8. $var is assigned
    in sources/subs/Members.subs.php on line 677
  9. $column_names is assigned
    in sources/subs/Members.subs.php on line 687
  10. $column_names is passed to Database_MySQL::insert()
    in sources/subs/Members.subs.php on line 694
  11. $columns is passed through array_keys(), and $indexed_columns is assigned
    in sources/database/Db-mysql.class.php on line 729
  12. $indexed_columns is passed through implode()
    in sources/database/Db-mysql.class.php on line 745
  13. ``' ' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`) VALUES ' . implode(', ', $insertRows)`` is passed to Database_MySQL::query()
    in sources/database/Db-mysql.class.php on line 744
  9. Path: Fetching key HTTP_CLIENT_IP from $_SERVER, and Request::$_ban_ip is assigned in sources/Request.php on line 209
  1. Fetching key HTTP_CLIENT_IP from $_SERVER, and Request::$_ban_ip is assigned
    in sources/Request.php on line 209
  2. Tainted property Request::$_ban_ip is read
    in sources/Request.php on line 100
  3. Request::ban_ip() returns tainted data, and $regOptions is assigned
    in sources/controllers/Register.controller.php on line 519
  4. $regOptions is passed to registerMember()
    in sources/controllers/Register.controller.php on line 520
  5. $regOptions is assigned
    in sources/subs/Members.subs.php on line 502
  6. $regOptions is assigned
    in sources/subs/Members.subs.php on line 586
  7. $regOptions is assigned
    in sources/subs/Members.subs.php on line 628
  8. $var is assigned
    in sources/subs/Members.subs.php on line 677
  9. $column_names is assigned
    in sources/subs/Members.subs.php on line 687
  10. $column_names is passed to Database_MySQL::insert()
    in sources/subs/Members.subs.php on line 694
  11. $columns is passed through array_keys(), and $indexed_columns is assigned
    in sources/database/Db-mysql.class.php on line 729
  12. $indexed_columns is passed through implode()
    in sources/database/Db-mysql.class.php on line 745
  13. ``' ' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`) VALUES ' . implode(', ', $insertRows)`` is passed to Database_MySQL::query()
    in sources/database/Db-mysql.class.php on line 744
  10. Path: Fetching key HTTP_CLIENT_IP from $_SERVER, and $_SERVER['HTTP_CLIENT_IP'] is passed through explode(), and explode('.', $_SERVER['HTTP_CLIENT_IP']) is passed through array_reverse(), and array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP'])) is passed through implode(), and Request::$_ban_ip is assigned in sources/Request.php on line 216
  1. Fetching key HTTP_CLIENT_IP from $_SERVER, and $_SERVER['HTTP_CLIENT_IP'] is passed through explode(), and explode('.', $_SERVER['HTTP_CLIENT_IP']) is passed through array_reverse(), and array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP'])) is passed through implode(), and Request::$_ban_ip is assigned
    in sources/Request.php on line 216
  2. Tainted property Request::$_ban_ip is read
    in sources/Request.php on line 100
  3. Request::ban_ip() returns tainted data, and $regOptions is assigned
    in sources/controllers/Register.controller.php on line 519
  4. $regOptions is passed to registerMember()
    in sources/controllers/Register.controller.php on line 520
  5. $regOptions is assigned
    in sources/subs/Members.subs.php on line 502
  6. $regOptions is assigned
    in sources/subs/Members.subs.php on line 586
  7. $regOptions is assigned
    in sources/subs/Members.subs.php on line 628
  8. $var is assigned
    in sources/subs/Members.subs.php on line 677
  9. $column_names is assigned
    in sources/subs/Members.subs.php on line 687
  10. $column_names is passed to Database_MySQL::insert()
    in sources/subs/Members.subs.php on line 694
  11. $columns is passed through array_keys(), and $indexed_columns is assigned
    in sources/database/Db-mysql.class.php on line 729
  12. $indexed_columns is passed through implode()
    in sources/database/Db-mysql.class.php on line 745
  13. ``' ' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`) VALUES ' . implode(', ', $insertRows)`` is passed to Database_MySQL::query()
    in sources/database/Db-mysql.class.php on line 744
  11. Path: Fetching key HTTP_CLIENT_IP from $_SERVER, and Request::$_ban_ip is assigned in sources/Request.php on line 218
  1. Fetching key HTTP_CLIENT_IP from $_SERVER, and Request::$_ban_ip is assigned
    in sources/Request.php on line 218
  2. Tainted property Request::$_ban_ip is read
    in sources/Request.php on line 100
  3. Request::ban_ip() returns tainted data, and $regOptions is assigned
    in sources/controllers/Register.controller.php on line 519
  4. $regOptions is passed to registerMember()
    in sources/controllers/Register.controller.php on line 520
  5. $regOptions is assigned
    in sources/subs/Members.subs.php on line 502
  6. $regOptions is assigned
    in sources/subs/Members.subs.php on line 586
  7. $regOptions is assigned
    in sources/subs/Members.subs.php on line 628
  8. $var is assigned
    in sources/subs/Members.subs.php on line 677
  9. $column_names is assigned
    in sources/subs/Members.subs.php on line 687
  10. $column_names is passed to Database_MySQL::insert()
    in sources/subs/Members.subs.php on line 694
  11. $columns is passed through array_keys(), and $indexed_columns is assigned
    in sources/database/Db-mysql.class.php on line 729
  12. $indexed_columns is passed through implode()
    in sources/database/Db-mysql.class.php on line 745
  13. ``' ' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`) VALUES ' . implode(', ', $insertRows)`` is passed to Database_MySQL::query()
    in sources/database/Db-mysql.class.php on line 744
  12. Path: Fetching key HTTP_X_FORWARDED_FOR from $_SERVER, and $_SERVER['HTTP_X_FORWARDED_FOR'] is passed through explode(), and explode(', ', $_SERVER['HTTP_X_FORWARDED_FOR']) is passed through array_reverse(), and $ips is assigned in sources/Request.php on line 225
  1. Fetching key HTTP_X_FORWARDED_FOR from $_SERVER, and $_SERVER['HTTP_X_FORWARDED_FOR'] is passed through explode(), and explode(', ', $_SERVER['HTTP_X_FORWARDED_FOR']) is passed through array_reverse(), and $ips is assigned
    in sources/Request.php on line 225
  2. $ip is assigned
    in sources/Request.php on line 228
  3. $ip is passed through trim(), and Request::$_ban_ip is assigned
    in sources/Request.php on line 235
  4. Tainted property Request::$_ban_ip is read
    in sources/Request.php on line 100
  5. Request::ban_ip() returns tainted data, and $regOptions is assigned
    in sources/controllers/Register.controller.php on line 519
  6. $regOptions is passed to registerMember()
    in sources/controllers/Register.controller.php on line 520
  7. $regOptions is assigned
    in sources/subs/Members.subs.php on line 502
  8. $regOptions is assigned
    in sources/subs/Members.subs.php on line 586
  9. $regOptions is assigned
    in sources/subs/Members.subs.php on line 628
  10. $var is assigned
    in sources/subs/Members.subs.php on line 677
  11. $column_names is assigned
    in sources/subs/Members.subs.php on line 687
  12. $column_names is passed to Database_MySQL::insert()
    in sources/subs/Members.subs.php on line 694
  13. $columns is passed through array_keys(), and $indexed_columns is assigned
    in sources/database/Db-mysql.class.php on line 729
  14. $indexed_columns is passed through implode()
    in sources/database/Db-mysql.class.php on line 745
  15. ``' ' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`) VALUES ' . implode(', ', $insertRows)`` is passed to Database_MySQL::query()
    in sources/database/Db-mysql.class.php on line 744
  13. Path: Fetching key HTTP_X_FORWARDED_FOR from $_SERVER, and Request::$_ban_ip is assigned in sources/Request.php on line 241
  1. Fetching key HTTP_X_FORWARDED_FOR from $_SERVER, and Request::$_ban_ip is assigned
    in sources/Request.php on line 241
  2. Tainted property Request::$_ban_ip is read
    in sources/Request.php on line 100
  3. Request::ban_ip() returns tainted data, and $regOptions is assigned
    in sources/controllers/Register.controller.php on line 519
  4. $regOptions is passed to registerMember()
    in sources/controllers/Register.controller.php on line 520
  5. $regOptions is assigned
    in sources/subs/Members.subs.php on line 502
  6. $regOptions is assigned
    in sources/subs/Members.subs.php on line 586
  7. $regOptions is assigned
    in sources/subs/Members.subs.php on line 628
  8. $var is assigned
    in sources/subs/Members.subs.php on line 677
  9. $column_names is assigned
    in sources/subs/Members.subs.php on line 687
  10. $column_names is passed to Database_MySQL::insert()
    in sources/subs/Members.subs.php on line 694
  11. $columns is passed through array_keys(), and $indexed_columns is assigned
    in sources/database/Db-mysql.class.php on line 729
  12. $indexed_columns is passed through implode()
    in sources/database/Db-mysql.class.php on line 745
  13. ``' ' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`) VALUES ' . implode(', ', $insertRows)`` is passed to Database_MySQL::query()
    in sources/database/Db-mysql.class.php on line 744

Preventing SQL Injection

There are two options to prevent SQL injection. Generally, it is recommended to use parameter binding:

$stmt = mysqli_prepare("SELECT * FROM users WHERE name = ?");
$stmt->bind_param("s", $taintedUserName);

An alternative – although generally not recommended – is to escape your data manually:

$mysqli = new mysqli('localhost', 'user', 'pass', 'dbname');

$escaped = $mysqli->real_escape_string($taintedUserName);
$mysqli->query("SELECT * FROM users WHERE name = '".$escaped."'");

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
252
253
		// @deprecated since 1.1 - use skip_next_error method
254 39
		if (!empty($db_values['db_error_skip']))
255 39
		{
256 11
			$this->_skip_error = true;
257 11
		}
258
259 39
		if ($ret === false && $this->_skip_error === false)
260 39
		{
261
			$ret = $this->error($db_string, $connection);
0 ignored issues
show
Bug introduced by
It seems like $connection defined by $connection === null ? $...onnection : $connection on line 142 can also be of type false or object<mysqli_result> or resource; however, Database_MySQL::error() does only seem to accept object<mysqli>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
262
		}
263
264
		// Revert not to skip errors
265 39
		if ($this->_skip_error === true)
266 39
		{
267 11
			$this->_skip_error = false;
268 11
		}
269
270
		// Debugging.
271 39 View Code Duplication
		if ($db_show_debug === true)
272 39
		{
273
			$db_cache['t'] = microtime(true) - $st;
0 ignored issues
show
Bug introduced by
The variable $db_cache 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...
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...
274
			$debug->db_query($db_cache);
0 ignored issues
show
Bug introduced by
The variable $debug 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...
275
		}
276
277 39
		return $ret;
278
	}
279
280
	/**
281
	 * Checks if the string contains any 4byte chars and if so,
282
	 * converts them into HTML entities.
283
	 *
284
	 * This is necessary because MySQL utf8 doesn't know how to store such
285
	 * characters and would generate an error any time one is used.
286
	 * The 4byte chars are used by emoji
287
	 *
288
	 * @param string $string
289
	 * @return string
290
	 */
291 33
	private function _clean_4byte_chars($string)
292
	{
293 33
		global $modSettings;
294
295 33
		if (!empty($modSettings['using_utf8mb4']))
296 33
			return $string;
297
298 33
		$result = $string;
299 33
		$ord = array_map('ord', str_split($string));
300
301
		// If we are in the 4-byte range
302 33
		if (max($ord) >= 240)
303 33
		{
304
			// Byte length
305
			$length = strlen($string);
306
			$result = '';
307
308
			// Look for a 4byte marker
309
			for ($i = 0; $i < $length; $i++)
310
			{
311
				// The first byte of a 4-byte character encoding starts with the bytes 0xF0-0xF4 (240 <-> 244)
312
				// but look all the way to 247 for safe measure
313
				$ord1 = $ord[$i];
314
				if ($ord1 >= 240 && $ord1 <= 247)
315
				{
316
					// Replace it with the corresponding html entity
317
					$entity = $this->_uniord(chr($ord[$i]) . chr($ord[$i + 1]) . chr($ord[$i + 2]) . chr($ord[$i + 3]));
318
					if ($entity === false)
319
						$result .= "\xEF\xBF\xBD";
320
					else
321
						$result .= '&#x' . dechex($entity) . ';';
322
					$i += 3;
323
				}
324
				else
325
					$result .= $string[$i];
326
			}
327
		}
328
329 33
		return $result;
330
	}
331
332
	/**
333
	 * Converts a 4byte char into the corresponding HTML entity code.
334
	 *
335
	 * This function is derived from:
336
	 * http://www.greywyvern.com/code/php/utf8_html.phps
337
	 *
338
	 * @param string $c
339
	 * @return integer|false
340
	 */
341
	private function _uniord($c)
342
	{
343 View Code Duplication
		if (ord($c[0]) >= 0 && ord($c[0]) <= 127)
344
			return ord($c[0]);
345
		if (ord($c[0]) >= 192 && ord($c[0]) <= 223)
346
			return (ord($c[0]) - 192) * 64 + (ord($c[1]) - 128);
347
		if (ord($c[0]) >= 224 && ord($c[0]) <= 239)
348
			return (ord($c[0]) - 224) * 4096 + (ord($c[1]) - 128) * 64 + (ord($c[2]) - 128);
349
		if (ord($c[0]) >= 240 && ord($c[0]) <= 247)
350
			return (ord($c[0]) - 240) * 262144 + (ord($c[1]) - 128) * 4096 + (ord($c[2]) - 128) * 64 + (ord($c[3]) - 128);
351
		if (ord($c[0]) >= 248 && ord($c[0]) <= 251)
352
			return (ord($c[0]) - 248) * 16777216 + (ord($c[1]) - 128) * 262144 + (ord($c[2]) - 128) * 4096 + (ord($c[3]) - 128) * 64 + (ord($c[4]) - 128);
353
		if (ord($c[0]) >= 252 && ord($c[0]) <= 253)
354
			return (ord($c[0]) - 252) * 1073741824 + (ord($c[1]) - 128) * 16777216 + (ord($c[2]) - 128) * 262144 + (ord($c[3]) - 128) * 4096 + (ord($c[4]) - 128) * 64 + (ord($c[5]) - 128);
355 View Code Duplication
		if (ord($c[0]) >= 254 && ord($c[0]) <= 255)
356
			return false;
357
	}
358
359
	/**
360
	 * Affected rows from previous operation.
361
	 *
362
	 * @param mysqli|null $connection
363
	 */
364 9
	public function affected_rows($connection = null)
365
	{
366 9
		return mysqli_affected_rows($connection === null ? $this->_connection : $connection);
367
	}
368
369
	/**
370
	 * Last inserted id.
371
	 *
372
	 * @param string $table
373
	 * @param string|null $field = null
374
	 * @param mysqli|null $connection = null
375
	 */
376 15
	public function insert_id($table, $field = null, $connection = null)
377
	{
378
		// MySQL doesn't need the table or field information.
379 15
		return mysqli_insert_id($connection === null ? $this->_connection : $connection);
380
	}
381
382
	/**
383
	 * Fetch a row from the result set given as parameter.
384
	 * MySQL implementation doesn't use $counter parameter.
385
	 *
386
	 * @param mysqli_result $result
387
	 * @param integer|bool $counter = false
388
	 */
389 16
	public function fetch_row($result, $counter = false)
390
	{
391
		// Just delegate to MySQL's function
392 16
		return mysqli_fetch_row($result);
393
	}
394
395
	/**
396
	 * Free the resultset.
397
	 *
398
	 * @param mysqli_result $result
399
	 */
400 37
	public function free_result($result)
401
	{
402
		// Just delegate to MySQL's function
403 37
		mysqli_free_result($result);
404 37
	}
405
406
	/**
407
	 * Get the number of rows in the result.
408
	 *
409
	 * @param mysqli_result $result
410
	 */
411 19
	public function num_rows($result)
412
	{
413
		// Simply delegate to the native function
414 19
		return mysqli_num_rows($result);
415
	}
416
417
	/**
418
	 * Get the number of fields in the result set.
419
	 *
420
	 * @param mysqli_result $request
421
	 */
422
	public function num_fields($request)
423
	{
424
		return mysqli_num_fields($request);
425
	}
426
427
	/**
428
	 * Reset the internal result pointer.
429
	 *
430
	 * @param mysqli_result $request
431
	 * @param integer $counter
432
	 */
433
	public function data_seek($request, $counter)
434
	{
435
		// Delegate to native mysql function
436
		return mysqli_data_seek($request, $counter);
437
	}
438
439
	/**
440
	 * Do a transaction.
441
	 *
442
	 * @param string $type - the step to perform (i.e. 'begin', 'commit', 'rollback')
443
	 * @param mysqli|null $connection = null
444
	 */
445
	public function db_transaction($type = 'commit', $connection = null)
446
	{
447
		// Decide which connection to use
448
		$connection = $connection === null ? $this->_connection : $connection;
449
450
		if ($type == 'begin')
451
			return @mysqli_query($connection, 'BEGIN');
452
		elseif ($type == 'rollback')
453
			return @mysqli_query($connection, 'ROLLBACK');
454
		elseif ($type == 'commit')
455
			return @mysqli_query($connection, 'COMMIT');
456
457
		return false;
458
	}
459
460
	/**
461
	 * Return last error string from the database server
462
	 *
463
	 * @param mysqli|null $connection = null
464
	 */
465
	public function last_error($connection = null)
466
	{
467
		// Decide which connection to use
468
		$connection = $connection === null ? $this->_connection : $connection;
469
470
		if (is_object($connection))
471
			return mysqli_error($connection);
472
	}
473
474
	/**
475
	 * Database error.
476
	 * Backtrace, log, try to fix.
477
	 *
478
	 * @param string      $db_string
479
	 * @param mysqli|null $connection = null
480
	 *
481
	 * @return bool
482
	 * @throws Elk_Exception
483
	 */
484
	public function error($db_string, $connection = null)
485
	{
486
		global $txt, $context, $webmaster_email, $modSettings, $db_persist;
487
		global $db_server, $db_user, $db_passwd, $db_name, $db_show_debug, $ssi_db_user, $ssi_db_passwd;
488
489
		// Get the file and line numbers.
490
		list ($file, $line) = $this->error_backtrace('', '', 'return', __FILE__, __LINE__);
491
492
		// Decide which connection to use
493
		$connection = $connection === null ? $this->_connection : $connection;
494
495
		// This is the error message...
496
		$query_error = mysqli_error($connection);
497
		$query_errno = mysqli_errno($connection);
498
499
		// Error numbers:
500
		//    1016: Can't open file '....MYI'
501
		//    1030: Got error ??? from table handler.
502
		//    1034: Incorrect key file for table.
503
		//    1035: Old key file for table.
504
		//    1142: Command denied
505
		//    1205: Lock wait timeout exceeded.
506
		//    1213: Deadlock found.
507
		//    2006: Server has gone away.
508
		//    2013: Lost connection to server during query.
509
510
		// We cannot do something, try to find out what and act accordingly
511
		if ($query_errno == 1142)
512
		{
513
			$command = substr(trim($db_string), 0, 6);
514
			if ($command === 'DELETE' || $command === 'UPDATE' || $command === 'INSERT')
515
			{
516
				// We can try to ignore it (warning the admin though it's a thing to do)
517
				// and serve the page just SELECTing
518
				$_SESSION['query_command_denied'][$command] = $query_error;
519
520
				// Let the admin know there is a command denied issue
521 View Code Duplication
				if (class_exists('Errors'))
522
				{
523
					Errors::instance()->log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n$db_string" : ''), 'database', $file, $line);
524
				}
525
526
				return false;
527
			}
528
		}
529
530
		// Log the error.
531
		if ($query_errno != 1213 && $query_errno != 1205 && class_exists('Errors'))
532
		{
533
			Errors::instance()->log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n$db_string" : ''), 'database', $file, $line);
534
		}
535
536
		// Database error auto fixing ;).
537
		if (function_exists('Cache::instance()->get') && (!isset($modSettings['autoFixDatabase']) || $modSettings['autoFixDatabase'] == '1'))
538
		{
539
			$db_last_error = db_last_error();
540
541
			// Force caching on, just for the error checking.
542
			$old_cache = isset($modSettings['cache_enable']) ? $modSettings['cache_enable'] : null;
543
			$modSettings['cache_enable'] = '1';
544
545
			if (Cache::instance()->getVar($temp, 'db_last_error', 600))
546
				$db_last_error = max($db_last_error, $temp);
0 ignored issues
show
Bug introduced by
The variable $temp 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...
547
548
			if ($db_last_error < time() - 3600 * 24 * 3)
549
			{
550
				// We know there's a problem... but what?  Try to auto detect.
551
				if ($query_errno == 1030 && strpos($query_error, ' 127 ') !== false)
552
				{
553
					preg_match_all('~(?:[\n\r]|^)[^\']+?(?:FROM|JOIN|UPDATE|TABLE) ((?:[^\n\r(]+?(?:, )?)*)~s', $db_string, $matches);
554
555
					$fix_tables = array();
556
					foreach ($matches[1] as $tables)
557
					{
558
						$tables = array_unique(explode(',', $tables));
559
						foreach ($tables as $table)
560
						{
561
							// Now, it's still theoretically possible this could be an injection.  So backtick it!
562
							if (trim($table) != '')
563
								$fix_tables[] = '`' . strtr(trim($table), array('`' => '')) . '`';
564
						}
565
					}
566
567
					$fix_tables = array_unique($fix_tables);
568
				}
569
				// Table crashed.  Let's try to fix it.
570 View Code Duplication
				elseif ($query_errno == 1016)
571
				{
572
					if (preg_match('~\'([^\.\']+)~', $query_error, $match) != 0)
573
						$fix_tables = array('`' . $match[1] . '`');
574
				}
575
				// Indexes crashed.  Should be easy to fix!
576 View Code Duplication
				elseif ($query_errno == 1034 || $query_errno == 1035)
577
				{
578
					preg_match('~\'([^\']+?)\'~', $query_error, $match);
579
					$fix_tables = array('`' . $match[1] . '`');
580
				}
581
			}
582
583
			// 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...)
584
			if (!empty($fix_tables))
585
			{
586
				// subs/Admin.subs.php for updateDbLastError(), subs/Mail.subs.php for sendmail().
587
				// @todo this should go somewhere else, not into the db-mysql layer I think
588
				require_once(SUBSDIR . '/Admin.subs.php');
589
				require_once(SUBSDIR . '/Mail.subs.php');
590
591
				// Make a note of the REPAIR...
592
				Cache::instance()->put('db_last_error', time(), 600);
593
				if (!Cache::instance()->getVar($temp, 'db_last_error', 600))
594
					updateDbLastError(time());
595
596
				// Attempt to find and repair the broken table.
597
				foreach ($fix_tables as $table)
598
				{
599
					$this->query('', "
600
						REPAIR TABLE $table", false, false);
601
				}
602
603
				// And send off an email!
604
				sendmail($webmaster_email, $txt['database_error'], $txt['tried_to_repair']);
605
606
				$modSettings['cache_enable'] = $old_cache;
607
608
				// Try the query again...?
609
				$ret = $this->query('', $db_string, false, false);
610
				if ($ret !== false)
611
					return $ret;
612
			}
613
			else
614
				$modSettings['cache_enable'] = $old_cache;
615
616
			// Check for the "lost connection" or "deadlock found" errors - and try it just one more time.
617
			if (in_array($query_errno, array(1205, 1213, 2006, 2013)))
618
			{
619
				$new_connection = false;
620
				if (in_array($query_errno, array(2006, 2013)) && $this->_connection == $connection)
621
				{
622
					// Are we in SSI mode?  If so try that username and password first
623
					if (ELK == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
624
						$new_connection = @mysqli_connect((!empty($db_persist) ? 'p:' : '') . $db_server, $ssi_db_user, $ssi_db_passwd, $db_name);
625
626
					// Fall back to the regular username and password if need be
627
					if (!$new_connection)
628
						$new_connection = @mysqli_connect((!empty($db_persist) ? 'p:' : '') . $db_server, $db_user, $db_passwd, $db_name);
629
				}
630
631
				if ($new_connection)
632
				{
633
					$this->_connection = $new_connection;
634
635
					// Try a deadlock more than once more.
636
					for ($n = 0; $n < 4; $n++)
637
					{
638
						$ret = $this->query('', $db_string, false, false);
639
640
						$new_errno = mysqli_errno($new_connection);
641
						if ($ret !== false || in_array($new_errno, array(1205, 1213)))
642
							break;
643
					}
644
645
					// If it failed again, shucks to be you... we're not trying it over and over.
646
					if ($ret !== false)
647
						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...
648
				}
649
			}
650
			// Are they out of space, perhaps?
651
			elseif ($query_errno == 1030 && (strpos($query_error, ' -1 ') !== false || strpos($query_error, ' 28 ') !== false || strpos($query_error, ' 12 ') !== false))
652
			{
653
				if (!isset($txt))
654
					$query_error .= ' - check database storage space.';
655
				else
656
				{
657
					if (!isset($txt['mysql_error_space']))
658
						loadLanguage('Errors');
659
660
					$query_error .= !isset($txt['mysql_error_space']) ? ' - check database storage space.' : $txt['mysql_error_space'];
661
				}
662
			}
663
		}
664
665
		// Nothing's defined yet... just die with it.
666
		if (empty($context) || empty($txt))
667
			die($query_error);
0 ignored issues
show
Coding Style Compatibility introduced by
The method error() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
668
669
		// Show an error message, if possible.
670
		$context['error_title'] = $txt['database_error'];
671 View Code Duplication
		if (allowedTo('admin_forum'))
672
			$context['error_message'] = nl2br($query_error) . '<br />' . $txt['file'] . ': ' . $file . '<br />' . $txt['line'] . ': ' . $line;
673
		else
674
			$context['error_message'] = $txt['try_again'];
675
676
		// Add database version that we know of, for the admin to know. (and ask for support)
677 View Code Duplication
		if (allowedTo('admin_forum'))
678
			$context['error_message'] .= '<br /><br />' . sprintf($txt['database_error_versions'], $modSettings['elkVersion']);
679
680 View Code Duplication
		if (allowedTo('admin_forum') && $db_show_debug === true)
681
			$context['error_message'] .= '<br /><br />' . nl2br($db_string);
682
683
		// It's already been logged... don't log it again.
684
		throw new Elk_Exception($context['error_message'], false);
685
	}
686
687
	/**
688
	 * Insert data.
689
	 *
690
	 * @param string $method - options 'replace', 'ignore', 'insert'
691
	 * @param string $table
692
	 * @param mixed[] $columns
693
	 * @param mixed[] $data
694
	 * @param mixed[] $keys
695
	 * @param bool $disable_trans = false
696
	 * @param mysqli|null $connection = null
697
	 * @throws Elk_Exception
698
	 */
699 24
	public function insert($method = 'replace', $table, $columns, $data, $keys, $disable_trans = false, $connection = null)
700
	{
701 24
		global $db_prefix;
702
703 24
		$connection = $connection === null ? $this->_connection : $connection;
704
705
		// With nothing to insert, simply return.
706 24
		if (empty($data))
707 24
			return;
708
709
		// Inserting data as a single row can be done as a single array.
710 24
		if (!is_array($data[array_rand($data)]))
711 24
			$data = array($data);
712
713
		// Replace the prefix holder with the actual prefix.
714 24
		$table = str_replace('{db_prefix}', $db_prefix, $table);
715
716
		// Create the mold for a single row insert.
717 24
		$insertData = '(';
718 24 View Code Duplication
		foreach ($columns as $columnName => $type)
719
		{
720
			// Are we restricting the length?
721 24
			if (strpos($type, 'string-') !== false)
722 24
				$insertData .= sprintf('SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName);
723
			else
724 20
				$insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName);
725 24
		}
726 24
		$insertData = substr($insertData, 0, -2) . ')';
727
728
		// Create an array consisting of only the columns.
729 24
		$indexed_columns = array_keys($columns);
730
731
		// Here's where the variables are injected to the query.
732 24
		$insertRows = array();
733 24
		foreach ($data as $dataRow)
734
		{
735 24
			$insertRows[] = $this->quote($insertData, $this->_array_combine($indexed_columns, $dataRow), $connection);
0 ignored issues
show
Bug introduced by
It seems like $connection defined by $connection === null ? $...onnection : $connection on line 703 can also be of type resource; however, Database_Abstract::quote() does only seem to accept object<mysqli>|object<postgre>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
736 24
		}
737
738
		// Determine the method of insertion.
739 24
		$queryTitle = $method === 'replace' ? 'REPLACE' : ($method == 'ignore' ? 'INSERT IGNORE' : 'INSERT');
740
741 24
		$skip_error = $table === $db_prefix . 'log_errors';
742 24
		$this->_skip_error = $skip_error;
743
		// Do the insert.
744 24
		$this->query('', '
745 24
			' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`)
746
			VALUES
747 24
				' . implode(',
748 24
				', $insertRows),
749
			array(
750 24
				'security_override' => true,
751 24
			),
752
			$connection
0 ignored issues
show
Bug introduced by
It seems like $connection defined by $connection === null ? $...onnection : $connection on line 703 can also be of type object<mysqli> or resource; however, Database_MySQL::query() does only seem to accept object<mysqli_result>|false|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
753 24
		);
754 24
	}
755
756
	/**
757
	 * Unescape an escaped string!
758
	 *
759
	 * @param string $string
760
	 */
761
	public function unescape_string($string)
762
	{
763
		return stripslashes($string);
764
	}
765
766
	/**
767
	 * Returns whether the database system supports ignore.
768
	 *
769
	 * @return boolean
770
	 */
771
	public function support_ignore()
772
	{
773
		return true;
774
	}
775
776
	/**
777
	 * Gets all the necessary INSERTs for the table named table_name.
778
	 * It goes in 250 row segments.
779
	 *
780
	 * @param string $tableName - the table to create the inserts for.
781
	 * @param bool $new_table
782
	 *
783
	 * @return string the query to insert the data back in, or an empty string if the table was empty.
784
	 * @throws Elk_Exception
785
	 */
786
	public function insert_sql($tableName, $new_table = false)
787
	{
788
		global $db_prefix;
789
790
		static $start = 0, $num_rows, $fields, $limit;
791
792 View Code Duplication
		if ($new_table)
793
		{
794
			$limit = strstr($tableName, 'log_') !== false ? 500 : 250;
795
			$start = 0;
796
		}
797
798
		$tableName = str_replace('{db_prefix}', $db_prefix, $tableName);
799
800
		// This will be handy...
801
		$crlf = "\r\n";
802
803
		$result = $this->query('', '
804
			SELECT /*!40001 SQL_NO_CACHE */ *
805
			FROM `' . $tableName . '`
806
			LIMIT ' . $start . ', ' . $limit,
807
			array(
808
				'security_override' => true,
809
			)
810
		);
811
812
		// The number of rows, just for record keeping and breaking INSERTs up.
813
		$num_rows = $this->num_rows($result);
814
815
		if ($num_rows == 0)
816
			return '';
817
818
		if ($new_table)
819
		{
820
			$fields = array_keys($this->fetch_assoc($result));
821
			$this->data_seek($result, 0);
822
		}
823
824
		// Start it off with the basic INSERT INTO.
825
		$data = 'INSERT INTO `' . $tableName . '`' . $crlf . "\t" . '(`' . implode('`, `', $fields) . '`)' . $crlf . 'VALUES ';
826
827
		// Loop through each row.
828 View Code Duplication
		while ($row = $this->fetch_assoc($result))
829
		{
830
			// Get the fields in this row...
831
			$field_list = array();
832
833
			foreach ($row as $key => $item)
834
			{
835
				// Try to figure out the type of each field. (NULL, number, or 'string'.)
836
				if (!isset($item))
837
					$field_list[] = 'NULL';
838
				elseif (is_numeric($item) && (int) $item == $item)
839
					$field_list[] = $item;
840
				else
841
					$field_list[] = '\'' . $this->escape_string($item) . '\'';
842
			}
843
844
			$data .= '(' . implode(', ', $field_list) . '),' . $crlf . "\t";
845
		}
846
847
		$this->free_result($result);
848
		$data = substr(trim($data), 0, -1) . ';' . $crlf . $crlf;
849
850
		$start += $limit;
851
852
		return $data;
853
	}
854
855
	/**
856
	 * Dumps the schema (CREATE) for a table.
857
	 *
858
	 * @param string $tableName - the table
859
	 *
860
	 * @return string - the CREATE statement as string
861
	 * @throws Elk_Exception
862
	 */
863
	public function db_table_sql($tableName)
864
	{
865
		global $db_prefix;
866
867
		$tableName = str_replace('{db_prefix}', $db_prefix, $tableName);
868
869
		// This will be needed...
870
		$crlf = "\r\n";
871
872
		// Drop it if it exists.
873
		$schema_create = 'DROP TABLE IF EXISTS `' . $tableName . '`;' . $crlf . $crlf;
874
875
		// Start the create table...
876
		$schema_create .= 'CREATE TABLE `' . $tableName . '` (' . $crlf;
877
878
		// Find all the fields.
879
		$result = $this->query('', '
880
			SHOW FIELDS
881
			FROM `{raw:table}`',
882
			array(
883
				'table' => $tableName,
884
			)
885
		);
886
		while ($row = $this->fetch_assoc($result))
887
		{
888
			// Make the CREATE for this column.
889
			$schema_create .= ' `' . $row['Field'] . '` ' . $row['Type'] . ($row['Null'] != 'YES' ? ' NOT NULL' : '');
890
891
			// Add a default...?
892
			if (!empty($row['Default']) || $row['Null'] !== 'YES')
893
			{
894
				// Make a special case of auto-timestamp.
895
				if ($row['Default'] == 'CURRENT_TIMESTAMP')
896
					$schema_create .= ' /*!40102 NOT NULL default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP */';
897
				// Text shouldn't have a default.
898
				elseif ($row['Default'] !== null)
899
				{
900
					// If this field is numeric the default needs no escaping.
901
					$type = strtolower($row['Type']);
902
					$isNumericColumn = strpos($type, 'int') !== false || strpos($type, 'bool') !== false || strpos($type, 'bit') !== false || strpos($type, 'float') !== false || strpos($type, 'double') !== false || strpos($type, 'decimal') !== false;
903
904
					$schema_create .= ' default ' . ($isNumericColumn ? $row['Default'] : '\'' . $this->escape_string($row['Default']) . '\'');
905
				}
906
			}
907
908
			// And now any extra information. (such as auto_increment.)
909
			$schema_create .= ($row['Extra'] != '' ? ' ' . $row['Extra'] : '') . ',' . $crlf;
910
		}
911
		$this->free_result($result);
912
913
		// Take off the last comma.
914
		$schema_create = substr($schema_create, 0, -strlen($crlf) - 1);
915
916
		// Find the keys.
917
		$result = $this->query('', '
918
			SHOW KEYS
919
			FROM `{raw:table}`',
920
			array(
921
				'table' => $tableName,
922
			)
923
		);
924
		$indexes = array();
925
		while ($row = $this->fetch_assoc($result))
926
		{
927
			// Is this a primary key, unique index, or regular index?
928
			$row['Key_name'] = $row['Key_name'] == 'PRIMARY' ? 'PRIMARY KEY' : (empty($row['Non_unique']) ? 'UNIQUE ' : ($row['Comment'] == 'FULLTEXT' || (isset($row['Index_type']) && $row['Index_type'] == 'FULLTEXT') ? 'FULLTEXT ' : 'KEY ')) . '`' . $row['Key_name'] . '`';
929
930
			// Is this the first column in the index?
931
			if (empty($indexes[$row['Key_name']]))
932
				$indexes[$row['Key_name']] = array();
933
934
			// A sub part, like only indexing 15 characters of a varchar.
935
			if (!empty($row['Sub_part']))
936
				$indexes[$row['Key_name']][$row['Seq_in_index']] = '`' . $row['Column_name'] . '`(' . $row['Sub_part'] . ')';
937
			else
938
				$indexes[$row['Key_name']][$row['Seq_in_index']] = '`' . $row['Column_name'] . '`';
939
		}
940
		$this->free_result($result);
941
942
		// Build the CREATEs for the keys.
943
		foreach ($indexes as $keyname => $columns)
944
		{
945
			// Ensure the columns are in proper order.
946
			ksort($columns);
947
948
			$schema_create .= ',' . $crlf . ' ' . $keyname . ' (' . implode(', ', $columns) . ')';
949
		}
950
951
		// Now just get the comment and type... (MyISAM, etc.)
952
		$result = $this->query('', '
953
			SHOW TABLE STATUS
954
			LIKE {string:table}',
955
			array(
956
				'table' => strtr($tableName, array('_' => '\\_', '%' => '\\%')),
957
			)
958
		);
959
		$row = $this->fetch_assoc($result);
960
		$this->free_result($result);
961
962
		// Probably MyISAM.... and it might have a comment.
963
		$schema_create .= $crlf . ') ENGINE=' . (isset($row['Type']) ? $row['Type'] : $row['Engine']) . ($row['Comment'] != '' ? ' COMMENT="' . $row['Comment'] . '"' : '');
964
965
		return $schema_create;
966
	}
967
968
	/**
969
	 * {@inheritdoc}
970
	 */
971 1
	public function db_list_tables($db_name_str = false, $filter = false)
972
	{
973 1
		global $db_name;
974
975 1
		$db_name_str = $db_name_str === false ? $db_name : $db_name_str;
976 1
		$db_name_str = trim($db_name_str);
977 1
		$filter = $filter === false ? '' : ' LIKE \'' . $filter . '\'';
978
979 1
		$request = $this->query('', '
980
			SHOW TABLES
981
			FROM `{raw:db_name_str}`
982 1
			{raw:filter}',
983
			array(
984 1
				'db_name_str' => $db_name_str[0] == '`' ? strtr($db_name_str, array('`' => '')) : $db_name_str,
985 1
				'filter' => $filter,
986
			)
987 1
		);
988 1
		$tables = array();
989 1
		while ($row = $this->fetch_row($request))
990
		{
991 1
			$tables[] = $row[0];
992 1
		}
993 1
		$this->free_result($request);
994
995 1
		return $tables;
996
	}
997
998
	/**
999
	 * Backup $table_name to $backup_table.
1000
	 *
1001
	 * @param string $table_name
1002
	 * @param string $backup_table
1003
	 *
1004
	 * @return resource - the request handle to the table creation query
1005
	 * @throws Elk_Exception
1006
	 */
1007
	public function db_backup_table($table_name, $backup_table)
1008
	{
1009
		global $db_prefix;
1010
1011
		$table = str_replace('{db_prefix}', $db_prefix, $table_name);
1012
1013
		// First, get rid of the old table.
1014
		$db_table = db_table();
1015
		$db_table->db_drop_table($backup_table);
1016
1017
		// Can we do this the quick way?
1018
		$result = $this->query('', '
1019
			CREATE TABLE {raw:backup_table} LIKE {raw:table}',
1020
			array(
1021
				'backup_table' => $backup_table,
1022
				'table' => $table
1023
		));
1024
		// If this failed, we go old school.
1025
		if ($result)
1026
		{
1027
			$request = $this->query('', '
1028
				INSERT INTO {raw:backup_table}
1029
				SELECT *
1030
				FROM {raw:table}',
1031
				array(
1032
					'backup_table' => $backup_table,
1033
					'table' => $table
1034
				));
1035
1036
			// Old school or no school?
1037
			if ($request)
1038
				return $request;
1039
		}
1040
1041
		// At this point, the quick method failed.
1042
		$result = $this->query('', '
1043
			SHOW CREATE TABLE {raw:table}',
1044
			array(
1045
				'table' => $table,
1046
			)
1047
		);
1048
		list (, $create) = $this->fetch_row($result);
1049
		$this->free_result($result);
1050
1051
		$create = preg_split('/[\n\r]/', $create);
1052
1053
		$auto_inc = '';
1054
1055
		// Default engine type.
1056
		$engine = 'MyISAM';
1057
		$charset = '';
1058
		$collate = '';
1059
1060
		foreach ($create as $k => $l)
1061
		{
1062
			// Get the name of the auto_increment column.
1063
			if (strpos($l, 'auto_increment'))
1064
				$auto_inc = trim($l);
1065
1066
			// For the engine type, see if we can work out what it is.
1067
			if (strpos($l, 'ENGINE') !== false || strpos($l, 'TYPE') !== false)
1068
			{
1069
				// Extract the engine type.
1070
				preg_match('~(ENGINE|TYPE)=(\w+)(\sDEFAULT)?(\sCHARSET=(\w+))?(\sCOLLATE=(\w+))?~', $l, $match);
1071
1072
				if (!empty($match[1]))
1073
					$engine = $match[1];
1074
1075
				if (!empty($match[2]))
1076
					$engine = $match[2];
1077
1078
				if (!empty($match[5]))
1079
					$charset = $match[5];
1080
1081
				if (!empty($match[7]))
1082
					$collate = $match[7];
1083
			}
1084
1085
			// Skip everything but keys...
1086
			if (strpos($l, 'KEY') === false)
1087
				unset($create[$k]);
1088
		}
1089
1090
		if (!empty($create))
1091
			$create = '(
1092
				' . implode('
1093
				', $create) . ')';
1094
		else
1095
			$create = '';
1096
1097
		$request = $this->query('', '
1098
			CREATE TABLE {raw:backup_table} {raw:create}
1099
			ENGINE={raw:engine}' . (empty($charset) ? '' : ' CHARACTER SET {raw:charset}' . (empty($collate) ? '' : ' COLLATE {raw:collate}')) . '
1100
			SELECT *
1101
			FROM {raw:table}',
1102
			array(
1103
				'backup_table' => $backup_table,
1104
				'table' => $table,
1105
				'create' => $create,
1106
				'engine' => $engine,
1107
				'charset' => empty($charset) ? '' : $charset,
1108
				'collate' => empty($collate) ? '' : $collate,
1109
			)
1110
		);
1111
1112
		if ($auto_inc != '')
1113
		{
1114
			if (preg_match('~\`(.+?)\`\s~', $auto_inc, $match) != 0 && substr($auto_inc, -1, 1) == ',')
1115
				$auto_inc = substr($auto_inc, 0, -1);
1116
1117
			$this->query('', '
1118
				ALTER TABLE {raw:backup_table}
1119
				CHANGE COLUMN {raw:column_detail} {raw:auto_inc}',
1120
				array(
1121
					'backup_table' => $backup_table,
1122
					'column_detail' => $match[1],
1123
					'auto_inc' => $auto_inc,
1124
				)
1125
			);
1126
		}
1127
1128
		return $request;
1129
	}
1130
1131
	/**
1132
	 * Get the version number.
1133
	 *
1134
	 * @return string - the version
1135
	 * @throws Elk_Exception
1136
	 */
1137 View Code Duplication
	public function db_server_version()
1138
	{
1139
		$request = $this->query('', '
1140
			SELECT VERSION()',
1141
			array(
1142
			)
1143
		);
1144
		list ($ver) = $this->fetch_row($request);
1145
		$this->free_result($request);
1146
1147
		return $ver;
1148
	}
1149
1150
	/**
1151
	 * Get the name (title) of the database system.
1152
	 *
1153
	 * @return string
1154
	 */
1155
	public function db_title()
1156
	{
1157
		return 'MySQL';
1158
	}
1159
1160
	/**
1161
	 * Whether the database system is case sensitive.
1162
	 *
1163
	 * @return false
1164
	 */
1165
	public function db_case_sensitive()
1166
	{
1167
		return false;
1168
	}
1169
1170
	/**
1171
	 * Escape string for the database input
1172
	 *
1173
	 * @param string $string
1174
	 */
1175 33
	public function escape_string($string)
1176
	{
1177 33
		$string = $this->_clean_4byte_chars($string);
1178
1179 33
		return mysqli_real_escape_string($this->_connection, $string);
1180
	}
1181
1182
	/**
1183
	 * Fetch next result as association.
1184
	 * The mysql implementation simply delegates to mysqli_fetch_assoc().
1185
	 * It ignores $counter parameter.
1186
	 *
1187
	 * @param mysqli_result $request
1188
	 * @param int|bool $counter = false
1189
	 */
1190 31
	public function fetch_assoc($request, $counter = false)
1191
	{
1192 31
		return mysqli_fetch_assoc($request);
1193
	}
1194
1195
	/**
1196
	 * Return server info.
1197
	 *
1198
	 * @param mysqli|null $connection
1199
	 *
1200
	 * @return string
1201
	 */
1202
	public function db_server_info($connection = null)
1203
	{
1204
		// Decide which connection to use
1205
		$connection = $connection === null ? $this->_connection : $connection;
1206
1207
		return mysqli_get_server_info($connection);
1208
	}
1209
1210
	/**
1211
	 *  Get the version number.
1212
	 *
1213
	 * @return string - the version
1214
	 * @throws Elk_Exception
1215
	 */
1216 View Code Duplication
	public function db_client_version()
1217
	{
1218
		$request = $this->query('', '
1219
			SELECT VERSION()',
1220
			array(
1221
			)
1222
		);
1223
		list ($ver) = $this->fetch_row($request);
1224
		$this->free_result($request);
1225
1226
		return $ver;
1227
	}
1228
1229
	/**
1230
	 * Select database.
1231
	 *
1232
	 * @param string|null $dbName = null
1233
	 * @param mysqli|null $connection = null
1234
	 */
1235
	public function select_db($dbName = null, $connection = null)
1236
	{
1237
		// Decide which connection to use
1238
		$connection = $connection === null ? $this->_connection : $connection;
1239
1240
		return mysqli_select_db($connection, $dbName);
1241
	}
1242
1243
	/**
1244
	 * Returns a reference to the existing instance
1245
	 */
1246 59
	public static function db()
1247
	{
1248 59
		return self::$_db;
1249
	}
1250
1251
	/**
1252
	 * Finds out if the connection is still valid.
1253
	 *
1254
	 * @param mysqli|null $connection = null
1255
	 */
1256 39
	public function validConnection($connection = null)
1257
	{
1258 39
		return is_object($connection);
1259
	}
1260
}
1261