Completed
Pull Request — development (#3085)
by Stephen
10:43
created

Database_PostgreSQL   D

Complexity

Total Complexity 145

Size/Duplication

Total Lines 1039
Duplicated Lines 16.17 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 168
loc 1039
ccs 0
cts 590
cp 0
rs 4.4304
c 0
b 0
f 0
wmc 145
lcom 1
cbo 6

31 Methods

Rating   Name   Duplication   Size   Complexity  
C insert_sql() 24 70 10
B initiate() 11 29 6
A fix_prefix() 0 4 1
F query() 76 205 38
B affected_rows() 0 9 5
A insert_id() 0 23 3
A fetch_row() 14 14 3
A free_result() 0 5 1
A num_rows() 0 5 1
A num_fields() 0 4 1
A data_seek() 0 8 1
B db_transaction() 0 20 5
A last_error() 0 8 3
D error() 12 40 10
F insert() 8 106 26
A unescape_string() 0 4 1
A support_ignore() 0 4 1
D db_table_sql() 0 99 11
A db_list_tables() 0 20 3
B db_backup_table() 0 32 1
A db_server_version() 0 6 1
A db_title() 0 4 1
A db_case_sensitive() 0 4 1
A _replaceIdentifier() 9 9 2
A escape_string() 0 4 1
A fetch_assoc() 14 14 3
A db_server_info() 0 7 1
A db_client_version() 0 6 1
A select_db() 0 4 1
A db() 0 4 1
A validConnection() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Database_PostgreSQL often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Database_PostgreSQL, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file has all the main functions in it that relate to the Postgre 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
 * @version 1.1
15
 *
16
 */
17
18
// Let's define the name of the class so that we will be able to use it in the instantiations
19
if (!defined('DB_TYPE'))
20
	define('DB_TYPE', 'PostgreSQL');
21
22
/**
23
 * PostgreSQL database class, implements database class to control mysql functions
24
 */
25
class Database_PostgreSQL extends Database_Abstract
26
{
27
	/**
28
	 * Holds current instance of the class
29
	 * @var Database_PostgreSQL
30
	 */
31
	private static $_db = null;
32
33
	/**
34
	 * Holds last query result
35
	 * @var string
36
	 */
37
	private $_db_last_result = null;
38
39
	/**
40
	 * Since PostgreSQL doesn't support INSERT REPLACE we are using this to remember
41
	 * the rows affected by the delete
42
	 * @var int
43
	 */
44
	private $_db_replace_result = null;
45
46
	/**
47
	 * A variable to remember if a transaction was started already or not
48
	 * @var boolean
49
	 */
50
	private $_in_transaction = false;
51
52
	/**
53
	 * Initializes a database connection.
54
	 * It returns the connection, if successful.
55
	 *
56
	 * @param string $db_server
57
	 * @param string $db_name
58
	 * @param string $db_user
59
	 * @param string $db_passwd
60
	 * @param string $db_prefix
61
	 * @param mixed[] $db_options
62
	 *
63
	 * @return resource
64
	 * @throws Elk_Exception
65
	 */
66
	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...
67
	{
68
		// initialize the instance... if not done already!
69
		if (self::$_db === null)
70
			self::$_db = new self();
71
72 View Code Duplication
		if (!empty($db_options['port']))
73
			$db_port = ' port=' . (int) $db_options['port'];
74
		else
75
			$db_port = '';
76
77
		if (!empty($db_options['persist']))
78
			$connection = @pg_pconnect('host=' . $db_server . $db_port . ' dbname=' . $db_name . ' user=\'' . $db_user . '\' password=\'' . $db_passwd . '\'');
79
		else
80
			$connection = @pg_connect('host=' . $db_server . $db_port . ' dbname=' . $db_name . ' user=\'' . $db_user . '\' password=\'' . $db_passwd . '\'');
81
82
		// Something's wrong, show an error if its fatal (which we assume it is)
83 View Code Duplication
		if (!$connection)
84
		{
85
			if (!empty($db_options['non_fatal']))
86
				return null;
87
			else
88
				Errors::instance()->display_db_error();
89
		}
90
91
		self::$_db->_connection = $connection;
92
93
		return $connection;
94
	}
95
96
	/**
97
	 * Fix the database prefix if necessary.
98
	 * Do nothing on postgreSQL
99
	 *
100
	 * @param string $db_prefix
101
	 * @param string $db_name
102
	 *
103
	 * @return string
104
	 */
105
	public function fix_prefix($db_prefix, $db_name)
106
	{
107
		return $db_prefix;
108
	}
109
110
	/**
111
	 * Do a query.  Takes care of errors too.
112
	 * Special queries may need additional replacements to be appropriate
113
	 * for PostgreSQL.
114
	 *
115
	 * @param string $identifier
116
	 * @param string $db_string
117
	 * @param mixed[] $db_values
118
	 * @param resource|null $connection
119
	 *
120
	 * @return bool|resource|string
121
	 * @throws Elk_Exception
122
	 */
123
	public function query($identifier, $db_string, $db_values = array(), $connection = null)
124
	{
125
		global $db_show_debug, $time_start, $modSettings;
126
127
		// Decide which connection to use.
128
		$connection = $connection === null ? $this->_connection : $connection;
129
130
		// Special queries that need processing.
131
		$replacements = array(
132
			'alter_table' => array(
133
				'~(.+)~' => '',
134
			),
135
			'ban_suggest_error_ips' => array(
136
				'~RLIKE~' => '~',
137
				'~\\.~' => '\.',
138
			),
139
			'ban_suggest_message_ips' => array(
140
				'~RLIKE~' => '~',
141
				'~\\.~' => '\.',
142
			),
143
			'consolidate_spider_stats' => array(
144
				'~MONTH\(log_time\), DAYOFMONTH\(log_time\)~' => 'MONTH(CAST(CAST(log_time AS abstime) AS timestamp)), DAYOFMONTH(CAST(CAST(log_time AS abstime) AS timestamp))',
145
			),
146
			'display_get_post_poster' => array(
147
				'~GROUP BY id_msg\s+HAVING~' => 'AND',
148
			),
149
			'attach_download_increase' => array(
150
				'~LOW_PRIORITY~' => '',
151
			),
152
			'boardindex_fetch_boards' => array(
153
				'~COALESCE\(lb.id_msg, 0\) >= b.id_msg_updated~' => 'CASE WHEN COALESCE(lb.id_msg, 0) >= b.id_msg_updated THEN 1 ELSE 0 END',
154
			),
155
			'get_random_number' => array(
156
				'~RAND~' => 'RANDOM',
157
			),
158
			'insert_log_search_topics' => array(
159
				'~NOT RLIKE~' => '!~',
160
			),
161
			'insert_log_search_results_no_index' => array(
162
				'~NOT RLIKE~' => '!~',
163
			),
164
			'insert_log_search_results_subject' => array(
165
				'~NOT RLIKE~' => '!~',
166
			),
167
			'pm_conversation_list' => array(
168
				'~ORDER\\s+BY\\s+\\{raw:sort\\}~' => 'ORDER BY ' . (isset($db_values['sort']) ? ($db_values['sort'] === 'pm.id_pm' ? 'MAX(pm.id_pm)' : $db_values['sort']) : ''),
169
			),
170
			'top_topic_starters' => array(
171
				'~ORDER BY FIND_IN_SET\(id_member,(.+?)\)~' => 'ORDER BY STRPOS(\',\' || $1 || \',\', \',\' || id_member|| \',\')',
172
			),
173
			'unread_replies' => array(
174
				'~SELECT\\s+DISTINCT\\s+t.id_topic~' => 'SELECT t.id_topic, {raw:sort}',
175
			),
176
			'profile_board_stats' => array(
177
				'~COUNT\(\*\) \/ MAX\(b.num_posts\)~' => 'CAST(COUNT(*) AS DECIMAL) / CAST(b.num_posts AS DECIMAL)',
178
			),
179
		);
180
181
		if (isset($replacements[$identifier]))
182
			$db_string = preg_replace(array_keys($replacements[$identifier]), array_values($replacements[$identifier]), $db_string);
183
184
		// Limits need to be a little different.
185
		$db_string = preg_replace('~\sLIMIT\s(\d+|{int:.+}),\s*(\d+|{int:.+})\s*$~i', 'LIMIT $2 OFFSET $1', $db_string);
186
187
		if (trim($db_string) == '')
188
			return false;
189
190
		// Comments that are allowed in a query are preg_removed.
191
		static $allowed_comments_from = array(
192
			'~\s+~s',
193
			'~/\*!40001 SQL_NO_CACHE \*/~',
194
			'~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~',
195
			'~/\*!40100 ON DUPLICATE KEY UPDATE id_msg = \d+ \*/~',
196
		);
197
		static $allowed_comments_to = array(
198
			' ',
199
			'',
200
			'',
201
			'',
202
		);
203
204
		// One more query....
205
		$this->_query_count++;
206
		$this->_db_replace_result = null;
207
208 View Code Duplication
		if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override']))
209
			$this->error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__);
210
211 View Code Duplication
		if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false))
212
		{
213
			// Store these values for use in the callback function.
214
			$this->_db_callback_values = $db_values;
215
			$this->_db_callback_connection = $connection;
216
217
			// Inject the values passed to this function.
218
			$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', array($this, 'replacement__callback'), $db_string);
219
220
			// No need for them any longer.
221
			$this->_db_callback_values = array();
222
			$this->_db_callback_connection = null;
223
		}
224
225
		// Debugging.
226 View Code Duplication
		if ($db_show_debug === true)
227
		{
228
			$debug = Debug::instance();
229
230
			// Get the file and line number this function was called.
231
			list ($file, $line) = $this->error_backtrace('', '', 'return', __FILE__, __LINE__);
232
233
			if (!empty($_SESSION['debug_redirect']))
234
			{
235
				$debug->merge_db($_SESSION['debug_redirect']);
236
				// @todo this may be off by 1
237
				$this->_query_count += count($_SESSION['debug_redirect']);
238
				$_SESSION['debug_redirect'] = array();
239
			}
240
241
			// Don't overload it.
242
			$st = microtime(true);
243
			$db_cache = array();
244
			$db_cache['q'] = $this->_query_count < 50 ? $db_string : '...';
245
			$db_cache['f'] = $file;
246
			$db_cache['l'] = $line;
247
			$db_cache['s'] = $st - $time_start;
248
		}
249
250
		// First, we clean strings out of the query, reduce whitespace, lowercase, and trim - so we can check it over.
251
		if (empty($modSettings['disableQueryCheck']))
252
		{
253
			$clean = '';
254
			$old_pos = 0;
255
			$pos = -1;
256 View Code Duplication
			while (true)
257
			{
258
				$pos = strpos($db_string, '\'', $pos + 1);
259
				if ($pos === false)
260
					break;
261
				$clean .= substr($db_string, $old_pos, $pos - $old_pos);
262
263
				while (true)
264
				{
265
					$pos1 = strpos($db_string, '\'', $pos + 1);
266
					$pos2 = strpos($db_string, '\'\'', $pos + 1);
267
268
					if ($pos1 === false)
269
						break;
270
					elseif ($pos2 === false || $pos2 > $pos1)
271
					{
272
						$pos = $pos1;
273
						break;
274
					}
275
276
					$pos = $pos2 + 1;
277
				}
278
279
				$clean .= ' %s ';
280
				$old_pos = $pos + 1;
281
			}
282
283
			$clean .= substr($db_string, $old_pos);
284
			$clean = trim(strtolower(preg_replace($allowed_comments_from, $allowed_comments_to, $clean)));
285
286
			// Comments?  We don't use comments in our queries, we leave 'em outside!
287 View Code Duplication
			if (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false)
288
				$fail = true;
289
			// Trying to change passwords, slow us down, or something?
290
			elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[_a-z])~s', $clean) != 0)
291
				$fail = true;
292
			elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
293
				$fail = true;
294
295
			if (!empty($fail) && class_exists('Errors'))
296
				$this->error_backtrace('Hacking attempt...', 'Hacking attempt...' . "\n" . $db_string, E_USER_ERROR, __FILE__, __LINE__);
297
298
			// If we are updating something, better start a transaction so that indexes may be kept consistent
299
			if (!$this->_in_transaction && strpos($clean, 'update') !== false)
300
				$this->db_transaction('begin', $connection);
0 ignored issues
show
Bug introduced by
It seems like $connection defined by $connection === null ? $...onnection : $connection on line 128 can also be of type object<mysqli>; however, Database_PostgreSQL::db_transaction() does only seem to accept resource|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...
301
		}
302
303
		$this->_db_last_result = @pg_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_PostgreSQL::query()
    in sources/subs/Post.subs.php on line 484
  7. $db_string is passed through preg_replace(), and $db_string is assigned
    in sources/database/Db-postgresql.class.php on line 185
  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 1676
  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 1699
  10. $db_string is passed to Database_PostgreSQL::query()
    in sources/database/DbSearch-mysql.php on line 50
  11. $db_string is passed through preg_replace(), and $db_string is assigned
    in sources/database/Db-postgresql.class.php on line 185
  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 1676
  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 1699
  10. $db_string is passed to Database_PostgreSQL::query()
    in sources/database/DbSearch-mysql.php on line 50
  11. $db_string is passed through preg_replace(), and $db_string is assigned
    in sources/database/Db-postgresql.class.php on line 185
  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 1676
  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 1699
  6. $db_string is passed to Database_PostgreSQL::query()
    in sources/database/DbSearch-mysql.php on line 50
  7. $db_string is passed through preg_replace(), and $db_string is assigned
    in sources/database/Db-postgresql.class.php on line 185
  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 513
  3. Data is passed through str_replace()
    in vendor/sources/Subs.php on line 514
  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 1923
  10. $db_string is passed to Database_PostgreSQL::query()
    in sources/database/DbSearch-mysql.php on line 50
  11. $db_string is passed through preg_replace(), and $db_string is assigned
    in sources/database/Db-postgresql.class.php on line 185
  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 1923
  7. $db_string is passed to Database_PostgreSQL::query()
    in sources/database/DbSearch-mysql.php on line 50
  8. $db_string is passed through preg_replace(), and $db_string is assigned
    in sources/database/Db-postgresql.class.php on line 185
  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 235
  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_PostgreSQL::query()
    in sources/subs/Memberlist.subs.php on line 230
  14. $db_string is passed through preg_replace(), and $db_string is assigned
    in sources/database/Db-postgresql.class.php on line 185
  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 487
  4. $regOptions is passed to registerMember()
    in sources/controllers/Register.controller.php on line 488
  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_PostgreSQL::insert()
    in sources/subs/Members.subs.php on line 694
  11. $columnName is assigned
    in sources/database/Db-postgresql.class.php on line 578
  12. $columnName . ' = SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ' is passed through sprintf(), and $actualType is assigned
    in sources/database/Db-postgresql.class.php on line 582
  13. $actualType is passed through substr(), and $where is assigned
    in sources/database/Db-postgresql.class.php on line 588
  14. ``' DELETE FROM ' . $table . ' WHERE ' . $where`` is passed to Database_PostgreSQL::query()
    in sources/database/Db-postgresql.class.php on line 597
  15. $db_string is passed through preg_replace(), and $db_string is assigned
    in sources/database/Db-postgresql.class.php on line 185
  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 487
  4. $regOptions is passed to registerMember()
    in sources/controllers/Register.controller.php on line 488
  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_PostgreSQL::insert()
    in sources/subs/Members.subs.php on line 694
  11. $columnName is assigned
    in sources/database/Db-postgresql.class.php on line 578
  12. $columnName . ' = SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ' is passed through sprintf(), and $actualType is assigned
    in sources/database/Db-postgresql.class.php on line 582
  13. $actualType is passed through substr(), and $where is assigned
    in sources/database/Db-postgresql.class.php on line 588
  14. ``' DELETE FROM ' . $table . ' WHERE ' . $where`` is passed to Database_PostgreSQL::query()
    in sources/database/Db-postgresql.class.php on line 597
  15. $db_string is passed through preg_replace(), and $db_string is assigned
    in sources/database/Db-postgresql.class.php on line 185
  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 487
  4. $regOptions is passed to registerMember()
    in sources/controllers/Register.controller.php on line 488
  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_PostgreSQL::insert()
    in sources/subs/Members.subs.php on line 694
  11. $columnName is assigned
    in sources/database/Db-postgresql.class.php on line 578
  12. $columnName . ' = SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ' is passed through sprintf(), and $actualType is assigned
    in sources/database/Db-postgresql.class.php on line 582
  13. $actualType is passed through substr(), and $where is assigned
    in sources/database/Db-postgresql.class.php on line 588
  14. ``' DELETE FROM ' . $table . ' WHERE ' . $where`` is passed to Database_PostgreSQL::query()
    in sources/database/Db-postgresql.class.php on line 597
  15. $db_string is passed through preg_replace(), and $db_string is assigned
    in sources/database/Db-postgresql.class.php on line 185
  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 487
  4. $regOptions is passed to registerMember()
    in sources/controllers/Register.controller.php on line 488
  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_PostgreSQL::insert()
    in sources/subs/Members.subs.php on line 694
  11. $columnName is assigned
    in sources/database/Db-postgresql.class.php on line 578
  12. $columnName . ' = SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ' is passed through sprintf(), and $actualType is assigned
    in sources/database/Db-postgresql.class.php on line 582
  13. $actualType is passed through substr(), and $where is assigned
    in sources/database/Db-postgresql.class.php on line 588
  14. ``' DELETE FROM ' . $table . ' WHERE ' . $where`` is passed to Database_PostgreSQL::query()
    in sources/database/Db-postgresql.class.php on line 597
  15. $db_string is passed through preg_replace(), and $db_string is assigned
    in sources/database/Db-postgresql.class.php on line 185
  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 487
  6. $regOptions is passed to registerMember()
    in sources/controllers/Register.controller.php on line 488
  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_PostgreSQL::insert()
    in sources/subs/Members.subs.php on line 694
  13. $columnName is assigned
    in sources/database/Db-postgresql.class.php on line 578
  14. $columnName . ' = SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ' is passed through sprintf(), and $actualType is assigned
    in sources/database/Db-postgresql.class.php on line 582
  15. $actualType is passed through substr(), and $where is assigned
    in sources/database/Db-postgresql.class.php on line 588
  16. ``' DELETE FROM ' . $table . ' WHERE ' . $where`` is passed to Database_PostgreSQL::query()
    in sources/database/Db-postgresql.class.php on line 597
  17. $db_string is passed through preg_replace(), and $db_string is assigned
    in sources/database/Db-postgresql.class.php on line 185
  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 487
  4. $regOptions is passed to registerMember()
    in sources/controllers/Register.controller.php on line 488
  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_PostgreSQL::insert()
    in sources/subs/Members.subs.php on line 694
  11. $columnName is assigned
    in sources/database/Db-postgresql.class.php on line 578
  12. $columnName . ' = SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ' is passed through sprintf(), and $actualType is assigned
    in sources/database/Db-postgresql.class.php on line 582
  13. $actualType is passed through substr(), and $where is assigned
    in sources/database/Db-postgresql.class.php on line 588
  14. ``' DELETE FROM ' . $table . ' WHERE ' . $where`` is passed to Database_PostgreSQL::query()
    in sources/database/Db-postgresql.class.php on line 597
  15. $db_string is passed through preg_replace(), and $db_string is assigned
    in sources/database/Db-postgresql.class.php on line 185

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...
304
305
		if ($this->_db_last_result === false && !$this->_skip_error)
306
		{
307
			$this->error($db_string, $connection);
0 ignored issues
show
Bug introduced by
It seems like $connection defined by $connection === null ? $...onnection : $connection on line 128 can also be of type object<mysqli>; however, Database_PostgreSQL::error() does only seem to accept resource|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...
308
		}
309
310
		// Revert not to skip errors
311
		if ($this->_skip_error === true)
312
		{
313
			$this->_skip_error = false;
314
		}
315
316
		if ($this->_in_transaction)
317
			$this->db_transaction('commit', $connection);
0 ignored issues
show
Bug introduced by
It seems like $connection defined by $connection === null ? $...onnection : $connection on line 128 can also be of type object<mysqli>; however, Database_PostgreSQL::db_transaction() does only seem to accept resource|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...
318
319
		// Debugging.
320 View Code Duplication
		if ($db_show_debug === true)
321
		{
322
			$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...
323
			$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...
324
		}
325
326
		return $this->_db_last_result;
327
	}
328
329
	/**
330
	 * Affected rows from previous operation.
331
	 *
332
	 * @param resource|null $result
333
	 */
334
	public function affected_rows($result = null)
335
	{
336
		if ($this->_db_replace_result !== null)
337
			return $this->_db_replace_result;
338
		elseif ($result === null && !$this->_db_last_result)
339
			return 0;
340
341
		return pg_affected_rows($result === null ? $this->_db_last_result : $result);
342
	}
343
344
	/**
345
	 * Last inserted id.
346
	 *
347
	 * @param string $table
348
	 * @param string|null $field = null
349
	 * @param resource|null $connection = null
350
	 * @throws Elk_Exception
351
	 */
352
	public function insert_id($table, $field = null, $connection = null)
353
	{
354
		global $db_prefix;
355
356
		$table = str_replace('{db_prefix}', $db_prefix, $table);
357
358
		$connection = $connection === null ? $this->_connection : $connection;
359
360
		// Try get the last ID for the auto increment field.
361
		$request = $this->query('', 'SELECT CURRVAL(\'' . $table . '_seq\') AS insertID',
362
			array(
363
			),
364
			$connection
0 ignored issues
show
Bug introduced by
It seems like $connection defined by $connection === null ? $...onnection : $connection on line 358 can also be of type object<mysqli>; however, Database_PostgreSQL::query() does only seem to accept resource|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...
365
		);
366
367
		if (!$request)
368
			return false;
369
370
		list ($lastID) = $this->fetch_row($request);
0 ignored issues
show
Documentation introduced by
$request is of type boolean|string, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
371
		$this->free_result($request);
0 ignored issues
show
Documentation introduced by
$request is of type boolean|string, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
372
373
		return $lastID;
374
	}
375
376
	/**
377
	 * Tracking the current row.
378
	 * Fetch a row from the resultset given as parameter.
379
	 *
380
	 * @param resource $request
381
	 * @param integer|bool $counter = false
382
	 */
383 View Code Duplication
	public function fetch_row($request, $counter = false)
384
	{
385
		global $db_row_count;
386
387
		if ($counter !== false)
388
			return pg_fetch_row($request, $counter);
389
390
		// Reset the row counter...
391
		if (!isset($db_row_count[(int) $request]))
392
			$db_row_count[(int) $request] = 0;
393
394
		// Return the right row.
395
		return @pg_fetch_row($request, $db_row_count[(int) $request]++);
396
	}
397
398
	/**
399
	 * Free the resultset.
400
	 *
401
	 * @param resource $result
402
	 */
403
	public function free_result($result)
404
	{
405
		// Just delegate to the native function
406
		pg_free_result($result);
407
	}
408
409
	/**
410
	 * Get the number of rows in the result.
411
	 *
412
	 * @param resource $result
413
	 */
414
	public function num_rows($result)
415
	{
416
		// simply delegate to the native function
417
		return pg_num_rows($result);
418
	}
419
420
	/**
421
	 * Get the number of fields in the resultset.
422
	 *
423
	 * @param resource $request
424
	 */
425
	public function num_fields($request)
426
	{
427
		return pg_num_fields($request);
428
	}
429
430
	/**
431
	 * Reset the internal result pointer.
432
	 *
433
	 * @param boolean $request
434
	 * @param integer $counter
435
	 */
436
	public function data_seek($request, $counter)
437
	{
438
		global $db_row_count;
439
440
		$db_row_count[(int) $request] = $counter;
441
442
		return true;
443
	}
444
445
	/**
446
	 * Do a transaction.
447
	 *
448
	 * @param string $type - the step to perform (i.e. 'begin', 'commit', 'rollback')
449
	 * @param resource|null $connection = null
450
	 */
451
	public function db_transaction($type = 'commit', $connection = null)
452
	{
453
		// Decide which connection to use
454
		$connection = $connection === null ? $this->_connection : $connection;
455
456
		if ($type == 'begin')
457
		{
458
			$this->_in_transaction = true;
459
			return @pg_query($connection, 'BEGIN');
460
		}
461
		elseif ($type == 'rollback')
462
			return @pg_query($connection, 'ROLLBACK');
463
		elseif ($type == 'commit')
464
		{
465
			$this->_in_transaction = false;
466
			return @pg_query($connection, 'COMMIT');
467
		}
468
469
		return false;
470
	}
471
472
	/**
473
	 * Return last error string from the database server
474
	 *
475
	 * @param resource|null $connection = null
476
	 */
477
	public function last_error($connection = null)
478
	{
479
		// Decide which connection to use
480
		$connection = $connection === null ? $this->_connection : $connection;
481
482
		if (is_resource($connection))
483
			return pg_last_error($connection);
484
	}
485
486
	/**
487
	 * Database error.
488
	 * Backtrace, log, try to fix.
489
	 *
490
	 * @param string        $db_string
491
	 * @param resource|null $connection = null
492
	 *
493
	 * @throws Elk_Exception
494
	 */
495
	public function error($db_string, $connection = null)
496
	{
497
		global $txt, $context, $modSettings, $db_show_debug;
498
499
		// We'll try recovering the file and line number the original db query was called from.
500
		list ($file, $line) = $this->error_backtrace('', '', 'return', __FILE__, __LINE__);
501
502
		// Decide which connection to use
503
		$connection = $connection === null ? $this->_connection : $connection;
504
505
		// This is the error message...
506
		$query_error = @pg_last_error($connection);
507
508
		// Log the error.
509 View Code Duplication
		if (class_exists('Errors'))
510
		{
511
			Errors::instance()->log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n" . $db_string : ''), 'database', $file, $line);
512
		}
513
514
		// Nothing's defined yet... just die with it.
515
		if (empty($context) || empty($txt))
516
			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...
517
518
		// Show an error message, if possible.
519
		$context['error_title'] = $txt['database_error'];
520 View Code Duplication
		if (allowedTo('admin_forum'))
521
			$context['error_message'] = nl2br($query_error) . '<br />' . $txt['file'] . ': ' . $file . '<br />' . $txt['line'] . ': ' . $line;
522
		else
523
			$context['error_message'] = $txt['try_again'];
524
525
		// Add database version that we know of, for the admin to know. (and ask for support)
526 View Code Duplication
		if (allowedTo('admin_forum'))
527
			$context['error_message'] .= '<br /><br />' . sprintf($txt['database_error_versions'], $modSettings['elkVersion']);
528
529 View Code Duplication
		if (allowedTo('admin_forum') && $db_show_debug === true)
530
			$context['error_message'] .= '<br /><br />' . nl2br($db_string);
531
532
		// It's already been logged... don't log it again.
533
		throw new Elk_Exception($context['error_message'], false);
534
	}
535
536
	/**
537
	 * Insert data.
538
	 *
539
	 * @param string $method - options 'replace', 'ignore', 'insert'
540
	 * @param string $table
541
	 * @param mixed[] $columns
542
	 * @param mixed[] $data
543
	 * @param mixed[] $keys
544
	 * @param bool $disable_trans = false
545
	 * @param resource|null $connection = null
546
	 * @throws Elk_Exception
547
	 */
548
	public function insert($method = 'replace', $table, $columns, $data, $keys, $disable_trans = false, $connection = null)
549
	{
550
		global $db_prefix;
551
552
		$connection = $connection === null ? $this->_connection : $connection;
553
554
		// With nothing to insert, simply return.
555
		if (empty($data))
556
			return;
557
558
		// Inserting data as a single row can be done as a single array.
559
		if (!is_array($data[array_rand($data)]))
560
			$data = array($data);
561
562
		// Replace the prefix holder with the actual prefix.
563
		$table = str_replace('{db_prefix}', $db_prefix, $table);
564
565
		$priv_trans = false;
566
		if ((count($data) > 1 || $method == 'replace') && !$this->_in_transaction && !$disable_trans)
567
		{
568
			$this->db_transaction('begin', $connection);
0 ignored issues
show
Bug introduced by
It seems like $connection defined by $connection === null ? $...onnection : $connection on line 552 can also be of type object<mysqli>; however, Database_PostgreSQL::db_transaction() does only seem to accept resource|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...
569
			$priv_trans = true;
570
		}
571
572
		// PostgreSQL doesn't support replace: we implement a MySQL-compatible behavior instead
573
		if ($method == 'replace')
574
		{
575
			$count = 0;
576
			$where = '';
577
			$db_replace_result = 0;
578
			foreach ($columns as $columnName => $type)
579
			{
580
				// Are we restricting the length?
581
				if (strpos($type, 'string-') !== false)
582
					$actualType = sprintf($columnName . ' = SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $count);
583
				else
584
					$actualType = sprintf($columnName . ' = {%1$s:%2$s}, ', $type, $count);
585
586
				// A key? That's what we were looking for.
587
				if (in_array($columnName, $keys))
588
					$where .= (empty($where) ? '' : ' AND ') . substr($actualType, 0, -2);
589
				$count++;
590
			}
591
592
			// Make it so.
593
			if (!empty($where) && !empty($data))
594
			{
595
				foreach ($data as $k => $entry)
596
				{
597
					$this->query('', '
598
						DELETE FROM ' . $table .
599
						' WHERE ' . $where,
600
						$entry, $connection
0 ignored issues
show
Bug introduced by
It seems like $connection defined by $connection === null ? $...onnection : $connection on line 552 can also be of type object<mysqli>; however, Database_PostgreSQL::query() does only seem to accept resource|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...
601
					);
602
					$db_replace_result += (!$this->_db_last_result ? 0 : pg_affected_rows($this->_db_last_result));
603
				}
604
			}
605
		}
606
607
		if (!empty($data))
608
		{
609
			// Create the mold for a single row insert.
610
			$insertData = '(';
611 View Code Duplication
			foreach ($columns as $columnName => $type)
612
			{
613
				// Are we restricting the length?
614
				if (strpos($type, 'string-') !== false)
615
					$insertData .= sprintf('SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName);
616
				else
617
					$insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName);
618
			}
619
			$insertData = substr($insertData, 0, -2) . ')';
620
621
			// Create an array consisting of only the columns.
622
			$indexed_columns = array_keys($columns);
623
624
			// Here's where the variables are injected to the query.
625
			$insertRows = array();
626
			foreach ($data as $dataRow)
627
				$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 552 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...
628
629
			$inserted_results = 0;
630
			$skip_error = $method == 'ignore' || $table === $db_prefix . 'log_errors';
631
			foreach ($insertRows as $entry)
632
			{
633
				$this->_skip_error = $skip_error;
634
635
				// Do the insert.
636
				$this->query('', '
637
					INSERT INTO ' . $table . '("' . implode('", "', $indexed_columns) . '")
638
					VALUES
639
						' . $entry,
640
					array(
641
						'security_override' => true,
642
					),
643
					$connection
0 ignored issues
show
Bug introduced by
It seems like $connection defined by $connection === null ? $...onnection : $connection on line 552 can also be of type object<mysqli>; however, Database_PostgreSQL::query() does only seem to accept resource|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...
644
				);
645
				$inserted_results += (!$this->_db_last_result ? 0 : pg_affected_rows($this->_db_last_result));
646
			}
647
			if (isset($db_replace_result))
648
				$this->_db_replace_result = $db_replace_result + $inserted_results;
649
		}
650
651
		if ($priv_trans)
652
			$this->db_transaction('commit', $connection);
0 ignored issues
show
Bug introduced by
It seems like $connection defined by $connection === null ? $...onnection : $connection on line 552 can also be of type object<mysqli>; however, Database_PostgreSQL::db_transaction() does only seem to accept resource|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...
653
	}
654
655
	/**
656
	 * Unescape an escaped string!
657
	 *
658
	 * @param string $string
659
	 */
660
	public function unescape_string($string)
661
	{
662
		return strtr($string, array('\'\'' => '\''));
663
	}
664
665
	/**
666
	 * Returns whether the database system supports ignore.
667
	 *
668
	 * @return false
669
	 */
670
	public function support_ignore()
671
	{
672
		return false;
673
	}
674
675
	/**
676
	 * Gets all the necessary INSERTs for the table named table_name.
677
	 * It goes in 250 row segments.
678
	 *
679
	 * @param string $tableName - the table to create the inserts for.
680
	 * @param bool $new_table
681
	 *
682
	 * @return string the query to insert the data back in, or an empty string if the table was empty.
683
	 * @throws Elk_Exception
684
	 */
685
	public function insert_sql($tableName, $new_table = false)
686
	{
687
		global $db_prefix;
688
689
		static $start = 0, $num_rows, $fields, $limit;
690
691 View Code Duplication
		if ($new_table)
692
		{
693
			$limit = strstr($tableName, 'log_') !== false ? 500 : 250;
694
			$start = 0;
695
		}
696
697
		$data = '';
698
		$tableName = str_replace('{db_prefix}', $db_prefix, $tableName);
699
700
		// This will be handy...
701
		$crlf = "\r\n";
702
703
		$result = $this->query('', '
704
			SELECT *
705
			FROM ' . $tableName . '
706
			LIMIT ' . $start . ', ' . $limit,
707
			array(
708
				'security_override' => true,
709
			)
710
		);
711
712
		// The number of rows, just for record keeping and breaking INSERTs up.
713
		$num_rows = $this->num_rows($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query('', ' SE...ity_override' => true)) on line 703 can also be of type boolean or string; however, Database_PostgreSQL::num_rows() does only seem to accept resource, 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...
714
715
		if ($num_rows == 0)
716
			return '';
717
718
		if ($new_table)
719
		{
720
			$fields = array_keys($this->fetch_assoc($result));
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query('', ' SE...ity_override' => true)) on line 703 can also be of type boolean or string; however, Database_PostgreSQL::fetch_assoc() does only seem to accept resource, 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...
721
			$this->data_seek($result, 0);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query('', ' SE...ity_override' => true)) on line 703 can also be of type resource or string; however, Database_PostgreSQL::data_seek() does only seem to accept boolean, 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...
722
		}
723
724
		// Start it off with the basic INSERT INTO.
725
		$insert_msg = 'INSERT INTO ' . $tableName . $crlf . "\t" . '(' . implode(', ', $fields) . ')' . $crlf . 'VALUES ' . $crlf . "\t";
726
727
		// Loop through each row.
728 View Code Duplication
		while ($row = $this->fetch_assoc($result))
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query('', ' SE...ity_override' => true)) on line 703 can also be of type boolean or string; however, Database_PostgreSQL::fetch_assoc() does only seem to accept resource, 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...
729
		{
730
			// Get the fields in this row...
731
			$field_list = array();
732
733
			foreach ($row as $key => $item)
734
			{
735
				// Try to figure out the type of each field. (NULL, number, or 'string'.)
736
				if (!isset($item))
737
					$field_list[] = 'NULL';
738
				elseif (is_numeric($item) && (int) $item == $item)
739
					$field_list[] = $item;
740
				else
741
					$field_list[] = '\'' . $this->escape_string($item) . '\'';
742
			}
743
744
			// 'Insert' the data.
745
			$data .= $insert_msg . '(' . implode(', ', $field_list) . ');' . $crlf;
746
		}
747
		$this->free_result($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query('', ' SE...ity_override' => true)) on line 703 can also be of type boolean or string; however, Database_PostgreSQL::free_result() does only seem to accept resource, 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...
748
749
		$data .= $crlf;
750
751
		$start += $limit;
752
753
		return $data;
754
	}
755
756
	/**
757
	 * Dumps the schema (CREATE) for a table.
758
	 *
759
	 * @param string $tableName - the table
760
	 *
761
	 * @return string - the CREATE statement as string
762
	 * @throws Elk_Exception
763
	 */
764
	public function db_table_sql($tableName)
765
	{
766
		global $db_prefix;
767
768
		$tableName = str_replace('{db_prefix}', $db_prefix, $tableName);
769
770
		// This will be needed...
771
		$crlf = "\r\n";
772
773
		// Start the create table...
774
		$schema_create = 'CREATE TABLE ' . $tableName . ' (' . $crlf;
775
		$index_create = '';
776
		$seq_create = '';
777
778
		// Find all the fields.
779
		$result = $this->query('', '
780
			SELECT column_name, column_default, is_nullable, data_type, character_maximum_length
781
			FROM information_schema.columns
782
			WHERE table_name = {string:table}
783
			ORDER BY ordinal_position',
784
			array(
785
				'table' => $tableName,
786
			)
787
		);
788
		while ($row = $this->fetch_assoc($result))
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query('', ' SE...'table' => $tableName)) on line 779 can also be of type boolean or string; however, Database_PostgreSQL::fetch_assoc() does only seem to accept resource, 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...
789
		{
790
			if ($row['data_type'] == 'character varying')
791
				$row['data_type'] = 'varchar';
792
			elseif ($row['data_type'] == 'character')
793
				$row['data_type'] = 'char';
794
795
			if ($row['character_maximum_length'])
796
				$row['data_type'] .= '(' . $row['character_maximum_length'] . ')';
797
798
			// Make the CREATE for this column.
799
			$schema_create .= ' "' . $row['column_name'] . '" ' . $row['data_type'] . ($row['is_nullable'] != 'YES' ? ' NOT NULL' : '');
800
801
			// Add a default...?
802
			if (trim($row['column_default']) != '')
803
			{
804
				$schema_create .= ' default ' . $row['column_default'] . '';
805
806
				// Auto increment?
807
				if (preg_match('~nextval\(\'(.+?)\'(.+?)*\)~i', $row['column_default'], $matches) != 0)
808
				{
809
					// Get to find the next variable first!
810
					$count_req = $this->query('', '
811
						SELECT MAX("{raw:column}")
812
						FROM {raw:table}',
813
						array(
814
							'column' => $row['column_name'],
815
							'table' => $tableName,
816
						)
817
					);
818
					list ($max_ind) = $this->fetch_row($count_req);
0 ignored issues
show
Bug introduced by
It seems like $count_req can also be of type boolean or string; however, Database_PostgreSQL::fetch_row() does only seem to accept resource, 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...
819
					$this->free_result($count_req);
0 ignored issues
show
Bug introduced by
It seems like $count_req defined by $this->query('', ' ...'table' => $tableName)) on line 810 can also be of type boolean or string; however, Database_PostgreSQL::free_result() does only seem to accept resource, 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...
820
821
					// Get the right bloody start!
822
					$seq_create .= 'CREATE SEQUENCE ' . $matches[1] . ' START WITH ' . ($max_ind + 1) . ';' . $crlf . $crlf;
823
				}
824
			}
825
826
			$schema_create .= ',' . $crlf;
827
		}
828
		$this->free_result($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query('', ' SE...'table' => $tableName)) on line 779 can also be of type boolean or string; however, Database_PostgreSQL::free_result() does only seem to accept resource, 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...
829
830
		// Take off the last comma.
831
		$schema_create = substr($schema_create, 0, -strlen($crlf) - 1);
832
833
		$result = $this->query('', '
834
			SELECT CASE WHEN i.indisprimary THEN 1 ELSE 0 END AS is_primary, pg_get_indexdef(i.indexrelid) AS inddef
835
			FROM pg_class AS c
836
				INNER JOIN pg_index AS i ON (i.indrelid = c.oid)
837
				INNER JOIN pg_class AS c2 ON (c2.oid = i.indexrelid)
838
			WHERE c.relname = {string:table}',
839
			array(
840
				'table' => $tableName,
841
			)
842
		);
843
844
		while ($row = $this->fetch_assoc($result))
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query('', ' SE...'table' => $tableName)) on line 833 can also be of type boolean or string; however, Database_PostgreSQL::fetch_assoc() does only seem to accept resource, 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...
845
		{
846
			if ($row['is_primary'])
847
			{
848
				if (preg_match('~\(([^\)]+?)\)~i', $row['inddef'], $matches) == 0)
849
					continue;
850
851
				$index_create .= $crlf . 'ALTER TABLE ' . $tableName . ' ADD PRIMARY KEY ("' . $matches[1] . '");';
852
			}
853
			else
854
				$index_create .= $crlf . $row['inddef'] . ';';
855
		}
856
		$this->free_result($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query('', ' SE...'table' => $tableName)) on line 833 can also be of type boolean or string; however, Database_PostgreSQL::free_result() does only seem to accept resource, 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...
857
858
		// Finish it off!
859
		$schema_create .= $crlf . ');';
860
861
		return $seq_create . $schema_create . $index_create;
862
	}
863
864
	/**
865
	 * {@inheritdoc}
866
	 */
867
	public function db_list_tables($db_name_str = false, $filter = false)
868
	{
869
		$request = $this->query('', '
870
			SELECT tablename
871
			FROM pg_tables
872
			WHERE schemaname = {string:schema_public}' . ($filter === false ? '' : '
873
				AND tablename LIKE {string:filter}') . '
874
			ORDER BY tablename',
875
			array(
876
				'schema_public' => 'public',
877
				'filter' => $filter,
878
			)
879
		);
880
		$tables = array();
881
		while ($row = $this->fetch_row($request))
0 ignored issues
show
Bug introduced by
It seems like $request defined by $this->query('', ' SE..., 'filter' => $filter)) on line 869 can also be of type boolean or string; however, Database_PostgreSQL::fetch_row() does only seem to accept resource, 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...
882
			$tables[] = $row[0];
883
		$this->free_result($request);
0 ignored issues
show
Bug introduced by
It seems like $request defined by $this->query('', ' SE..., 'filter' => $filter)) on line 869 can also be of type boolean or string; however, Database_PostgreSQL::free_result() does only seem to accept resource, 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...
884
885
		return $tables;
886
	}
887
888
	/**
889
	 * Backup $table to $backup_table.
890
	 *
891
	 * @param string $table
892
	 * @param string $backup_table
893
	 * @throws Elk_Exception
894
	 */
895
	public function db_backup_table($table, $backup_table)
896
	{
897
		global $db_prefix;
898
899
		$table = str_replace('{db_prefix}', $db_prefix, $table);
900
901
		// Do we need to drop it first?
902
		$db_table = db_table();
903
		$db_table->db_drop_table($backup_table);
904
905
		// @todo Should we create backups of sequences as well?
906
		$this->query('', '
907
			CREATE TABLE {raw:backup_table}
908
			(
909
				LIKE {raw:table}
910
				INCLUDING DEFAULTS
911
			)',
912
			array(
913
				'backup_table' => $backup_table,
914
				'table' => $table,
915
			)
916
		);
917
918
		$this->query('', '
919
			INSERT INTO {raw:backup_table}
920
			SELECT * FROM {raw:table}',
921
			array(
922
				'backup_table' => $backup_table,
923
				'table' => $table,
924
			)
925
		);
926
	}
927
928
	/**
929
	 * Get the server version number.
930
	 *
931
	 * @return string - the version
932
	 */
933
	public function db_server_version()
934
	{
935
		$version = pg_version();
936
937
		return $version['server'];
938
	}
939
940
	/**
941
	 * Get the name (title) of the database system.
942
	 *
943
	 * @return string
944
	 */
945
	public function db_title()
946
	{
947
		return 'PostgreSQL';
948
	}
949
950
	/**
951
	 * Whether the database system is case sensitive.
952
	 *
953
	 * @return boolean
954
	 */
955
	public function db_case_sensitive()
956
	{
957
		return true;
958
	}
959
960
	/**
961
	 * Quotes identifiers for replacement__callback.
962
	 *
963
	 * @param mixed $replacement
964
	 * @return string
965
	 * @throws Elk_Exception
966
	 */
967 View Code Duplication
	protected function _replaceIdentifier($replacement)
968
	{
969
		if (preg_match('~[a-z_][0-9,a-z,A-Z$_]{0,60}~', $replacement) !== 1)
970
		{
971
			$this->error_backtrace('Wrong value type sent to the database. Invalid identifier used. (' . $replacement . ')', '', E_USER_ERROR, __FILE__, __LINE__);
972
		}
973
974
		return '"' . $replacement . '"';
975
	}
976
977
	/**
978
	 * Escape string for the database input
979
	 *
980
	 * @param string $string
981
	 */
982
	public function escape_string($string)
983
	{
984
		return pg_escape_string($string);
985
	}
986
987
	/**
988
	 * Fetch next result as association.
989
	 *
990
	 * @param resource $request
991
	 * @param int|bool $counter = false
992
	 */
993 View Code Duplication
	public function fetch_assoc($request, $counter = false)
994
	{
995
		global $db_row_count;
996
997
		if ($counter !== false)
998
			return pg_fetch_assoc($request, $counter);
999
1000
		// Reset the row counter...
1001
		if (!isset($db_row_count[(int) $request]))
1002
			$db_row_count[(int) $request] = 0;
1003
1004
		// Return the right row.
1005
		return @pg_fetch_assoc($request, $db_row_count[(int) $request]++);
1006
	}
1007
1008
	/**
1009
	 * Return server info.
1010
	 *
1011
	 * @return string
1012
	 */
1013
	public function db_server_info()
1014
	{
1015
		// give info on client! we use it in install and upgrade and such things.
1016
		$version = pg_version();
1017
1018
		return $version['client'];
1019
	}
1020
1021
	/**
1022
	 * Return client version.
1023
	 *
1024
	 * @return string - the version
1025
	 */
1026
	public function db_client_version()
1027
	{
1028
		$version = pg_version();
1029
1030
		return $version['client'];
1031
	}
1032
1033
	/**
1034
	 * Dummy function really. Doesn't do anything on PostgreSQL.
1035
	 *
1036
	 * @param string|null $db_name = null
1037
	 * @param resource|null $connection = null
1038
	 *
1039
	 * @return boolean
1040
	 */
1041
	public function select_db($db_name = null, $connection = null)
1042
	{
1043
		return true;
1044
	}
1045
1046
	/**
1047
	 * Returns a reference to the existing instance
1048
	 */
1049
	public static function db()
1050
	{
1051
		return self::$_db;
1052
	}
1053
1054
	/**
1055
	 * Finds out if the connection is still valid.
1056
	 *
1057
	 * @param postgre|null $connection = null
1058
	 */
1059
	public function validConnection($connection = null)
1060
	{
1061
		return is_resource($connection);
1062
	}
1063
}
1064