Completed
Pull Request — development (#2329)
by Joshua
09:15
created

Database_Abstract::error_backtrace()   C

Complexity

Conditions 12
Paths 42

Size

Total Lines 41
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 156
Metric Value
dl 0
loc 41
ccs 0
cts 26
cp 0
rs 5.1612
cc 12
eloc 21
nc 42
nop 5
crap 156

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file provides an implementation of the most common functions needed
5
 * for the database drivers to work.
6
 *
7
 * @name      ElkArte Forum
8
 * @copyright ElkArte Forum contributors
9
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
10
 *
11
 * This file contains code covered by:
12
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
13
 * license:  	BSD, See included LICENSE.TXT for terms and conditions.
14
 *
15
 * @version 1.1 dev Release Candidate 1
16
 *
17
 */
18
19
if (!defined('ELK'))
20
	die('No access...');
21
22
/**
23
 * Abstract database class, implements database to control functions
24
 */
25
abstract class Database_Abstract implements Database
26
{
27
	/**
28
	 * Current connection to the database
29
	 * @var resource
30
	 */
31
	protected $_connection = null;
32
33
	/**
34
	 * Number of queries run (may include queries from $_SESSION if is a redirect)
35
	 * @var int
36
	 */
37
	protected $_query_count = 0;
38
39
	/**
40
	 * Yet another way to skip a database error
41
	 * @var boolean
42
	 */
43
	protected $_skip_error = false;
44
45
	/**
46
	 * This is used to remember the "previous" state of the skip_error parameter
47
	 * @var null|boolean
48
	 */
49
	protected $_old_skip_error = null;
50
51
	/**
52
	 * Private constructor.
53
	 */
54
	protected function __construct()
55
	{
56
		// Objects should be created through initiate().
57
	}
58
59
	/**
60
	 * Callback for preg_replace_callback on the query.
61
	 * It allows to replace on the fly a few pre-defined strings, for
62
	 * convenience ('query_see_board', 'query_wanna_see_board'), with
63
	 * their current values from $user_info.
64
	 * In addition, it performs checks and sanitization on the values
65
	 * sent to the database.
66
	 *
67
	 * @param mixed[] $matches
68
	 */
69 24
	public function replacement__callback($matches)
70
	{
71 24
		global $db_callback, $user_info, $db_prefix;
72
73 24
		list ($values, $connection) = $db_callback;
74
75
		// Connection gone???  This should *never* happen at this point, yet it does :'(
76 24
		if (!$this->_validConnection($connection))
0 ignored issues
show
Bug introduced by
The method _validConnection() does not exist on Database_Abstract. Did you maybe mean connection()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
77 24
			Errors::instance()->display_db_error();
78
79 24
		if ($matches[1] === 'db_prefix')
80 24
			return $db_prefix;
81
82 24
		if ($matches[1] === 'query_see_board')
83 24
			return $user_info['query_see_board'];
84
85 24
		if ($matches[1] === 'query_wanna_see_board')
86 24
			return $user_info['query_wanna_see_board'];
87
88 24
		if (!isset($matches[2]))
89 24
			$this->error_backtrace('Invalid value inserted or no type specified.', '', E_USER_ERROR, __FILE__, __LINE__);
90
91 24
		if (!isset($values[$matches[2]]))
92 24
			$this->error_backtrace('The database value you\'re trying to insert does not exist: ' . htmlspecialchars($matches[2], ENT_COMPAT, 'UTF-8'), '', E_USER_ERROR, __FILE__, __LINE__);
93
94 24
		$replacement = $values[$matches[2]];
95
96 24
		switch ($matches[1])
97
		{
98 24
			case 'int':
99 15
				if (!is_numeric($replacement) || (string) $replacement !== (string) (int) $replacement)
100 15
					$this->error_backtrace('Wrong value type sent to the database. Integer expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
101 15
				return (string) (int) $replacement;
102
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
103
104 24
			case 'string':
105 24
			case 'text':
106 22
				return sprintf('\'%1$s\'', $this->escape_string($replacement));
107
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
108
109 15
			case 'array_int':
110 11
				if (is_array($replacement))
111 11
				{
112 11
					if (empty($replacement))
113 11
						$this->error_backtrace('Database error, given array of integer values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
114
115 11
					foreach ($replacement as $key => $value)
116
					{
117 11
						if (!is_numeric($value) || (string) $value !== (string) (int) $value)
118 11
							$this->error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
119
120 11
						$replacement[$key] = (string) (int) $value;
121 11
					}
122
123 11
					return implode(', ', $replacement);
124
				}
125
				else
126
					$this->error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
127
128
			break;
129
130 14
			case 'array_string':
131 10
				if (is_array($replacement))
132 10
				{
133 10
					if (empty($replacement))
134 10
						$this->error_backtrace('Database error, given array of string values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
135
136 10
					foreach ($replacement as $key => $value)
137 10
						$replacement[$key] = sprintf('\'%1$s\'', $this->escape_string($value));
138
139 10
					return implode(', ', $replacement);
140
				}
141
				else
142
					$this->error_backtrace('Wrong value type sent to the database. Array of strings expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
143
			break;
144
145 13
			case 'date':
146
				if (preg_match('~^(\d{4})-([0-1]?\d)-([0-3]?\d)$~', $replacement, $date_matches) === 1)
147
					return sprintf('\'%04d-%02d-%02d\'', $date_matches[1], $date_matches[2], $date_matches[3]);
148
				else
149
					$this->error_backtrace('Wrong value type sent to the database. Date expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
150
			break;
151
152 13
			case 'float':
153 1
				if (!is_numeric($replacement))
154 1
					$this->error_backtrace('Wrong value type sent to the database. Floating point number expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
155 1
				return (string) (float) $replacement;
156
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
157
158 12
			case 'identifier':
159
				return '`' . strtr($replacement, array('`' => '', '.' => '')) . '`';
160
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
161
162 12
			case 'raw':
163 12
				return $replacement;
164
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
165
166
			default:
167
				$this->error_backtrace('Undefined type used in the database query. (' . $matches[1] . ':' . $matches[2] . ')', '', false, __FILE__, __LINE__);
168
			break;
169
		}
170
	}
171
172
	/**
173
	 * This function works like $this->query(), escapes and quotes a string,
174
	 * but it doesn't execute the query.
175
	 *
176
	 * @param string $db_string
177
	 * @param mixed[] $db_values
178
	 * @param resource|null $connection = null
179
	 */
180 17
	public function quote($db_string, $db_values, $connection = null)
181
	{
182 17
		global $db_callback;
183
184
		// Only bother if there's something to replace.
185 17
		if (strpos($db_string, '{') !== false)
186 17
		{
187
			// This is needed by the callback function.
188 17
			$db_callback = array($db_values, $connection === null ? $this->_connection : $connection);
189
190
			// Do the quoting and escaping
191 17
			$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', array($this, 'replacement__callback'), $db_string);
192
193
			// Clear this global variable.
194 17
			$db_callback = array();
195 17
		}
196
197 17
		return $db_string;
198
	}
199
200
	/**
201
	 * {@inheritDoc}
202
	 */
203 1
	public function fetchQuery($db_string, $db_values = array(), $seeds = null)
204
	{
205 1
		$request = $this->query('', $db_string, $db_values);
206
207 1
		$results = $seeds !== null ? $seeds : array();
208 1
		while ($row = $this->fetch_assoc($request))
209 1
			$results[] = $row;
210 1
		$this->free_result($request);
211
212 1
		return $results;
213
	}
214
215
	/**
216
	 * {@inheritDoc}
217
	 */
218 2
	public function fetchQueryCallback($db_string, $db_values = array(), $callback = null, $seeds = null)
219
	{
220 2
		if ($callback === null)
221 2
			return $this->fetchQuery($db_string, $db_values);
222
223 2
		$request = $this->query('', $db_string, $db_values);
224
225 2
		$results = $seeds !== null ? $seeds : array();
226 2
		while ($row = $this->fetch_assoc($request))
227 2
			$results[] = $callback($row);
228 2
		$this->free_result($request);
229
230 2
		return $results;
231
	}
232
233
	/**
234
	 * This function combines the keys and values of the data passed to db::insert.
235
	 *
236
	 * @param integer[] $keys
237
	 * @param mixed[] $values
238
	 * @return mixed[]
239
	 */
240 17
	protected function _array_combine($keys, $values)
241
	{
242 17
		$is_numeric = array_filter(array_keys($values), 'is_numeric');
243
244 17
		if (!empty($is_numeric))
245 17
			return array_combine($keys, $values);
246
		else
247
		{
248 11
			$combined = array();
249 11
			foreach ($keys as $key)
250
			{
251 11
				if (isset($values[$key]))
252 11
					$combined[$key] = $values[$key];
253 11
			}
254
255
			// @todo should throw an E_WARNING if count($combined) != count($keys)
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
256 11
			return $combined;
257
		}
258
	}
259
260
	/**
261
	 * This function tries to work out additional error information from a back trace.
262
	 *
263
	 * @param string $error_message
264
	 * @param string $log_message
265
	 * @param string|boolean $error_type
266
	 * @param string|null $file
267
	 * @param integer|null $line
268
	 */
269
	public function error_backtrace($error_message, $log_message = '', $error_type = false, $file = null, $line = null)
270
	{
271
		if (empty($log_message))
272
			$log_message = $error_message;
273
274
		foreach (debug_backtrace() as $step)
275
		{
276
			// Found it?
277
			if (!method_exists($this, $step['function']) && !in_array(substr($step['function'], 0, 7), array('elk_db_', 'preg_re', 'db_erro', 'call_us')))
278
			{
279
				$log_message .= '<br />Function: ' . $step['function'];
280
				break;
281
			}
282
283
			if (isset($step['line']))
284
			{
285
				$file = $step['file'];
286
				$line = $step['line'];
287
			}
288
		}
289
290
		// A special case - we want the file and line numbers for debugging.
291
		if ($error_type == 'return')
292
			return array($file, $line);
293
294
		// Is always a critical error.
295
		if (function_exists('log_error'))
296
			Errors::instance()->log_error($log_message, 'critical', $file, $line);
297
298
		if (function_exists('fatal_error'))
299
		{
300
			Errors::instance()->fatal_error($error_message, false);
301
302
			// Cannot continue...
303
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method error_backtrace() 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...
304
		}
305
		elseif ($error_type)
306
			trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''), $error_type);
307
		else
308
			trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''));
309
	}
310
311
	/**
312
	 * Escape the LIKE wildcards so that they match the character and not the wildcard.
313
	 *
314
	 * @param string $string
315
	 * @param bool $translate_human_wildcards = false, if true, turns human readable wildcards into SQL wildcards.
316
	 */
317
	public function escape_wildcard_string($string, $translate_human_wildcards = false)
318
	{
319
		$replacements = array(
320
			'%' => '\%',
321
			'_' => '\_',
322
			'\\' => '\\\\',
323
		);
324
325
		if ($translate_human_wildcards)
326
			$replacements += array(
327
				'*' => '%',
328
			);
329
330
		return strtr($string, $replacements);
331
	}
332
333
	/**
334
	 * This function optimizes a table.
335
	 *
336
	 * - reclaims storage occupied by dead tuples. In normal PostgreSQL operation, tuples
337
	 * that are deleted or obsoleted by an update are not physically removed from their table;
338
	 * they remain present until a VACUUM is done. Therefore it's necessary to do VACUUM periodically,
339
	 * especially on frequently-updated tables.
340
	 *
341
	 * @param string $table - the table to be optimized
342
	 *
343
	 * @deprecated since 1.1 - the function was moved to DbTable class
344
	 *
345
	 * @return int how much it was gained
346
	 */
347
	public function db_optimize_table($table)
348
	{
349
		$db_table = db_table();
350
351
		return $db_table->optimize($table);
352
	}
353
354
	/**
355
	 * Retrieve the connection object
356
	 *
357
	 * @return resource what? The connection
358
	 */
359
	public function connection()
360
	{
361
		// find it, find it
362
		return $this->_connection;
363
	}
364
365
	/**
366
	 * Return the number of queries executed
367
	 *
368
	 * @return int
369
	 */
370
	public function num_queries()
371
	{
372
		return $this->_query_count;
373
	}
374
375
	/**
376
	 * Defines if the class should or not return the error in case of failures.
377
	 *
378
	 * @param null|boolean $set if true the query method will not return any error
379
	 *                     if null will restore the last known value of skip_error
380
	 */
381
	public function skip_error($set = true)
382
	{
383
		if ($set === null)
384
			$this->_skip_error = $this->_old_skip_error;
385
		else
386
			$this->_old_skip_error = $this->_skip_error;
387
388
		$this->_skip_error = $set;
389
	}
390
}