Completed
Pull Request — development (#2907)
by Martyn
13:27
created

Database_Abstract::_replaceInt()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 2
Ratio 33.33 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 2
nop 2
dl 2
loc 6
ccs 4
cts 4
cp 1
crap 3
rs 9.4285
c 0
b 0
f 0
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 Release Candidate 1
16
 *
17
 */
18
19
/**
20
 * Abstract database class, implements database to control functions
21
 */
22
abstract class Database_Abstract implements Database
23
{
24
	/**
25
	 * Current connection to the database
26
	 * @var mysqli|postgre|resource
27
	 */
28
	protected $_connection = null;
29
30
	/**
31
	 * Number of queries run (may include queries from $_SESSION if is a redirect)
32
	 * @var int
33
	 */
34
	protected $_query_count = 0;
35
36
	/**
37
	 * The way to skip a database error
38
	 * @var boolean
39
	 */
40
	protected $_skip_error = false;
41
42
	/**
43
	 * MySQL supports unbuffered queries, this remembers if we are running an
44
	 * unbuffered or not
45
	 * @var boolean
46
	 */
47
	protected $_unbuffered = false;
48
49
	/**
50
	 * This holds the "values" used in the replacement__callback method
51
	 * @var mixed[]
52
	 */
53
	protected $_db_callback_values = array();
54
55
	/**
56
	 * This contains the "connection" used in the replacement__callback method
57
	 * TBH I'm not sure why $this->_connection is not used
58
	 * @var resource|object
59
	 */
60
	protected $_db_callback_connection = null;
61
62
	/**
63
	 * Private constructor.
64
	 */
65
	protected function __construct()
66
	{
67
		// Objects should be created through initiate().
68
	}
69
70
	/**
71
	 * Callback for preg_replace_callback on the query.
72
	 * It allows to replace on the fly a few pre-defined strings, for
73
	 * convenience ('query_see_board', 'query_wanna_see_board'), with
74
	 * their current values from $user_info.
75
	 * In addition, it performs checks and sanitation on the values
76
	 * sent to the database.
77
	 *
78
	 * @param mixed[] $matches
79
	 * @throws Elk_Exception
80
	 */
81 238
	public function replacement__callback($matches)
82
	{
83 238
		global $user_info, $db_prefix;
84
85
		// Connection gone???  This should *never* happen at this point, yet it does :'(
86 238
		if (!$this->validConnection($this->_db_callback_connection))
0 ignored issues
show
Documentation introduced by
$this->_db_callback_connection is of type resource|object, but the function expects a object<mysqli>|object<postgre>|null.

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...
87 160
			Errors::instance()->display_db_error();
88
89 238
		if ($matches[1] === 'db_prefix')
90 236
			return $db_prefix;
91
92 238
		if ($matches[1] === 'query_see_board')
93 166
			return $user_info['query_see_board'];
94
95 238
		if ($matches[1] === 'query_wanna_see_board')
96 162
			return $user_info['query_wanna_see_board'];
97
98 238
		if (!isset($matches[2]))
99 160
			$this->error_backtrace('Invalid value inserted or no type specified.', '', E_USER_ERROR, __FILE__, __LINE__);
100
101 238
		if (!isset($this->_db_callback_values[$matches[2]]))
102 160
			$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__);
103
104 238
		$replacement = $this->_db_callback_values[$matches[2]];
105
106 238
		switch ($matches[1])
107
		{
108 238
			case 'int':
109 166
				return $this->_replaceInt($matches[2], $replacement);
110 238
			case 'string':
111 215
			case 'text':
112 202
				return $this->_replaceString($replacement);
113 163
			case 'array_int':
114 84
				return $this->_replaceArrayInt($matches[2], $replacement);
115 151
			case 'array_string':
116 113
				return $this->_replaceArrayString($matches[2], $replacement);
117 97
			case 'date':
118 1
				return $this->_replaceDate($matches[2], $replacement);
119 96
			case 'float':
120 20
				return $this->_replaceFloat($matches[2], $replacement);
121 76
			case 'identifier':
122
				return $this->_replaceIdentifier($replacement);
123 76
			case 'raw':
124 76
				return $replacement;
125
			default:
126
				$this->error_backtrace('Undefined type used in the database query. (' . $matches[1] . ':' . $matches[2] . ')', '', false, __FILE__, __LINE__);
127
			break;
128
		}
129
130
		return '';
131
	}
132
133
	/**
134
	 * This function works like $this->query(), escapes and quotes a string,
135
	 * but it doesn't execute the query.
136
	 *
137
	 * @param string $db_string
138
	 * @param mixed[] $db_values
139
	 * @param mysqli|postgre|null $connection = null
140
	 */
141 150
	public function quote($db_string, $db_values, $connection = null)
142
	{
143
		// Only bother if there's something to replace.
144 150
		if (strpos($db_string, '{') !== false)
145 100
		{
146
			// This is needed by the callback function.
147 150
			$this->_db_callback_values = $db_values;
148 150
			if ($connection === null)
149 100
			{
150 6
				$this->_db_callback_connection = $this->_connection;
151 4
			}
152
			else
153
			{
154 150
				$this->_db_callback_connection = $connection;
155
			}
156
157
			// Do the quoting and escaping
158 150
			$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', array($this, 'replacement__callback'), $db_string);
159
160
			// Clear this variables.
161 150
			$this->_db_callback_values = array();
162 150
			$this->_db_callback_connection = null;
163 100
		}
164
165 150
		return $db_string;
166
	}
167
168
	/**
169
	 * {@inheritDoc}
170
	 */
171 6
	public function fetchQuery($db_string, $db_values = array(), $seeds = null)
172
	{
173 6
		$request = $this->query('', $db_string, $db_values);
174
175 6
		$results = $seeds !== null ? $seeds : array();
176 6
		while ($row = $this->fetch_assoc($request))
177 6
			$results[] = $row;
178 6
		$this->free_result($request);
179
180 6
		return $results;
181
	}
182
183
	/**
184
	 * {@inheritDoc}
185
	 */
186 20
	public function fetchQueryCallback($db_string, $db_values = array(), $callback = '', $seeds = null)
187
	{
188 20
		if ($callback === '')
189 13
			return $this->fetchQuery($db_string, $db_values);
190
191 20
		$request = $this->query('', $db_string, $db_values);
192
193 20
		$results = $seeds !== null ? $seeds : array();
194 20
		while ($row = $this->fetch_assoc($request))
195 20
			$results[] = $callback($row);
196 20
		$this->free_result($request);
197
198 20
		return $results;
199
	}
200
201
	/**
202
	 * This function combines the keys and values of the data passed to db::insert.
203
	 *
204
	 * @param integer[] $keys
205
	 * @param mixed[] $values
206
	 * @return mixed[]
207
	 */
208 150
	protected function _array_combine($keys, $values)
209
	{
210 150
		$is_numeric = array_filter(array_keys($values), 'is_numeric');
211
212 150
		if (!empty($is_numeric))
213 150
			return array_combine($keys, $values);
214
		else
215
		{
216 70
			$combined = array();
217 70
			foreach ($keys as $key)
218
			{
219 70
				if (isset($values[$key]))
220 70
					$combined[$key] = $values[$key];
221 48
			}
222
223
			// @todo should throw an E_WARNING if count($combined) != count($keys)
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
224 70
			return $combined;
225
		}
226
	}
227
228
	/**
229
	 * Tests and casts integers for replacement__callback.
230
	 *
231
	 * @param mixed $identifier
232
	 * @param mixed $replacement
233
	 * @return string
234
	 * @throws Elk_Exception
235
	 */
236 166
	protected function _replaceInt($identifier, $replacement)
237
	{
238 166 View Code Duplication
		if (!is_numeric($replacement) || (string) $replacement !== (string) (int) $replacement)
239 110
			$this->error_backtrace('Wrong value type sent to the database. Integer expected. (' . $identifier . ')', '', E_USER_ERROR, __FILE__, __LINE__);
240 166
		return (string) (int) $replacement;
241
	}
242
243
	/**
244
	 * Tests and casts arrays of integers for replacement__callback.
245
	 *
246
	 * @param string $identifier
247
	 * @param mixed[] $replacement
248
	 * @return string
249
	 * @throws Elk_Exception
250
	 */
251 84
	protected function _replaceArrayInt($identifier, $replacement)
252
	{
253 84
			if (is_array($replacement))
254 57
			{
255 84
				if (empty($replacement))
256 57
					$this->error_backtrace('Database error, given array of integer values is empty. (' . $identifier . ')', '', E_USER_ERROR, __FILE__, __LINE__);
257
258 84
				foreach ($replacement as $key => $value)
259
				{
260 84 View Code Duplication
					if (!is_numeric($value) || (string) $value !== (string) (int) $value)
261 57
						$this->error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $identifier . ')', '', E_USER_ERROR, __FILE__, __LINE__);
262
263 84
					$replacement[$key] = (string) (int) $value;
264 57
				}
265
266 84
				return implode(', ', $replacement);
267
			}
268
			else
269
			{
270
				$this->error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $identifier . ')', '', E_USER_ERROR, __FILE__, __LINE__);
271
			}
272
	}
273
274
	/**
275
	 * Casts values to string for replacement__callback.
276
	 *
277
	 * @param mixed $replacement
278
	 * @return string
279
	 */
280 202
	protected function _replaceString($replacement)
281
	{
282 202
		return sprintf('\'%1$s\'', $this->escape_string($replacement));
283
	}
284
285
	/**
286
	 * Tests and casts arrays of strings for replacement__callback.
287
	 *
288
	 * @param string $identifier
289
	 * @param mixed[] $replacement
290
	 * @return string
291
	 * @throws Elk_Exception
292
	 */
293 113
	protected function _replaceArrayString($identifier, $replacement)
294
	{
295 113
		if (is_array($replacement))
296 75
		{
297 113
			if (empty($replacement))
298 75
				$this->error_backtrace('Database error, given array of string values is empty. (' . $identifier . ')', '', E_USER_ERROR, __FILE__, __LINE__);
299
300 113
			foreach ($replacement as $key => $value)
301 113
				$replacement[$key] = sprintf('\'%1$s\'', $this->escape_string($value));
302
303 113
			return implode(', ', $replacement);
304
		}
305
		else
306
		{
307
			$this->error_backtrace('Wrong value type sent to the database. Array of strings expected. (' . $identifier . ')', '', E_USER_ERROR, __FILE__, __LINE__);
308
		}
309
	}
310
311
	/**
312
	 * Tests and casts date for replacement__callback.
313
	 *
314
	 * @param mixed $identifier
315
	 * @param mixed $replacement
316
	 * @return string
317
	 * @throws Elk_Exception
318
	 */
319 1
	protected function _replaceDate($identifier, $replacement)
320
	{
321 1
		if (preg_match('~^(\d{4})-([0-1]?\d)-([0-3]?\d)$~', $replacement, $date_matches) === 1)
322 1
			return sprintf('\'%04d-%02d-%02d\'', $date_matches[1], $date_matches[2], $date_matches[3]);
323
		else
324
			$this->error_backtrace('Wrong value type sent to the database. Date expected. (' . $identifier . ')', '', E_USER_ERROR, __FILE__, __LINE__);
325
	}
326
327
	/**
328
	 * Tests and casts floating numbers for replacement__callback.
329
	 *
330
	 * @param mixed $identifier
331
	 * @param mixed $replacement
332
	 * @return string
333
	 * @throws Elk_Exception
334
	 */
335 20
	protected function _replaceFloat($identifier, $replacement)
336
	{
337 20
		if (!is_numeric($replacement))
338 12
			$this->error_backtrace('Wrong value type sent to the database. Floating point number expected. (' . $identifier . ')', '', E_USER_ERROR, __FILE__, __LINE__);
339 20
		return (string) (float) $replacement;
340
	}
341
342
	/**
343
	 * Quotes identifiers for replacement__callback.
344
	 *
345
	 * @param mixed $replacement
346
	 * @return string
347
	 * @throws Elk_Exception
348
	 */
349 View Code Duplication
	protected function _replaceIdentifier($replacement)
350
	{
351
		if (preg_match('~[a-z_][0-9,a-z,A-Z$_]{0,60}~', $replacement) !== 1)
352
		{
353
			$this->error_backtrace('Wrong value type sent to the database. Invalid identifier used. (' . $replacement . ')', '', E_USER_ERROR, __FILE__, __LINE__);
354
		}
355
356
		return '`' . $replacement . '`';
357
	}
358
359
	/**
360
	 * This function tries to work out additional error information from a back trace.
361
	 *
362
	 * @param string         $error_message
363
	 * @param string         $log_message
364
	 * @param string|boolean $error_type
365
	 * @param string|null    $file
366
	 * @param integer|null   $line
367
	 *
368
	 * @return array
369
	 * @throws Elk_Exception
370
	 */
371 45
	public function error_backtrace($error_message, $log_message = '', $error_type = false, $file = null, $line = null)
372
	{
373 45
		if (empty($log_message))
374 30
			$log_message = $error_message;
375
376 45
		foreach (debug_backtrace() as $step)
377
		{
378
			// Found it?
379 45
			if (!method_exists($this, $step['function']) && !in_array(substr($step['function'], 0, 7), array('elk_db_', 'preg_re', 'db_erro', 'call_us')))
380 30
			{
381 45
				$log_message .= '<br />Function: ' . $step['function'];
382 45
				break;
383
			}
384
385 45
			if (isset($step['line']))
386 30
			{
387 45
				$file = $step['file'];
388 45
				$line = $step['line'];
389 30
			}
390 30
		}
391
392
		// A special case - we want the file and line numbers for debugging.
393 45
		if ($error_type == 'return')
394 45
			return array($file, $line);
395
396
		// Is always a critical error.
397
		if (class_exists('Errors'))
398
		{
399
			Errors::instance()->log_error($log_message, 'critical', $file, $line);
400
		}
401
402
		if (class_exists('Elk_Exception'))
403
		{
404
			throw new Elk_Exception($error_message, false);
405
		}
406 View Code Duplication
		elseif ($error_type)
407
			trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''), $error_type);
408 View Code Duplication
		else
409
			trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''));
410
411
		return array('', '');
412
	}
413
414
	/**
415
	 * Escape the LIKE wildcards so that they match the character and not the wildcard.
416
	 *
417
	 * @param string $string
418
	 * @param bool $translate_human_wildcards = false, if true, turns human readable wildcards into SQL wildcards.
419
	 */
420
	public function escape_wildcard_string($string, $translate_human_wildcards = false)
421
	{
422
		$replacements = array(
423
			'%' => '\%',
424
			'_' => '\_',
425
			'\\' => '\\\\',
426
		);
427
428
		if ($translate_human_wildcards)
429
			$replacements += array(
430
				'*' => '%',
431
			);
432
433
		return strtr($string, $replacements);
434
	}
435
436
	/**
437
	 * This function optimizes a table.
438
	 *
439
	 * - reclaims storage occupied by dead tuples. In normal PostgreSQL operation, tuples
440
	 * that are deleted or obsoleted by an update are not physically removed from their table;
441
	 * they remain present until a VACUUM is done. Therefore it's necessary to do VACUUM periodically,
442
	 * especially on frequently-updated tables.
443
	 *
444
	 * @param string $table - the table to be optimized
445
	 *
446
	 * @deprecated since 1.1 - the function was moved to DbTable class
447
	 *
448
	 * @return int how much it was gained
449
	 */
450
	public function db_optimize_table($table)
451
	{
452
		$db_table = db_table();
453
454
		return $db_table->optimize($table);
455
	}
456
457
	/**
458
	 * Retrieve the connection object
459
	 *
460
	 * @return resource what? The connection
461
	 */
462
	public function connection()
463
	{
464
		// find it, find it
465
		return $this->_connection;
466
	}
467
468
	/**
469
	 * Return the number of queries executed
470
	 *
471
	 * @return int
472
	 */
473
	public function num_queries()
474
	{
475
		return $this->_query_count;
476
	}
477
478
	/**
479
	 * {@inheritDoc}
480
	 */
481 33
	public function skip_next_error()
482
	{
483 33
		$this->_skip_error = true;
484 33
	}
485
486
	/**
487
	 * Set the unbuffered state for the connection
488
	 *
489
	 * @param bool $state
490
	 */
491
	public function setUnbuffered($state)
492
	{
493
		$this->_unbuffered = (bool) $state;
494
	}
495
496
	/**
497
	 * Finds out if the connection is still valid.
498
	 *
499
	 * @param mysqli|postgre|null $connection = null
500
	 */
501
	public function validConnection($connection = null)
502
	{
503
		return (bool) $connection;
504
	}
505
}
506