Completed
Branch development (176841)
by Elk
06:59
created

AbstractQuery::_replaceArrayInt()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 22

Duplication

Lines 2
Ratio 9.09 %

Code Coverage

Tests 7
CRAP Score 7.7305

Importance

Changes 0
Metric Value
cc 6
nc 7
nop 2
dl 2
loc 22
ccs 7
cts 11
cp 0.6364
crap 7.7305
rs 8.9457
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
 * @package   ElkArte Forum
8
 * @copyright ElkArte Forum contributors
9
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
10
 *
11
 * This file contains code covered by:
12
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
13
 *
14
 * @version 2.0 dev
15
 *
16
 */
17
18
namespace ElkArte\Database;
19
20
/**
21
 * Abstract database class, implements database to control functions
22
 */
23
abstract class AbstractQuery implements QueryInterface
24
{
25
	/**
26
	 * Current connection to the database
27
	 * @var \ElkArte\Database\ConnectionInterface
28
	 */
29
	protected $connection = null;
30
31
	/**
32
	 * Number of queries run (may include queries from $_SESSION if is a redirect)
33
	 * @var int
34
	 */
35
	protected $_query_count = 0;
36
37
	/**
38
	 * The way to skip a database error
39
	 * @var boolean
40
	 */
41
	protected $_skip_error = false;
42
43
	/**
44
	 * The tables prefix
45
	 * @var string
46
	 */
47
	protected $_db_prefix = '';
48
49
	/**
50
	 * String to match visible boards.
51
	 * By default set to a false, so that unless it is set, nothing is returned.
52
	 * @var string
53
	 */
54
	protected $query_see_board = '1!=1';
55
56
	/**
57
	 * String to match boards the user want to see.
58
	 * By default set to a false, so that unless it is set, nothing is returned.
59
	 * @var string
60
	 */
61
	protected $query_wanna_see_board = '1!=1';
62
63
	/**
64
	 * MySQL supports unbuffered queries, this remembers if we are running an
65
	 * unbuffered or not
66
	 * @var boolean
67
	 */
68
	protected $_unbuffered = false;
69
70
	/**
71
	 * This holds the "values" used in the replacement__callback method
72
	 * @var array
73
	 */
74
	protected $_db_callback_values = array();
75
76
	/**
77
	 * Temporary variable to support the migration to the new db-layer
78
	 * Ideally to be removed before 2.0 shipment
79
	 * @var \ElkArte\Database\AbstractResult
80
	 */
81
	protected $result = null;
82
83
	/**
84
	 * Comments that are allowed in a query are preg_removed.
85
	 * These replacements happen in the query checks.
86
	 * @var string[]
87
	 */
88
	protected $allowed_comments = [
89
		'from' => [
90
			'~\s+~s',
91
			'~/\*!40001 SQL_NO_CACHE \*/~',
92
			'~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~',
93
			'~/\*!40100 ON DUPLICATE KEY UPDATE id_msg = \d+ \*/~',
94
		],
95
		'to' => [
96
			' ',
97
			'',
98
			'',
99
			'',
100
		]
101
	];
102
103
	/**
104
	 * Holds some values (time, file, line, delta) to debug performancs of the queries.
105
	 * @var mixed[]
106
	 */
107
	protected $db_cache = [];
108
109
	/**
110
	 * The debug object.
111
	 * @var \ElkArte\Debug
112
	 */
113
	protected $_debug = null;
114
115
	/**
116
	 * Constructor.
117
	 *
118
	 * @param string $db_prefix Guess what? The tables prefix
119
	 * @param resource|object $connection Obviously the database connection
120
	 */
121 1
	public function __construct($db_prefix, $connection)
122
	{
123 1
		global $db_show_debug;
124
125 1
		$this->_db_prefix = $db_prefix;
126 1
		$this->connection = $connection;
0 ignored issues
show
Documentation Bug introduced by
It seems like $connection can also be of type resource. However, the property $connection is declared as type object<ElkArte\Database\ConnectionInterface>. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
127
128
		// Debugging.
129 1
		if ($db_show_debug === true)
130
		{
131
			$this->_debug = \ElkArte\Debug::instance();
132
		}
133 1
	}
134
135
	/**
136
	 * {@inheritDoc}
137
	 */
138
	abstract public function query($identifier, $db_string, $db_values = array());
139
140
	/**
141
	 * {@inheritDoc}
142
	 */
143
	abstract public function transaction($type = 'commit');
144
145
	/**
146
	 * {@inheritDoc}
147
	 */
148
	abstract public function last_error();
149
150
	/**
151
	 * Public setter for the string that defines which boards the user can see.
152
	 * @param string $string
153
	 */
154 3
	public function setSeeBoard($string)
155
	{
156 3
		$this->query_see_board = $string;
157 3
	}
158
159
	/**
160
	 * Public setter for the string that defines which boards the user want to see.
161
	 * @param string $string
162
	 */
163 3
	public function setWannaSeeBoard($string)
164
	{
165 3
		$this->query_wanna_see_board = $string;
166 3
	}
167
168
	/**
169
	 * Callback for preg_replace_callback on the query.
170
	 * It allows to replace on the fly a few pre-defined strings, for
171
	 * convenience ('query_see_board', 'query_wanna_see_board'), with
172
	 * their current values from User::$info.
173
	 * In addition, it performs checks and sanitation on the values
174
	 * sent to the database.
175
	 *
176
	 * @param mixed[] $matches
177
	 *
178
	 * @return mixed|string
179
	 * @throws \ElkArte\Exceptions\Exception
180
	 */
181 87
	public function replacement__callback($matches)
182
	{
183
		// Connection gone???  This should *never* happen at this point, yet it does :'(
184 87
		if (!$this->validConnection())
185
		{
186
			\ElkArte\Errors\Errors::instance()->display_db_error('ElkArte\\Database\\AbstractQuery::replacement__callback');
187
		}
188
189 87
		switch ($matches[1])
190
		{
191 87
			case 'db_prefix':
192 81
				return $this->_db_prefix;
193 85
			case 'query_see_board':
194 9
				return $this->query_see_board;
195 85
			case 'query_wanna_see_board':
196 4
				return $this->query_wanna_see_board;
197 85
			case 'column_case_insensitive':
198 6
				return $this->_replaceColumnCaseInsensitive($matches[2]);
199
		}
200
201 85
		if (!isset($matches[2]))
202
		{
203
			$this->error_backtrace('Invalid value inserted or no type specified.', '', E_USER_ERROR, __FILE__, __LINE__);
0 ignored issues
show
Documentation introduced by
E_USER_ERROR is of type integer, but the function expects a boolean.

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...
204
		}
205
206 85
		if (!isset($this->_db_callback_values[$matches[2]]))
207
		{
208
			$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__);
0 ignored issues
show
Documentation introduced by
E_USER_ERROR is of type integer, but the function expects a boolean.

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...
209
		}
210
211 85
		$replacement = $this->_db_callback_values[$matches[2]];
212
213 85
		switch ($matches[1])
214
		{
215 85
			case 'int':
216 59
				return $this->_replaceInt($matches[2], $replacement);
217 85
			case 'string':
218 64
			case 'text':
219 71
				return $this->_replaceString($replacement);
220 64
			case 'string_case_sensitive':
221 2
				return $this->_replaceStringCaseSensitive($replacement);
222 64
			case 'string_case_insensitive':
223 6
				return $this->_replaceStringCaseInsensitive($replacement);
224 62
			case 'array_int':
225 31
				return $this->_replaceArrayInt($matches[2], $replacement);
226 60
			case 'array_string':
227 38
				return $this->_replaceArrayString($matches[2], $replacement);
228 43
			case 'array_string_case_insensitive':
229 2
				return $this->_replaceArrayStringCaseInsensitive($matches[2], $replacement);
230 41
			case 'date':
231 1
				return $this->_replaceDate($matches[2], $replacement);
232 40
			case 'float':
233 15
				return $this->_replaceFloat($matches[2], $replacement);
234 28
			case 'identifier':
235
				return $this->_replaceIdentifier($replacement);
236 28
			case 'raw':
237 28
				return $replacement;
238
			default:
239
				$this->error_backtrace('Undefined type used in the database query. (' . $matches[1] . ':' . $matches[2] . ')', '', false, __FILE__, __LINE__);
240
			break;
241
		}
242
243
		return '';
244
	}
245
246
	/**
247
	 * {@inheritDoc}
248
	 */
249 61
	public function quote($db_string, $db_values)
250
	{
251
		// Only bother if there's something to replace.
252 61
		if (strpos($db_string, '{') !== false)
253
		{
254
			// This is needed by the callback function.
255 61
			$this->_db_callback_values = $db_values;
256
257
			// Do the quoting and escaping
258 61
			$db_string = preg_replace_callback('~{([a-z_]+)(?::([\.a-zA-Z0-9_-]+))?}~', array($this, 'replacement__callback'), $db_string);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $db_string. This often makes code more readable.
Loading history...
259
260
			// Clear this variables.
261 61
			$this->_db_callback_values = array();
262
		}
263
264 61
		return $db_string;
265
	}
266
267
	/**
268
	 * {@inheritDoc}
269
	 */
270 18
	public function fetchQuery($db_string, $db_values = array())
271
	{
272 18
		return $this->query('', $db_string, $db_values);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->query('', $db_string, $db_values); (boolean|resource) is incompatible with the return type declared by the interface ElkArte\Database\QueryInterface::fetchQuery of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
273
	}
274
275
	/**
276
	 * This function combines the keys and values of the data passed to db::insert.
277
	 *
278
	 * @param integer[] $keys
279
	 * @param mixed[] $values
280
	 * @return mixed[]
281
	 */
282 59
	protected function _array_combine($keys, $values)
283
	{
284 59
		$is_numeric = array_filter(array_keys($values), 'is_numeric');
285
286 59
		if (!empty($is_numeric))
287 59
			return array_combine($keys, $values);
288
		else
289
		{
290 26
			$combined = array();
291 26
			foreach ($keys as $key)
292
			{
293 26
				if (isset($values[$key]))
294 26
					$combined[$key] = $values[$key];
295
			}
296
297
			// @todo should throw an E_WARNING if count($combined) != count($keys)
298 26
			return $combined;
299
		}
300
	}
301
302
	/**
303
	 * Tests and casts integers for replacement__callback.
304
	 *
305
	 * @param mixed $identifier
306
	 * @param mixed $replacement
307
	 * @return string
308
	 * @throws \ElkArte\Exceptions\Exception
309
	 */
310 59
	protected function _replaceInt($identifier, $replacement)
311
	{
312 59 View Code Duplication
		if (!is_numeric($replacement) || (string) $replacement !== (string) (int) $replacement)
313
			$this->error_backtrace('Wrong value type sent to the database. Integer expected. (' . $identifier . ')', '', E_USER_ERROR, __FILE__, __LINE__);
0 ignored issues
show
Documentation introduced by
E_USER_ERROR is of type integer, but the function expects a boolean.

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...
314 59
		return (string) (int) $replacement;
315
	}
316
317
	/**
318
	 * Tests and casts arrays of integers for replacement__callback.
319
	 *
320
	 * @param string $identifier
321
	 * @param mixed[] $replacement
322
	 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
323
	 * @throws \ElkArte\Exceptions\Exception
324
	 */
325 31
	protected function _replaceArrayInt($identifier, $replacement)
326
	{
327 31
			if (is_array($replacement))
328
			{
329 31
				if (empty($replacement))
330
					$this->error_backtrace('Database error, given array of integer values is empty. (' . $identifier . ')', '', E_USER_ERROR, __FILE__, __LINE__);
0 ignored issues
show
Documentation introduced by
E_USER_ERROR is of type integer, but the function expects a boolean.

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...
331
332 31
				foreach ($replacement as $key => $value)
333
				{
334 31 View Code Duplication
					if (!is_numeric($value) || (string) $value !== (string) (int) $value)
335
						$this->error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $identifier . ')', '', E_USER_ERROR, __FILE__, __LINE__);
0 ignored issues
show
Documentation introduced by
E_USER_ERROR is of type integer, but the function expects a boolean.

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...
336
337 31
					$replacement[$key] = (string) (int) $value;
338
				}
339
340 31
				return implode(', ', $replacement);
341
			}
342
			else
343
			{
344
				$this->error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $identifier . ')', '', E_USER_ERROR, __FILE__, __LINE__);
0 ignored issues
show
Documentation introduced by
E_USER_ERROR is of type integer, but the function expects a boolean.

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...
345
			}
346
	}
347
348
	/**
349
	 * Casts values to string for replacement__callback.
350
	 *
351
	 * @param mixed $replacement
352
	 * @return string
353
	 */
354 75
	protected function _replaceString($replacement)
355
	{
356 75
		return sprintf('\'%1$s\'', $this->escape_string($replacement));
357
	}
358
359
	/**
360
	 * Casts values to string for replacement__callback and in the DBMS that
361
	 * require this solution makes it so that the comparison will be case sensitive.
362
	 *
363
	 * @param mixed $replacement
364
	 * @return string
365
	 */
366 1
	protected function _replaceStringCaseSensitive($replacement)
367
	{
368 1
		return $this->_replaceString($replacement);
369
	}
370
371
	/**
372
	 * Casts values to LOWER(string) for replacement__callback.
373
	 *
374
	 * @param mixed $replacement
375
	 * @return string
376
	 */
377 3
	protected function _replaceStringCaseInsensitive($replacement)
378
	{
379 3
		return 'LOWER(' . $this->_replaceString($replacement) . ')';
380
	}
381
382
	/**
383
	 * Casts the column to LOWER(column_name) for replacement__callback.
384
	 *
385
	 * @param mixed $replacement
386
	 * @return string
387
	 */
388 3
	protected function _replaceColumnCaseInsensitive($replacement)
389
	{
390 3
		return 'LOWER(' . $replacement . ')';
391
	}
392
393
	/**
394
	 * Tests and casts arrays of strings for replacement__callback.
395
	 *
396
	 * @param string $identifier
397
	 * @param mixed[] $replacement
398
	 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
399
	 * @throws \ElkArte\Exceptions\Exception
400
	 */
401 38 View Code Duplication
	protected function _replaceArrayString($identifier, $replacement)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
402
	{
403 38
		if (is_array($replacement))
404
		{
405 38
			if (empty($replacement))
406
			{
407
				$this->error_backtrace('Database error, given array of string values is empty. (' . $identifier . ')', '', E_USER_ERROR, __FILE__, __LINE__);
0 ignored issues
show
Documentation introduced by
E_USER_ERROR is of type integer, but the function expects a boolean.

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...
408
			}
409
410 38
			foreach ($replacement as $key => $value)
411
			{
412 38
				$replacement[$key] = sprintf('\'%1$s\'', $this->escape_string($value));
413
			}
414
415 38
			return implode(', ', $replacement);
416
		}
417
		else
418
		{
419
			$this->error_backtrace('Wrong value type sent to the database. Array of strings expected. (' . $identifier . ')', '', E_USER_ERROR, __FILE__, __LINE__);
0 ignored issues
show
Documentation introduced by
E_USER_ERROR is of type integer, but the function expects a boolean.

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...
420
		}
421
	}
422
423
	/**
424
	 * Tests and casts to LOWER(column_name) (if needed) arrays of strings
425
	 * for replacement__callback.
426
	 *
427
	 * @param string $identifier
428
	 * @param mixed[] $replacement
429
	 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
430
	 * @throws \ElkArte\Exceptions\Exception
431
	 */
432 2 View Code Duplication
	protected function _replaceArrayStringCaseInsensitive($identifier, $replacement)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
433
	{
434 2
		if (is_array($replacement))
435
		{
436 2
			if (empty($replacement))
437
			{
438
				$this->error_backtrace('Database error, given array of string values is empty. (' . $identifier . ')', '', E_USER_ERROR, __FILE__, __LINE__);
0 ignored issues
show
Documentation introduced by
E_USER_ERROR is of type integer, but the function expects a boolean.

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...
439
			}
440
441 2
			foreach ($replacement as $key => $value)
442
			{
443 2
				$replacement[$key] = $this->_replaceStringCaseInsensitive($value);
444
			}
445
446 2
			return implode(', ', $replacement);
447
		}
448
		else
449
		{
450
			$this->error_backtrace('Wrong value type sent to the database. Array of strings expected. (' . $identifier . ')', '', E_USER_ERROR, __FILE__, __LINE__);
0 ignored issues
show
Documentation introduced by
E_USER_ERROR is of type integer, but the function expects a boolean.

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...
451
		}
452
	}
453
454
	/**
455
	 * Tests and casts date for replacement__callback.
456
	 *
457
	 * @param mixed $identifier
458
	 * @param mixed $replacement
459
	 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
460
	 * @throws \ElkArte\Exceptions\Exception
461
	 */
462 1
	protected function _replaceDate($identifier, $replacement)
463
	{
464 1
		if (preg_match('~^(\d{4})-([0-1]?\d)-([0-3]?\d)$~', $replacement, $date_matches) === 1)
465 1
			return sprintf('\'%04d-%02d-%02d\'', $date_matches[1], $date_matches[2], $date_matches[3]);
466
		else
467
			$this->error_backtrace('Wrong value type sent to the database. Date expected. (' . $identifier . ')', '', E_USER_ERROR, __FILE__, __LINE__);
0 ignored issues
show
Documentation introduced by
E_USER_ERROR is of type integer, but the function expects a boolean.

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...
468
	}
469
470
	/**
471
	 * Tests and casts floating numbers for replacement__callback.
472
	 *
473
	 * @param mixed $identifier
474
	 * @param mixed $replacement
475
	 * @return string
476
	 * @throws \ElkArte\Exceptions\Exception
477
	 */
478 15
	protected function _replaceFloat($identifier, $replacement)
479
	{
480 15
		if (!is_numeric($replacement))
481
			$this->error_backtrace('Wrong value type sent to the database. Floating point number expected. (' . $identifier . ')', '', E_USER_ERROR, __FILE__, __LINE__);
0 ignored issues
show
Documentation introduced by
E_USER_ERROR is of type integer, but the function expects a boolean.

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...
482 15
		return (string) (float) $replacement;
483
	}
484
485
	/**
486
	 * Quotes identifiers for replacement__callback.
487
	 *
488
	 * @param mixed $replacement
489
	 * @return string
490
	 * @throws \ElkArte\Exceptions\Exception
491
	 */
492 View Code Duplication
	protected function _replaceIdentifier($replacement)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
493
	{
494
		if (preg_match('~[a-z_][0-9,a-z,A-Z$_]{0,60}~', $replacement) !== 1)
495
		{
496
			$this->error_backtrace('Wrong value type sent to the database. Invalid identifier used. (' . $replacement . ')', '', E_USER_ERROR, __FILE__, __LINE__);
0 ignored issues
show
Documentation introduced by
E_USER_ERROR is of type integer, but the function expects a boolean.

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...
497
		}
498
499
		return '`' . $replacement . '`';
500
	}
501
502
	/**
503
	 * Checks for "illegal characters" and runs replacement__callback if not
504
	 * overriden.
505
	 * In case of problems, the method can ends up dying.
506
	 *
507
	 * @param string $db_string
508
	 * @param mixed $db_values
509
	 * @return null|string|string[]
510
	 */
511 85
	protected function _prepareQuery($db_string, $db_values)
512
	{
513 85
		global $modSettings;
514
515 85
		if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override']))
516
		{
517 27
			$this->error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__);
518
		}
519
520 85
		if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false))
521
		{
522
			// Store these values for use in the callback function.
523 83
			$this->_db_callback_values = $db_values;
0 ignored issues
show
Documentation Bug introduced by
It seems like $db_values of type * is incompatible with the declared type array of property $_db_callback_values.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
524
525
			// Inject the values passed to this function.
526 83
			$count = -1;
527 83
			while (($count > 0 && isset($db_values['recursive'])) || $count === -1)
528
			{
529 83
				$db_string = preg_replace_callback('~{([a-z_]+)(?::([\.a-zA-Z0-9_-]+))?}~', array($this, 'replacement__callback'), $db_string, -1, $count);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $db_string. This often makes code more readable.
Loading history...
530
			}
531
532
			// No need for them any longer.
533 83
			$this->_db_callback_values = array();
534
		}
535
536 85
		return $db_string;
537
	}
538
539
	/**
540
	 * Tracks the initial status (time, file/line, query) for performance evaluation.
541
	 *
542
	 * @param string $db_string
543
	 */
544 85
	protected function _preQueryDebug($db_string)
545
	{
546 85
		global $db_show_debug, $time_start;
547
548
		// Debugging.
549 85
		if ($db_show_debug === true)
550
		{
551
			// Get the file and line number this function was called.
552
			list ($file, $line) = $this->error_backtrace('', '', 'return', __FILE__, __LINE__);
0 ignored issues
show
Documentation introduced by
'return' is of type string, but the function expects a boolean.

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...
553
554
			if (!empty($_SESSION['debug_redirect']))
555
			{
556
				$this->_debug->merge_db($_SESSION['debug_redirect']);
557
				// @todo this may be off by 1
558
				$this->_query_count += count($_SESSION['debug_redirect']);
559
				$_SESSION['debug_redirect'] = array();
560
			}
561
562
			// Don't overload it.
563
			$st = microtime(true);
564
			$this->db_cache = [];
565
			$this->db_cache['q'] = $this->_query_count < 50 ? $db_string : '...';
566
			$this->db_cache['f'] = $file;
567
			$this->db_cache['l'] = $line;
568
			$this->db_cache['s'] = $st - $time_start;
569
			$this->db_cache['st'] = $st;
570
		}
571 85
	}
572
573
	/**
574
	 * Closes up the tracking and stores everything in the debug class.
575
	 */
576 85
	protected function _postQueryDebug()
577
	{
578 85
		global $db_show_debug;
579
580 85
		if ($db_show_debug === true)
581
		{
582
			$this->db_cache['t'] = microtime(true) - $this->db_cache['st'];
583
			$this->_debug->db_query($this->db_cache);
584
			$this->db_cache = [];
585
		}
586 85
	}
587
588
	/**
589
	 * Checks the query doesn't have nasty stuff in it.
590
	 * In case of problems, the method can ends up dying.
591
	 *
592
	 * @param string $db_string
593
	 * @param string $escape_char
594
	 */
595 85
	protected function _doSanityCheck($db_string, $escape_char)
596
	{
597 85
		global $modSettings;
598
599
		// First, we clean strings out of the query, reduce whitespace, lowercase, and trim - so we can check it over.
600 85
		$clean = '';
601 85
		if (empty($modSettings['disableQueryCheck']))
602
		{
603 85
			$old_pos = 0;
604 85
			$pos = -1;
605 85
			while (true)
606
			{
607 85
				$pos = strpos($db_string, '\'', $pos + 1);
608 85
				if ($pos === false)
609 85
					break;
610 77
				$clean .= substr($db_string, $old_pos, $pos - $old_pos);
611
612 77
				while (true)
613
				{
614 77
					$pos1 = strpos($db_string, '\'', $pos + 1);
615 77
					$pos2 = strpos($db_string, $escape_char, $pos + 1);
616
617 77
					if ($pos1 === false)
618
						break;
619 77
					elseif ($pos2 === false || $pos2 > $pos1)
620
					{
621 77
						$pos = $pos1;
622 77
						break;
623
					}
624
625 16
					$pos = $pos2 + 1;
626
				}
627
628 77
				$clean .= ' %s ';
629 77
				$old_pos = $pos + 1;
630
			}
631
632 85
			$clean .= substr($db_string, $old_pos);
633 85
			$clean = trim(strtolower(preg_replace($this->allowed_comments['from'], $this->allowed_comments['to'], $clean)));
634
635
			// Comments?  We don't use comments in our queries, we leave 'em outside!
636 85
			if (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false)
637
				$fail = true;
638
			// Trying to change passwords, slow us down, or something?
639 85
			elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[_a-z])~s', $clean) != 0)
640
				$fail = true;
641 85
			elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
642
				$fail = true;
643
644 85
			if (!empty($fail) && class_exists('\\ElkArte\\Errors\\Errors'))
645
			{
646
				$this->error_backtrace('Hacking attempt...', 'Hacking attempt...' . "\n" . $db_string, E_USER_ERROR, __FILE__, __LINE__);
0 ignored issues
show
Documentation introduced by
E_USER_ERROR is of type integer, but the function expects a boolean.

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...
647
			}
648
		}
649 85
	}
650
651
	/**
652
	 * {@inheritDoc}
653
	 */
654
	abstract public function error($db_string);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
655
656
	/**
657
	 * {@inheritDoc}
658
	 */
659
	abstract public function insert($method = 'replace', $table, $columns, $data, $keys, $disable_trans = false);
660
661
	/**
662
	 * {@inheritDoc}
663
	 */
664 27
	public function error_backtrace($error_message, $log_message = '', $error_type = false, $file = null, $line = null)
665
	{
666 27
		if (empty($log_message))
667
			$log_message = $error_message;
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $log_message. This often makes code more readable.
Loading history...
668
669 27
		foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $step)
670
		{
671
			// Found it?
672 27
			if (!method_exists($this, $step['function']) && !in_array(substr($step['function'], 0, 7), array('elk_db_', 'preg_re', 'db_erro', 'call_us')))
673
			{
674 27
				$log_message .= '<br />Function: ' . $step['function'];
675 27
				break;
676
			}
677
678 27
			if (isset($step['line']))
679
			{
680 27
				$file = $step['file'];
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $file. This often makes code more readable.
Loading history...
681 27
				$line = $step['line'];
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $line. This often makes code more readable.
Loading history...
682
			}
683
		}
684
685
		// A special case - we want the file and line numbers for debugging.
686 27
		if ($error_type == 'return')
687 27
			return array($file, $line);
688
689
		// Is always a critical error.
690
		if (class_exists('\\ElkArte\\Errors\\Errors'))
691
		{
692
			\ElkArte\Errors\Errors::instance()->log_error($log_message, 'critical', $file, $line);
693
		}
694
695
		if (class_exists('\\ElkArte\\Exceptions\\Exception'))
696
		{
697
			throw new \ElkArte\Exceptions\Exception([false, $error_message], false);
0 ignored issues
show
Documentation introduced by
array(false, $error_message) is of type array<integer,false|stri...:"false","1":"string"}>, but the function expects a string|array<integer,string>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
698
		}
699 View Code Duplication
		elseif ($error_type)
700
			trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''), $error_type);
701 View Code Duplication
		else
702
			trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''));
703
704
		return array('', '');
705
	}
706
707
	/**
708
	 * {@inheritDoc}
709
	 */
710
	public function escape_wildcard_string($string, $translate_human_wildcards = false)
711
	{
712
		$replacements = array(
713
			'%' => '\%',
714
			'_' => '\_',
715
			'\\' => '\\\\',
716
		);
717
718
		if ($translate_human_wildcards)
719
			$replacements += array(
720
				'*' => '%',
721
			);
722
723
		return strtr($string, $replacements);
724
	}
725
726
	/**
727
	 * {@inheritDoc}
728
	 */
729
	public function connection()
730
	{
731
		// find it, find it
732
		return $this->connection;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->connection; (ElkArte\Database\ConnectionInterface) is incompatible with the return type declared by the interface ElkArte\Database\QueryInterface::connection of type resource.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
733
	}
734
735
	/**
736
	 * {@inheritDoc}
737
	 */
738
	public function num_queries()
739
	{
740
		return $this->_query_count;
741
	}
742
743
	/**
744
	 * {@inheritDoc}
745
	 */
746 28
	public function skip_next_error()
747
	{
748 28
		$this->_skip_error = true;
749 28
	}
750
751
	/**
752
	 * Set the unbuffered state for the connection
753
	 *
754
	 * @param bool $state
755
	 */
756
	public function setUnbuffered($state)
757
	{
758
		$this->_unbuffered = (bool) $state;
759
	}
760
761
	/**
762
	 * Finds out if the connection is still valid.
763
	 *
764
	 * @return bool
765
	 */
766
	public function validConnection()
767
	{
768
		return (bool) $this->connection;
769
	}
770
771
	/**
772
	 *  Get the version number.
773
	 *
774
	 * @return string - the version
775
	 * @throws \ElkArte\Exceptions\Exception
776
	 */
777
	abstract public function client_version();
778
779
	/**
780
	 * Return server info.
781
	 *
782
	 * @return string
783
	 */
784
	abstract public function server_info();
785
786
	/**
787
	 * Escape string for the database input
788
	 *
789
	 * @param string $string
790
	 *
791
	 * @return string
792
	 */
793
	abstract public function escape_string($string);
794
795
	/**
796
	 * Whether the database system is case sensitive.
797
	 *
798
	 * @return boolean
799
	 */
800
	abstract public function case_sensitive();
801
802
	/**
803
	 * Get the name (title) of the database system.
804
	 *
805
	 * @return string
806
	 */
807
	abstract public function title();
808
809
	/**
810
	 * Returns whether the database system supports ignore.
811
	 *
812
	 * @return false
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
813
	 */
814
	abstract public function support_ignore();
815
816
	/**
817
	 * Get the version number.
818
	 *
819
	 * @return string - the version
820
	 * @throws \ElkArte\Exceptions\Exception
821
	 */
822
	abstract public function server_version();
823
824
	/**
825
	 * Temporary function to support migration to the new schema of the db layer
826
	 * @deprecated since 2.0
827
	 */
828 42
	public function fetch_row($result)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
829
	{
830
// 		\ElkArte\Errors\Errors::instance()->log_deprecated('Query::fetch_row()', 'Result::fetch_row()');
831 42
		if ($result === false)
832
		{
833
			return false;
834
		}
835
		else
836
		{
837 42
			return $result->fetch_row();
838
		}
839
	}
840
841
	/**
842
	 * Temporary function to support migration to the new schema of the db layer
843
	 * @deprecated since 2.0
844
	 */
845 64
	public function fetch_assoc($result)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
846
	{
847
// 		\ElkArte\Errors\Errors::instance()->log_deprecated('Query::fetch_assoc()', 'Result::fetch_assoc()');
848 64
		if ($result === false)
849
		{
850
			return false;
851
		}
852
		else
853
		{
854 64
			return $result->fetch_assoc();
855
		}
856
	}
857
858
	/**
859
	 * Temporary function to support migration to the new schema of the db layer
860
	 * @deprecated since 2.0
861
	 */
862 75
	public function free_result($result)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
863
	{
864
// 		\ElkArte\Errors\Errors::instance()->log_deprecated('Query::free_result()', 'Result::free_result()');
865 75
		if ($result === false)
866
		{
867
			return;
868
		}
869
		else
870
		{
871 75
			return $result->free_result();
872
		}
873
	}
874
875
	/**
876
	 * Temporary function to supoprt migration to the new schema of the db layer
877
	 * @deprecated since 2.0
878
	 */
879 27
	public function affected_rows()
880
	{
881
// 		\ElkArte\Errors\Errors::instance()->log_deprecated('Query::affected_rows()', 'Result::affected_rows()');
882 27
		return $this->result->affected_rows();
883
	}
884
885
	/**
886
	 * Temporary function to support migration to the new schema of the db layer
887
	 * @deprecated since 2.0
888
	 */
889 47
	public function num_rows($result)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
890
	{
891
// 		\ElkArte\Errors\Errors::instance()->log_deprecated('Query::num_rows()', 'Result::num_rows()');
892 47
		if ($result === false)
893
		{
894
			return 0;
895
		}
896
		else
897
		{
898 47
			return $result->num_rows();
899
		}
900
	}
901
902
	/**
903
	 * Temporary function to support migration to the new schema of the db layer
904
	 * @deprecated since 2.0
905
	 */
906
	public function num_fields($result)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
907
	{
908
// 		\ElkArte\Errors\Errors::instance()->log_deprecated('Query::num_fields()', 'Result::num_fields()');
909
		if ($result === false)
910
		{
911
			return 0;
912
		}
913
		else
914
		{
915
			return $result->num_fields();
916
		}
917
	}
918
919
	/**
920
	 * Temporary function to support migration to the new schema of the db layer
921
	 * @deprecated since 2.0
922
	 */
923 22
	public function insert_id($table)
924
	{
925
// 		\ElkArte\Errors\Errors::instance()->log_deprecated('Query::insert_id()', 'Result::insert_id()');
926 22
		return $this->result->insert_id();
927
	}
928
929
	/**
930
	 * Temporary function to support migration to the new schema of the db layer
931
	 * @deprecated since 2.0
932
	 */
933
	public function data_seek($result, $counter)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
934
	{
935
// 		\ElkArte\Errors\Errors::instance()->log_deprecated('Query::data_seek()', 'Result::data_seek()');
936
		return $result->data_seek($counter);
937
	}
938
939
	/**
940
	 * Temporary function: I'm not sure this is the best place to have it, though it was
941
	 * convenient while fixing other issues.
942
	 * @deprecated since 2.0
943
	 */
944
	public function supportMediumtext()
945
	{
946
		return false;
947
	}
948
949
	/**
950
	 * Temporary function to support migration to the new schema of the db layer
951
	 * @deprecated since 2.0
952
	 */
953
	abstract public function list_tables($db_name_str = false, $filter = false);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
954
}
955