Completed
Push — master ( 3260db...1c6413 )
by Lars
09:14 queued 43s
created

DB::_parseArrayPair()   F

Complexity

Conditions 37
Paths > 20000

Size

Total Lines 146
Code Lines 78

Duplication

Lines 6
Ratio 4.11 %

Code Coverage

Tests 104
CRAP Score 37.0011

Importance

Changes 0
Metric Value
dl 6
loc 146
ccs 104
cts 105
cp 0.9905
rs 2
c 0
b 0
f 0
cc 37
eloc 78
nc 12582914
nop 2
crap 37.0011

How to fix   Long Method    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
namespace voku\db;
4
5
use voku\cache\Cache;
6
use voku\helper\UTF8;
7
8
/**
9
 * DB: this handles DB queries via MySQLi
10
 *
11
 * @package   voku\db
12
 */
13
final class DB
14
{
15
16
  /**
17
   * @var int
18
   */
19
  public $query_count = 0;
20
21
  /**
22
   * @var \mysqli
23
   */
24
  private $link = false;
25
26
  /**
27
   * @var bool
28
   */
29
  private $connected = false;
30
31
  /**
32
   * @var array
33
   */
34
  private $mysqlDefaultTimeFunctions;
35
36
  /**
37
   * @var string
38
   */
39
  private $hostname = '';
40
41
  /**
42
   * @var string
43
   */
44
  private $username = '';
45
46
  /**
47
   * @var string
48
   */
49
  private $password = '';
50
51
  /**
52
   * @var string
53
   */
54
  private $database = '';
55
56
  /**
57
   * @var int
58
   */
59
  private $port = 3306;
60
61
  /**
62
   * @var string
63
   */
64
  private $charset = 'utf8';
65
66
  /**
67
   * @var string
68
   */
69
  private $socket = '';
70
71
  /**
72
   * @var bool
73
   */
74
  private $session_to_db = false;
75
76
  /**
77
   * @var bool
78
   */
79
  private $_in_transaction = false;
80
81
  /**
82
   * @var bool
83
   */
84
  private $_convert_null_to_empty_string = false;
85
86
  /**
87
   * @var Debug
88
   */
89
  private $_debug;
90
91
  /**
92
   * __construct()
93
   *
94
   * @param string         $hostname
95
   * @param string         $username
96
   * @param string         $password
97
   * @param string         $database
98
   * @param int            $port
99
   * @param string         $charset
100
   * @param boolean|string $exit_on_error use a empty string "" or false to disable it
101
   * @param boolean|string $echo_on_error use a empty string "" or false to disable it
102
   * @param string         $logger_class_name
103
   * @param string         $logger_level  'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'
104
   * @param boolean|string $session_to_db use a empty string "" or false to disable it
105
   */
106 10
  protected function __construct($hostname, $username, $password, $database, $port, $charset, $exit_on_error, $echo_on_error, $logger_class_name, $logger_level, $session_to_db)
107
  {
108 10
    $this->connected = false;
109
110 10
    $this->_debug = new Debug($this);
111
112 10
    $this->_loadConfig(
113 10
        $hostname,
114 10
        $username,
115 10
        $password,
116 10
        $database,
117 10
        $port,
118 10
        $charset,
119 10
        $exit_on_error,
120 10
        $echo_on_error,
121 10
        $logger_class_name,
122 10
        $logger_level,
123
        $session_to_db
124 10
    );
125
126 7
    $this->connect();
127
128 4
    $this->mysqlDefaultTimeFunctions = array(
129
      // Returns the current date.
130 4
      'CURDATE()',
131
      // CURRENT_DATE	| Synonyms for CURDATE()
132 4
      'CURRENT_DATE()',
133
      // CURRENT_TIME	| Synonyms for CURTIME()
134 4
      'CURRENT_TIME()',
135
      // CURRENT_TIMESTAMP | Synonyms for NOW()
136 4
      'CURRENT_TIMESTAMP()',
137
      // Returns the current time.
138 4
      'CURTIME()',
139
      // Synonym for NOW()
140 4
      'LOCALTIME()',
141
      // Synonym for NOW()
142 4
      'LOCALTIMESTAMP()',
143
      // Returns the current date and time.
144 4
      'NOW()',
145
      // Returns the time at which the function executes.
146 4
      'SYSDATE()',
147
      // Returns a UNIX timestamp.
148 4
      'UNIX_TIMESTAMP()',
149
      // Returns the current UTC date.
150 4
      'UTC_DATE()',
151
      // Returns the current UTC time.
152 4
      'UTC_TIME()',
153
      // Returns the current UTC date and time.
154 4
      'UTC_TIMESTAMP()',
155
    );
156 4
  }
157
158
  /**
159
   * Load the config from the constructor.
160
   *
161
   * @param string         $hostname
162
   * @param string         $username
163
   * @param string         $password
164
   * @param string         $database
165
   * @param int            $port
166
   * @param string         $charset
167
   * @param boolean|string $exit_on_error use a empty string "" or false to disable it
168
   * @param boolean|string $echo_on_error use a empty string "" or false to disable it
169
   * @param string         $logger_class_name
170
   * @param string         $logger_level
171
   * @param boolean|string $session_to_db use a empty string "" or false to disable it
172
   *
173
   * @return bool
174
   */
175 10
  private function _loadConfig($hostname, $username, $password, $database, $port, $charset, $exit_on_error, $echo_on_error, $logger_class_name, $logger_level, $session_to_db)
176
  {
177 10
    $this->hostname = (string)$hostname;
178 10
    $this->username = (string)$username;
179 10
    $this->password = (string)$password;
180 10
    $this->database = (string)$database;
181
182 10
    if ($charset) {
183 4
      $this->charset = (string)$charset;
184 4
    }
185
186 10
    if ($port) {
187 4
      $this->port = (int)$port;
188 4
    } else {
189
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
190
      /** @noinspection UsageOfSilenceOperatorInspection */
191 7
      $this->port = (int)@ini_get('mysqli.default_port');
192
    }
193
194
    // fallback
195 10
    if (!$this->port) {
196
      $this->port = 3306;
197
    }
198
199 10
    if (!$this->socket) {
200
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
201 10
      $this->socket = @ini_get('mysqli.default_socket');
202 10
    }
203
204 10
    if ($exit_on_error === true || $exit_on_error === false) {
205 10
      $this->_debug->setExitOnError($exit_on_error);
206 10
    }
207
208 10
    if ($echo_on_error === true || $echo_on_error === false) {
209 10
      $this->_debug->setEchoOnError($echo_on_error);
210 10
    }
211
212 10
    $this->_debug->setLoggerClassName($logger_class_name);
213 10
    $this->_debug->setLoggerLevel($logger_level);
214
215 10
    $this->session_to_db = (boolean)$session_to_db;
216
217 10
    return $this->showConfigError();
218
  }
219
220
  /**
221
   * Show config errors by throw exceptions.
222
   *
223
   * @return bool
224
   *
225
   * @throws \Exception
226
   */
227 10
  public function showConfigError()
228
  {
229
230
    if (
231 10
        !$this->hostname
232 10
        ||
233 9
        !$this->username
234 9
        ||
235 8
        !$this->database
236 10
    ) {
237
238 3
      if (!$this->hostname) {
239 1
        throw new \Exception('no-sql-hostname');
240
      }
241
242 2
      if (!$this->username) {
243 1
        throw new \Exception('no-sql-username');
244
      }
245
246 1
      if (!$this->database) {
247 1
        throw new \Exception('no-sql-database');
248
      }
249
250
      return false;
251
    }
252
253 7
    return true;
254
  }
255
256
  /**
257
   * Open a new connection to the MySQL server.
258
   *
259
   * @return boolean
260
   */
261 9
  public function connect()
262
  {
263 9
    if ($this->isReady()) {
264 1
      return true;
265
    }
266
267 9
    \mysqli_report(MYSQLI_REPORT_STRICT);
268
    try {
269 9
      $this->link = \mysqli_init();
0 ignored issues
show
Documentation Bug introduced by
It seems like \mysqli_init() of type object<mysql> is incompatible with the declared type object<mysqli> of property $link.

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...
270
271 9
      if (Helper::isMysqlndIsUsed() === true) {
272 9
        \mysqli_options($this->link, MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
273 9
      }
274
275
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
276 9
      $this->connected = @\mysqli_real_connect(
277 9
          $this->link,
278 9
          $this->hostname,
279 9
          $this->username,
280 9
          $this->password,
281 9
          $this->database,
282 9
          $this->port,
283 9
          $this->socket
284 9
      );
285 9
    } catch (\Exception $e) {
286 3
      $this->_debug->displayError('Error connecting to mysql server: ' . $e->getMessage(), true);
287
    }
288 6
    \mysqli_report(MYSQLI_REPORT_OFF);
289
290 6
    if (!$this->connected) {
291
      $this->_debug->displayError('Error connecting to mysql server: ' . \mysqli_connect_error(), true);
292
    } else {
293 6
      $this->set_charset($this->charset);
294
    }
295
296 6
    return $this->isReady();
297
  }
298
299
  /**
300
   * Check if db-connection is ready.
301
   *
302
   * @return boolean
303
   */
304 45
  public function isReady()
305
  {
306 45
    return $this->connected ? true : false;
307
  }
308
309
  /**
310
   * Get a new "Prepare"-Object for your sql-query.
311
   *
312
   * @param string $query
313
   *
314
   * @return Prepare
315
   */
316 2
  public function prepare($query)
317
  {
318 2
    return new Prepare($this, $query);
319
  }
320
321
  /**
322
   * Execute a sql-query and return the result-array for select-statements.
323
   *
324
   * @param $query
325
   *
326
   * @return mixed
327
   * @deprecated
328
   * @throws \Exception
329
   */
330
  public static function qry($query)
331
  {
332
    $db = self::getInstance();
333
334
    $args = func_get_args();
335
    /** @noinspection SuspiciousAssignmentsInspection */
336
    $query = array_shift($args);
337
    $query = str_replace('?', '%s', $query);
338
    $args = array_map(
339
        array(
340
            $db,
341
            'escape',
342
        ),
343
        $args
344
    );
345
    array_unshift($args, $query);
346
    $query = call_user_func_array('sprintf', $args);
347
    $result = $db->query($query);
348
349
    if ($result instanceof Result) {
350
      return $result->fetchAllArray();
351
    }
352
353
    return $result;
354
  }
355
356
  /**
357
   * getInstance()
358
   *
359
   * @param string      $hostname
360
   * @param string      $username
361
   * @param string      $password
362
   * @param string      $database
363
   * @param string      $port          default is (int)3306
364
   * @param string      $charset       default is 'utf8' or 'utf8mb4' (if supported)
365
   * @param bool|string $exit_on_error use a empty string "" or false to disable it
366
   * @param bool|string $echo_on_error use a empty string "" or false to disable it
367
   * @param string      $logger_class_name
368
   * @param string      $logger_level
369
   * @param bool|string $session_to_db use a empty string "" or false to disable it
370
   *
371
   * @return \voku\db\DB
372
   */
373 57
  public static function getInstance($hostname = '', $username = '', $password = '', $database = '', $port = '', $charset = '', $exit_on_error = '', $echo_on_error = '', $logger_class_name = '', $logger_level = '', $session_to_db = '')
374
  {
375
    /**
376
     * @var $instance DB[]
377
     */
378 57
    static $instance = array();
379
380
    /**
381
     * @var $firstInstance DB
382
     */
383 57
    static $firstInstance = null;
384
385
    if (
386 57
        $hostname . $username . $password . $database . $port . $charset == ''
387 57
        &&
388 11
        null !== $firstInstance
389 57
    ) {
390 11
      return $firstInstance;
391
    }
392
393 57
    $connection = md5(
394 57
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . (int)$session_to_db
395 57
    );
396
397 57
    if (!isset($instance[$connection])) {
398 10
      $instance[$connection] = new self(
399 10
          $hostname,
400 10
          $username,
401 10
          $password,
402 10
          $database,
403 10
          $port,
404 10
          $charset,
405 10
          $exit_on_error,
406 10
          $echo_on_error,
407 10
          $logger_class_name,
408 10
          $logger_level,
409
          $session_to_db
410 10
      );
411
412 4
      if (null === $firstInstance) {
413 1
        $firstInstance = $instance[$connection];
414 1
      }
415 4
    }
416
417 57
    return $instance[$connection];
418
  }
419
420
  /**
421
   * Execute a sql-query.
422
   *
423
   * @param string        $sql            sql-query
424
   *
425
   * @param array|boolean $params         "array" of sql-query-parameters
426
   *                                      "false" if you don't need any parameter (default)
427
   *
428
   * @return bool|int|Result              "Result" by "<b>SELECT</b>"-queries<br />
429
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
430
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
431
   *                                      "true" by e.g. "DROP"-queries<br />
432
   *                                      "false" on error
433
   *
434
   * @throws \Exception
435
   */
436 35
  public function query($sql = '', $params = false)
437
  {
438 35
    if (!$this->isReady()) {
439
      return false;
440
    }
441
442 35 View Code Duplication
    if (!$sql || $sql === '') {
443 4
      $this->_debug->displayError('Can\'t execute an empty Query', false);
444
445 4
      return false;
446
    }
447
448
    if (
449
        $params !== false
450 33
        &&
451 3
        is_array($params)
452 33
        &&
453 3
        count($params) > 0
454 33
    ) {
455 3
      $sql = $this->_parseQueryParams($sql, $params);
456 3
    }
457
458 33
    $query_start_time = microtime(true);
459 33
    $query_result = \mysqli_real_query($this->link, $sql);
460 33
    $query_duration = microtime(true) - $query_start_time;
461
462 33
    $this->query_count++;
463
464 33
    $mysqli_field_count = \mysqli_field_count($this->link);
465 33
    if ($mysqli_field_count) {
466 28
      $result = \mysqli_store_result($this->link);
467 28
    } else {
468 24
      $result = $query_result;
469
    }
470
471 33
    if ($result instanceof \mysqli_result) {
472
473
      // log the select query
474 27
      $this->_debug->logQuery($sql, $query_duration, $mysqli_field_count);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 455 can also be of type array; however, voku\db\Debug::logQuery() does only seem to accept string, 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...
475
476
      // return query result object
477 27
      return new Result($sql, $result);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 455 can also be of type array; however, voku\db\Result::__construct() does only seem to accept string, 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...
478
479
    }
480
481 25
    if ($query_result === true) {
482
483
      // "INSERT" || "REPLACE"
484 23 View Code Duplication
      if (preg_match('/^\s*"?(INSERT|REPLACE)\s+/i', $sql)) {
485 22
        $insert_id = (int)$this->insert_id();
486 22
        $this->_debug->logQuery($sql, $query_duration, $insert_id);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 455 can also be of type array; however, voku\db\Debug::logQuery() does only seem to accept string, 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...
487
488 22
        return $insert_id;
489
      }
490
491
      // "UPDATE" || "DELETE"
492 8 View Code Duplication
      if (preg_match('/^\s*"?(UPDATE|DELETE)\s+/i', $sql)) {
493 8
        $affected_rows = (int)$this->affected_rows();
494 8
        $this->_debug->logQuery($sql, $query_duration, $affected_rows);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 455 can also be of type array; however, voku\db\Debug::logQuery() does only seem to accept string, 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...
495
496 8
        return $affected_rows;
497
      }
498
499
      // log the ? query
500
      $this->_debug->logQuery($sql, $query_duration, 0);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 455 can also be of type array; however, voku\db\Debug::logQuery() does only seem to accept string, 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...
501
502
      return true;
503
    }
504
505
    // log the error query
506 8
    $this->_debug->logQuery($sql, $query_duration, 0, true);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 455 can also be of type array; however, voku\db\Debug::logQuery() does only seem to accept string, 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...
507
508 8
    return $this->queryErrorHandling(\mysqli_error($this->link), $sql, $params);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 455 can also be of type array; however, voku\db\DB::queryErrorHandling() does only seem to accept string, 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...
509
  }
510
511
  /**
512
   * _parseQueryParams
513
   *
514
   * @param string $sql
515
   * @param array  $params
516
   *
517
   * @return string
518
   */
519 3
  private function _parseQueryParams($sql, array $params)
520
  {
521
    // is there anything to parse?
522 3
    if (strpos($sql, '?') === false) {
523
      return $sql;
524
    }
525
526 3
    if (count($params) > 0) {
527 3
      $parseKey = md5(uniqid((string)mt_rand(), true));
528 3
      $sql = str_replace('?', $parseKey, $sql);
529
530 3
      $k = 0;
531 3
      while (strpos($sql, $parseKey) !== false) {
532 3
        $value = $this->secure($params[$k]);
533 3
        $sql = UTF8::str_replace_first($parseKey, $value, $sql);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by \voku\helper\UTF8::str_r...parseKey, $value, $sql) on line 533 can also be of type array; however, voku\helper\UTF8::str_replace_first() does only seem to accept string, 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...
534 3
        $k++;
535 3
      }
536 3
    }
537
538 3
    return $sql;
539
  }
540
541
  /**
542
   * Try to secure a variable, so can you use it in sql-queries.
543
   *
544
   * <p>
545
   * <strong>int:</strong> (also strings that contains only an int-value)<br />
546
   * 1. parse into "int"
547
   * </p><br />
548
   *
549
   * <p>
550
   * <strong>float:</strong><br />
551
   * 1. return "float"
552
   * </p><br />
553
   *
554
   * <p>
555
   * <strong>string:</strong><br />
556
   * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'<br />
557
   * 2. trim whitespace<br />
558
   * 3. trim '<br />
559
   * 4. escape the string (and remove non utf-8 chars)<br />
560
   * 5. trim ' again (because we maybe removed some chars)<br />
561
   * 6. add ' around the new string<br />
562
   * </p><br />
563
   *
564
   * <p>
565
   * <strong>array:</strong><br />
566
   * 1. return null
567
   * </p><br />
568
   *
569
   * <p>
570
   * <strong>object:</strong><br />
571
   * 1. return false
572
   * </p><br />
573
   *
574
   * <p>
575
   * <strong>null:</strong><br />
576
   * 1. return null
577
   * </p>
578
   *
579
   * @param mixed $var
580
   *
581
   * @return mixed
582
   */
583 26
  public function secure($var)
584
  {
585
    if (
586
        $var === ''
587 26
        ||
588 26
        ($this->_convert_null_to_empty_string === true && $var === null)
589 26
    ) {
590 1
      return "''";
591
    }
592
593 26
    if (in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
594 1
      return $var;
595
    }
596
597 26
    if (is_string($var)) {
598 22
      $var = trim(trim($var), "'");
599 22
    }
600
601 26
    $var = $this->escape($var, false, false, null);
602
603 26
    if (is_string($var)) {
604 22
      $var = "'" . trim($var, "'") . "'";
605 22
    }
606
607 26
    return $var;
608
  }
609
610
  /**
611
   * Escape: Use "mysqli_real_escape_string" and clean non UTF-8 chars + some extra optional stuff.
612
   *
613
   * @param mixed     $var           boolean: convert into "integer"<br />
614
   *                                 int: int (don't change it)<br />
615
   *                                 float: float (don't change it)<br />
616
   *                                 null: null (don't change it)<br />
617
   *                                 array: run escape() for every key => value<br />
618
   *                                 string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
619
   * @param bool      $stripe_non_utf8
620
   * @param bool      $html_entity_decode
621
   * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
622
   *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
623
   *                                 <strong>null</strong> => Convert the array into null, every time.
624
   *
625
   * @return mixed
626
   */
627 33
  public function escape($var = '', $stripe_non_utf8 = true, $html_entity_decode = false, $convert_array = false)
628
  {
629 33
    if ($var === '') {
630 2
      return '';
631
    }
632
633 33
    if ($var === null) {
634 2
      return null;
635
    }
636
637
    // save the current value as int (for later usage)
638 33
    if (!is_object($var)) {
639 33
      $varInt = (int)$var;
640 33
    }
641
642
    /** @noinspection TypeUnsafeComparisonInspection */
643
    if (
644 33
        is_int($var)
645
        ||
646 33
        is_bool($var)
647 33
        ||
648
        (
649 33
            isset($varInt, $var[0])
650 33
            &&
651 33
            $var[0] != '0'
652 33
            &&
653
            "$varInt" == $var
0 ignored issues
show
Bug introduced by
The variable $varInt 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...
654 33
        )
655 33
    ) {
656
657
      // "int" || int || bool
658
659 24
      return (int)$var;
660
    }
661
662 33
    if (is_float($var)) {
663
664
      // float
665
666 5
      return $var;
667
    }
668
669 33
    if (is_array($var)) {
670
671
      // array
672
673 3
      if ($convert_array === null) {
674 3
        return null;
675
      }
676
677 1
      $varCleaned = array();
678 1
      foreach ((array)$var as $key => $value) {
679
680 1
        $key = $this->escape($key, $stripe_non_utf8, $html_entity_decode);
681 1
        $value = $this->escape($value, $stripe_non_utf8, $html_entity_decode);
682
683
        /** @noinspection OffsetOperationsInspection */
684 1
        $varCleaned[$key] = $value;
685 1
      }
686
687 1
      if ($convert_array === true) {
688 1
        $varCleaned = implode(',', $varCleaned);
689
690 1
        return $varCleaned;
691
      }
692
693 1
      return (array)$varCleaned;
694
    }
695
696
    if (
697 33
        is_string($var)
698
        ||
699
        (
700 3
            is_object($var)
701 3
            &&
702 3
            method_exists($var, '__toString')
703 3
        )
704 33
    ) {
705
706
      // "string"
707
708 33
      $var = (string)$var;
709
710 33
      if ($stripe_non_utf8 === true) {
711 9
        $var = UTF8::cleanup($var);
712 9
      }
713
714 33
      if ($html_entity_decode === true) {
715
        // use no-html-entity for db
716 1
        $var = UTF8::html_entity_decode($var);
717 1
      }
718
719 33
      $var = get_magic_quotes_gpc() ? stripslashes($var) : $var;
720
721 33
      $var = \mysqli_real_escape_string($this->getLink(), $var);
722
723 33
      return (string)$var;
724
725
    }
726
727 3
    if ($var instanceof \DateTime) {
728
729
      // "DateTime"-object
730
731
      try {
732 3
        return $this->escape($var->format('Y-m-d H:i:s'), false);
733
      } catch (\Exception $e) {
734
        return null;
735
      }
736
737
    } else {
738 2
      return false;
739
    }
740
  }
741
742
  /**
743
   * Get the mysqli-link (link identifier returned by mysqli-connect).
744
   *
745
   * @return \mysqli
746
   */
747 35
  public function getLink()
748
  {
749 35
    return $this->link;
750
  }
751
752
  /**
753
   * Returns the auto generated id used in the last query.
754
   *
755
   * @return int|string
756
   */
757 22
  public function insert_id()
758
  {
759 22
    return \mysqli_insert_id($this->link);
760
  }
761
762
  /**
763
   * Gets the number of affected rows in a previous MySQL operation.
764
   *
765
   * @return int
766
   */
767 8
  public function affected_rows()
768
  {
769 8
    return \mysqli_affected_rows($this->link);
770
  }
771
772
  /**
773
   * Error-handling for the sql-query.
774
   *
775
   * @param string     $errorMsg
776
   * @param string     $sql
777
   * @param array|bool $sqlParams false if there wasn't any parameter
778
   *
779
   * @throws \Exception
780
   *
781
   * @return bool
782
   */
783 9 View Code Duplication
  protected function queryErrorHandling($errorMsg, $sql, $sqlParams = false)
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...
784
  {
785 9
    if ($errorMsg === 'DB server has gone away' || $errorMsg === 'MySQL server has gone away') {
786 1
      static $RECONNECT_COUNTER;
787
788
      // exit if we have more then 3 "DB server has gone away"-errors
789 1
      if ($RECONNECT_COUNTER > 3) {
790
        $this->_debug->mailToAdmin('SQL-Fatal-Error', $errorMsg . ":\n<br />" . $sql, 5);
791
        throw new \Exception($errorMsg);
792
      }
793
794 1
      $this->_debug->mailToAdmin('SQL-Error', $errorMsg . ":\n<br />" . $sql);
795
796
      // reconnect
797 1
      $RECONNECT_COUNTER++;
798 1
      $this->reconnect(true);
799
800
      // re-run the current query
801 1
      return $this->query($sql, $sqlParams);
802
    }
803
804 8
    $this->_debug->mailToAdmin('SQL-Warning', $errorMsg . ":\n<br />" . $sql);
805
806
    // this query returned an error, we must display it (only for dev) !!!
807 8
    $this->_debug->displayError($errorMsg . ' | ' . $sql);
808
809 8
    return false;
810
  }
811
812
  /**
813
   * Reconnect to the MySQL-Server.
814
   *
815
   * @param bool $checkViaPing
816
   *
817
   * @return bool
818
   */
819 3
  public function reconnect($checkViaPing = false)
820
  {
821 3
    $ping = false;
822
823 3
    if ($checkViaPing === true) {
824 2
      $ping = $this->ping();
825 2
    }
826
827 3
    if ($ping !== true) {
828 3
      $this->connected = false;
829 3
      $this->connect();
830 3
    }
831
832 3
    return $this->isReady();
833
  }
834
835
  /**
836
   * Pings a server connection, or tries to reconnect
837
   * if the connection has gone down.
838
   *
839
   * @return boolean
840
   */
841 3
  public function ping()
842
  {
843
    if (
844 3
        $this->link
845 3
        &&
846 3
        $this->link instanceof \mysqli
847 3
    ) {
848
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
849
      /** @noinspection UsageOfSilenceOperatorInspection */
850 3
      return (bool)@\mysqli_ping($this->link);
851
    }
852
853
    return false;
854
  }
855
856
  /**
857
   * Execute select/insert/update/delete sql-queries.
858
   *
859
   * @param string $query    sql-query
860
   * @param bool   $useCache use cache?
861
   * @param int    $cacheTTL cache-ttl in seconds
862
   *
863
   * @return mixed "array" by "<b>SELECT</b>"-queries<br />
864
   *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
865
   *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
866
   *               "true" by e.g. "DROP"-queries<br />
867
   *               "false" on error
868
   */
869 3
  public static function execSQL($query, $useCache = false, $cacheTTL = 3600)
870
  {
871
    // init
872 3
    $cacheKey = null;
873 3
    $db = self::getInstance();
874
875 3 View Code Duplication
    if ($useCache === true) {
876 1
      $cache = new Cache(null, null, false, $useCache);
877 1
      $cacheKey = 'sql-' . md5($query);
878
879
      if (
880 1
          $cache->getCacheIsReady() === true
881 1
          &&
882 1
          $cache->existsItem($cacheKey)
883 1
      ) {
884 1
        return $cache->getItem($cacheKey);
885
      }
886
887 1
    } else {
888 3
      $cache = false;
889
    }
890
891 3
    $result = $db->query($query);
892
893 3
    if ($result instanceof Result) {
894
895 1
      $return = $result->fetchAllArray();
896
897
      // save into the cache
898 View Code Duplication
      if (
899
          $cacheKey !== null
900 1
          &&
901
          $useCache === true
902 1
          &&
903
          $cache instanceof Cache
904 1
          &&
905 1
          $cache->getCacheIsReady() === true
906 1
      ) {
907 1
        $cache->setItem($cacheKey, $return, $cacheTTL);
908 1
      }
909
910 1
    } else {
911 2
      $return = $result;
912
    }
913
914 3
    return $return;
915
  }
916
917
  /**
918
   * Get the current charset.
919
   *
920
   * @return string
921
   */
922 1
  public function get_charset()
923
  {
924 1
    return $this->charset;
925
  }
926
927
  /**
928
   * Enables or disables internal report functions
929
   *
930
   * @link http://php.net/manual/en/function.mysqli-report.php
931
   *
932
   * @param int $flags <p>
933
   *                   <table>
934
   *                   Supported flags
935
   *                   <tr valign="top">
936
   *                   <td>Name</td>
937
   *                   <td>Description</td>
938
   *                   </tr>
939
   *                   <tr valign="top">
940
   *                   <td><b>MYSQLI_REPORT_OFF</b></td>
941
   *                   <td>Turns reporting off</td>
942
   *                   </tr>
943
   *                   <tr valign="top">
944
   *                   <td><b>MYSQLI_REPORT_ERROR</b></td>
945
   *                   <td>Report errors from mysqli function calls</td>
946
   *                   </tr>
947
   *                   <tr valign="top">
948
   *                   <td><b>MYSQLI_REPORT_STRICT</b></td>
949
   *                   <td>
950
   *                   Throw <b>mysqli_sql_exception</b> for errors
951
   *                   instead of warnings
952
   *                   </td>
953
   *                   </tr>
954
   *                   <tr valign="top">
955
   *                   <td><b>MYSQLI_REPORT_INDEX</b></td>
956
   *                   <td>Report if no index or bad index was used in a query</td>
957
   *                   </tr>
958
   *                   <tr valign="top">
959
   *                   <td><b>MYSQLI_REPORT_ALL</b></td>
960
   *                   <td>Set all options (report all)</td>
961
   *                   </tr>
962
   *                   </table>
963
   *                   </p>
964
   *
965
   * @return bool
966
   */
967
  public function set_mysqli_report($flags)
968
  {
969
    return \mysqli_report($flags);
970
  }
971
972
  /**
973
   * Set the current charset.
974
   *
975
   * @param string $charset
976
   *
977
   * @return bool
978
   */
979 7
  public function set_charset($charset)
980
  {
981 7
    $charsetLower = strtolower($charset);
982 7
    if ($charsetLower === 'utf8' || $charsetLower === 'utf-8') {
983 5
      $charset = 'utf8';
984 5
    }
985 7
    if ($charset === 'utf8' && Helper::isUtf8mb4Supported($this) === true) {
986 5
      $charset = 'utf8mb4';
987 5
    }
988
989 7
    $this->charset = (string)$charset;
990
991 7
    $return = mysqli_set_charset($this->link, $charset);
992
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
993
    /** @noinspection UsageOfSilenceOperatorInspection */
994 7
    @\mysqli_query($this->link, 'SET CHARACTER SET ' . $charset);
995
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
996
    /** @noinspection UsageOfSilenceOperatorInspection */
997 7
    @\mysqli_query($this->link, "SET NAMES '" . $charset . "'");
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
998
999 7
    return $return;
1000
  }
1001
1002
  /**
1003
   * Set the option to convert null to "''" (empty string).
1004
   *
1005
   * Used in secure() => select(), insert(), update(), delete()
1006
   *
1007
   * @param $bool
1008
   */
1009 1
  public function set_convert_null_to_empty_string($bool)
1010
  {
1011 1
    $this->_convert_null_to_empty_string = (bool)$bool;
1012 1
  }
1013
1014
  /**
1015
   * Get all table-names via "SHOW TABLES".
1016
   *
1017
   * @return array
1018
   */
1019 1
  public function getAllTables()
1020
  {
1021 1
    $query = 'SHOW TABLES';
1022 1
    $result = $this->query($query);
1023
1024 1
    return $result->fetchAllArray();
1025
  }
1026
1027
  /**
1028
   * Execute a sql-multi-query.
1029
   *
1030
   * @param string $sql
1031
   *
1032
   * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
1033
   *                        "boolean" by only "<b>INSERT</b>"-queries<br />
1034
   *                        "boolean" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1035
   *                        "boolean" by only by e.g. "DROP"-queries<br />
1036
   *
1037
   * @throws \Exception
1038
   */
1039 1
  public function multi_query($sql)
1040
  {
1041 1
    if (!$this->isReady()) {
1042
      return false;
1043
    }
1044
1045 1 View Code Duplication
    if (!$sql || $sql === '') {
1046 1
      $this->_debug->displayError('Can\'t execute an empty Query', false);
1047
1048 1
      return false;
1049
    }
1050
1051 1
    $query_start_time = microtime(true);
1052 1
    $resultTmp = \mysqli_multi_query($this->link, $sql);
1053 1
    $query_duration = microtime(true) - $query_start_time;
1054
1055 1
    $this->_debug->logQuery($sql, $query_duration, 0);
1056
1057 1
    $returnTheResult = false;
1058 1
    $result = array();
1059 1
    if ($resultTmp) {
1060
      do {
1061 1
        $resultTmpInner = \mysqli_store_result($this->link);
1062
1063 1
        if ($resultTmpInner instanceof \mysqli_result) {
1064 1
          $returnTheResult = true;
1065 1
          $result[] = new Result($sql, $resultTmpInner);
1066 1
        } else {
1067 1
          $errorMsg = \mysqli_error($this->link);
1068
1069
          // is the query successful
1070 1
          if ($resultTmpInner === true || !$errorMsg) {
1071 1
            $result[] = true;
1072 1
          } else {
1073
            $result[] = $this->queryErrorHandling($errorMsg, $sql);
1074
          }
1075
        }
1076 1
      } while (\mysqli_more_results($this->link) === true ? \mysqli_next_result($this->link) : false);
1077
1078 1
    } else {
1079
1080
      $errorMsg = \mysqli_error($this->link);
1081
1082
      if ($this->_debug->checkForDev() === true) {
1083
        echo "Info: maybe you have to increase your 'max_allowed_packet = 30M' in the config: 'my.conf' \n<br />";
1084
        echo 'Error:' . $errorMsg;
1085
      }
1086
1087
      $this->_debug->mailToAdmin('SQL-Error in mysqli_multi_query', $errorMsg . ":\n<br />" . $sql);
1088
    }
1089
1090
    // return the result only if there was a "SELECT"-query
1091 1
    if ($returnTheResult === true) {
1092 1
      return $result;
1093
    }
1094
1095 1
    if (in_array(false, $result, true) === false) {
1096 1
      return true;
1097
    }
1098
1099
    return false;
1100
  }
1101
1102
  /**
1103
   * alias: "beginTransaction()"
1104
   */
1105 1
  public function startTransaction()
1106
  {
1107 1
    $this->beginTransaction();
1108 1
  }
1109
1110
  /**
1111
   * Begins a transaction, by turning off auto commit.
1112
   *
1113
   * @return boolean this will return true or false indicating success of transaction
1114
   */
1115 4
  public function beginTransaction()
1116
  {
1117 4
    $this->clearErrors();
1118
1119 4
    if ($this->inTransaction() === true) {
1120 1
      $this->_debug->displayError('Error mysql server already in transaction!', true);
1121
1122
      return false;
1123
    }
1124
1125 4
    if (\mysqli_connect_errno()) {
1126
      $this->_debug->displayError('Error connecting to mysql server: ' . \mysqli_connect_error(), true);
1127
1128
      return false;
1129
    }
1130
1131 4
    $this->_in_transaction = true;
1132 4
    \mysqli_autocommit($this->link, false);
1133
1134 4
    return true;
1135
  }
1136
1137
  /**
1138
   * Clear the errors in "_debug->_errors".
1139
   *
1140
   * @return bool
1141
   */
1142 4
  public function clearErrors()
1143
  {
1144 4
    return $this->_debug->clearErrors();
1145
  }
1146
1147
  /**
1148
   * Check if we are in a transaction.
1149
   *
1150
   * @return boolean
1151
   */
1152 4
  public function inTransaction()
1153
  {
1154 4
    return $this->_in_transaction;
1155
  }
1156
1157
  /**
1158
   * Ends a transaction and commits if no errors, then ends autocommit.
1159
   *
1160
   * @return boolean this will return true or false indicating success of transactions
1161
   */
1162 2
  public function endTransaction()
1163
  {
1164
1165 2
    if (!$this->errors()) {
1166 1
      \mysqli_commit($this->link);
1167 1
      $return = true;
1168 1
    } else {
1169 1
      $this->rollback();
1170 1
      $return = false;
1171
    }
1172
1173 2
    \mysqli_autocommit($this->link, true);
1174 2
    $this->_in_transaction = false;
1175
1176 2
    return $return;
1177
  }
1178
1179
  /**
1180
   * Get all errors from "$this->_errors".
1181
   *
1182
   * @return array|false false === on errors
1183
   */
1184 2
  public function errors()
1185
  {
1186 2
    $errors = $this->_debug->getErrors();
1187
1188 2
    return count($errors) > 0 ? $errors : false;
1189
  }
1190
1191
  /**
1192
   * Rollback in a transaction.
1193
   */
1194 2
  public function rollback()
1195
  {
1196
    // init
1197 2
    $return = false;
1198
1199 2
    if ($this->_in_transaction === true) {
1200 2
      $return = \mysqli_rollback($this->link);
1201 2
      \mysqli_autocommit($this->link, true);
1202 2
      $this->_in_transaction = false;
1203 2
    }
1204
1205 2
    return $return;
1206
  }
1207
1208
  /**
1209
   * Execute a "insert"-query.
1210
   *
1211
   * @param string      $table
1212
   * @param array       $data
1213
   * @param string|null $databaseName <p>use <strong>null</strong> if you will use the current database</p>
1214
   *
1215
   * @return false|int false on error
1216
   */
1217 21
  public function insert($table, array $data = array(), $databaseName = null)
1218
  {
1219
    // init
1220 21
    $table = trim($table);
1221
1222 21
    if ($table === '') {
1223 2
      $this->_debug->displayError('invalid table name');
1224
1225 1
      return false;
1226
    }
1227
1228 20
    if (count($data) === 0) {
1229 3
      $this->_debug->displayError('empty data for INSERT');
1230
1231 2
      return false;
1232
    }
1233
1234 18
    $SET = $this->_parseArrayPair($data);
1235
1236 18
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1237
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1238
    }
1239
1240 18
    $sql = 'INSERT INTO ' . $databaseName . $this->quote_string($table) . " SET $SET;";
1241
1242 18
    return $this->query($sql);
1243
  }
1244
1245
  /**
1246
   * Parses arrays with value pairs and generates SQL to use in queries.
1247
   *
1248
   * @param array  $arrayPair
1249
   * @param string $glue this is the separator
1250
   *
1251
   * @return string
1252
   *
1253
   * @internal
1254
   */
1255 23
  public function _parseArrayPair($arrayPair, $glue = ',')
1256
  {
1257
    // init
1258 23
    $sql = '';
1259
1260
    /** @noinspection IsEmptyFunctionUsageInspection */
1261 23
    if (empty($arrayPair)) {
1262
      return '';
1263
    }
1264
1265 23
    $arrayPairCounter = 0;
1266 23
    foreach ($arrayPair as $_key => $_value) {
1267 23
      $_connector = '=';
1268 23
      $_glueHelper = '';
1269 23
      $_key_upper = strtoupper($_key);
1270
1271 23
      if (strpos($_key_upper, ' NOT') !== false) {
1272 2
        $_connector = 'NOT';
1273 2
      }
1274
1275 23
      if (strpos($_key_upper, ' IS') !== false) {
1276 1
        $_connector = 'IS';
1277 1
      }
1278
1279 23
      if (strpos($_key_upper, ' IS NOT') !== false) {
1280 1
        $_connector = 'IS NOT';
1281 1
      }
1282
1283 23
      if (strpos($_key_upper, ' IN') !== false) {
1284 1
        $_connector = 'IN';
1285 1
      }
1286
1287 23
      if (strpos($_key_upper, ' NOT IN') !== false) {
1288 1
        $_connector = 'NOT IN';
1289 1
      }
1290
1291 23
      if (strpos($_key_upper, ' BETWEEN') !== false) {
1292 1
        $_connector = 'BETWEEN';
1293 1
      }
1294
1295 23
      if (strpos($_key_upper, ' NOT BETWEEN') !== false) {
1296 1
        $_connector = 'NOT BETWEEN';
1297 1
      }
1298
1299 23
      if (strpos($_key_upper, ' LIKE') !== false) {
1300 2
        $_connector = 'LIKE';
1301 2
      }
1302
1303 23
      if (strpos($_key_upper, ' NOT LIKE') !== false) {
1304 2
        $_connector = 'NOT LIKE';
1305 2
      }
1306
1307 23 View Code Duplication
      if (strpos($_key_upper, ' >') !== false && strpos($_key_upper, ' =') === false) {
1308 4
        $_connector = '>';
1309 4
      }
1310
1311 23 View Code Duplication
      if (strpos($_key_upper, ' <') !== false && strpos($_key_upper, ' =') === false) {
1312 1
        $_connector = '<';
1313 1
      }
1314
1315 23
      if (strpos($_key_upper, ' >=') !== false) {
1316 4
        $_connector = '>=';
1317 4
      }
1318
1319 23
      if (strpos($_key_upper, ' <=') !== false) {
1320 1
        $_connector = '<=';
1321 1
      }
1322
1323 23
      if (strpos($_key_upper, ' <>') !== false) {
1324 1
        $_connector = '<>';
1325 1
      }
1326
1327 23
      if (strpos($_key_upper, ' OR') !== false) {
1328 2
        $_glueHelper = 'OR';
1329 2
      }
1330
1331 23
      if (strpos($_key_upper, ' AND') !== false) {
1332 1
        $_glueHelper = 'AND';
1333 1
      }
1334
1335 23
      if (is_array($_value) === true) {
1336 2
        foreach ($_value as $oldKey => $oldValue) {
1337 2
          $_value[$oldKey] = $this->secure($oldValue);
1338 2
        }
1339
1340 2
        if ($_connector === 'NOT IN' || $_connector === 'IN') {
1341 1
          $_value = '(' . implode(',', $_value) . ')';
1342 2
        } elseif ($_connector === 'NOT BETWEEN' || $_connector === 'BETWEEN') {
1343 1
          $_value = '(' . implode(' AND ', $_value) . ')';
1344 1
        }
1345
1346 2
      } else {
1347 23
        $_value = $this->secure($_value);
1348
      }
1349
1350 23
      $quoteString = $this->quote_string(
1351 23
          trim(
1352 23
              str_ireplace(
1353
                  array(
1354 23
                      $_connector,
1355 23
                      $_glueHelper,
1356 23
                  ),
1357 23
                  '',
1358
                  $_key
1359 23
              )
1360 23
          )
1361 23
      );
1362
1363 23
      if (!is_array($_value)) {
1364 23
        $_value = array($_value);
1365 23
      }
1366
1367 23
      if (!$_glueHelper) {
1368 23
        $_glueHelper = $glue;
1369 23
      }
1370
1371 23
      $tmpCounter = 0;
1372 23
      foreach ($_value as $valueInner) {
1373
1374 23
        $_glueHelperInner = $_glueHelper;
1375
1376 23
        if ($arrayPairCounter === 0) {
1377
1378 23
          if ($tmpCounter === 0 && $_glueHelper === 'OR') {
1379 1
            $_glueHelperInner = '1 = 1 AND ('; // first "OR"-query glue
1380 23
          } elseif ($tmpCounter === 0) {
1381 23
            $_glueHelperInner = ''; // first query glue e.g. for "INSERT"-query -> skip the first ","
1382 23
          }
1383
1384 23
        } elseif ($tmpCounter === 0 && $_glueHelper === 'OR') {
1385 1
          $_glueHelperInner = 'AND ('; // inner-loop "OR"-query glue
1386 1
        }
1387
1388 23
        $sql .= ' ' . $_glueHelperInner . ' ' . $quoteString . ' ' . $_connector . ' ' . $valueInner . " \n";
1389 23
        $tmpCounter++;
1390 23
      }
1391
1392 23
      if ($_glueHelper === 'OR') {
1393 2
        $sql .= ' ) ';
1394 2
      }
1395
1396 23
      $arrayPairCounter++;
1397 23
    }
1398
1399 23
    return $sql;
1400
  }
1401
1402
  /**
1403
   * Quote && Escape e.g. a table name string.
1404
   *
1405
   * @param string $str
1406
   *
1407
   * @return string
1408
   */
1409 26
  public function quote_string($str)
1410
  {
1411 26
    $str = str_replace(
1412 26
        '`',
1413 26
        '``',
1414 26
        trim(
1415 26
            $this->escape($str, false),
1416
            '`'
1417 26
        )
1418 26
    );
1419
1420 26
    return '`' . $str . '`';
1421
  }
1422
1423
  /**
1424
   * Get errors from "$this->_errors".
1425
   *
1426
   * @return array
1427
   */
1428 1
  public function getErrors()
1429
  {
1430 1
    return $this->_debug->getErrors();
1431
  }
1432
1433
  /**
1434
   * Execute a "replace"-query.
1435
   *
1436
   * @param string      $table
1437
   * @param array       $data
1438
   * @param null|string $databaseName <p>use <strong>null</strong> if you will use the current database</p>
1439
   *
1440
   * @return false|int false on error
1441
   */
1442 1
  public function replace($table, array $data = array(), $databaseName = null)
1443
  {
1444
    // init
1445 1
    $table = trim($table);
1446
1447 1
    if ($table === '') {
1448 1
      $this->_debug->displayError('invalid table name');
1449
1450 1
      return false;
1451
    }
1452
1453 1
    if (count($data) === 0) {
1454 1
      $this->_debug->displayError('empty data for REPLACE');
1455
1456 1
      return false;
1457
    }
1458
1459
    // extracting column names
1460 1
    $columns = array_keys($data);
1461 1
    foreach ($columns as $k => $_key) {
1462
      /** @noinspection AlterInForeachInspection */
1463 1
      $columns[$k] = $this->quote_string($_key);
1464 1
    }
1465
1466 1
    $columns = implode(',', $columns);
1467
1468
    // extracting values
1469 1
    foreach ($data as $k => $_value) {
1470
      /** @noinspection AlterInForeachInspection */
1471 1
      $data[$k] = $this->secure($_value);
1472 1
    }
1473 1
    $values = implode(',', $data);
1474
1475 1
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1476
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1477
    }
1478
1479 1
    $sql = 'REPLACE INTO ' . $databaseName . $this->quote_string($table) . " ($columns) VALUES ($values);";
1480
1481 1
    return $this->query($sql);
1482
  }
1483
1484
  /**
1485
   * Execute a "update"-query.
1486
   *
1487
   * @param string       $table
1488
   * @param array        $data
1489
   * @param array|string $where
1490
   * @param null|string  $databaseName <p>use <strong>null</strong> if you will use the current database</p>
1491
   *
1492
   * @return false|int false on error
1493
   */
1494 6
  public function update($table, array $data = array(), $where = '1=1', $databaseName = null)
1495
  {
1496
    // init
1497 6
    $table = trim($table);
1498
1499 6
    if ($table === '') {
1500 1
      $this->_debug->displayError('invalid table name');
1501
1502 1
      return false;
1503
    }
1504
1505 6
    if (count($data) === 0) {
1506 2
      $this->_debug->displayError('empty data for UPDATE');
1507
1508 2
      return false;
1509
    }
1510
1511 6
    $SET = $this->_parseArrayPair($data);
1512
1513 6
    if (is_string($where)) {
1514 2
      $WHERE = $this->escape($where, false);
1515 6
    } elseif (is_array($where)) {
1516 4
      $WHERE = $this->_parseArrayPair($where, 'AND');
1517 4
    } else {
1518 1
      $WHERE = '';
1519
    }
1520
1521 6
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1522
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1523
    }
1524
1525 6
    $sql = 'UPDATE ' . $databaseName . $this->quote_string($table) . " SET $SET WHERE ($WHERE);";
1526
1527 6
    return $this->query($sql);
1528
  }
1529
1530
  /**
1531
   * Execute a "delete"-query.
1532
   *
1533
   * @param string       $table
1534
   * @param string|array $where
1535
   * @param string|null  $databaseName <p>use <strong>null</strong> if you will use the current database</p>
1536
   *
1537
   * @return false|int false on error
1538
   */
1539 2
  public function delete($table, $where, $databaseName = null)
1540
  {
1541
    // init
1542 2
    $table = trim($table);
1543
1544 2
    if ($table === '') {
1545 1
      $this->_debug->displayError('invalid table name');
1546
1547 1
      return false;
1548
    }
1549
1550 2
    if (is_string($where)) {
1551 1
      $WHERE = $this->escape($where, false);
1552 2
    } elseif (is_array($where)) {
1553 2
      $WHERE = $this->_parseArrayPair($where, 'AND');
1554 2
    } else {
1555 1
      $WHERE = '';
1556
    }
1557
1558 2
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1559
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1560
    }
1561
1562 2
    $sql = 'DELETE FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE);";
1563
1564 2
    return $this->query($sql);
1565
  }
1566
1567
  /**
1568
   * Execute a "select"-query.
1569
   *
1570
   * @param string       $table
1571
   * @param string|array $where
1572
   * @param string|null  $databaseName <p>use <strong>null</strong> if you will use the current database</p>
1573
   *
1574
   * @return false|Result false on error
1575
   */
1576 20
  public function select($table, $where = '1=1', $databaseName = null)
1577
  {
1578
    // init
1579 20
    $table = trim($table);
1580
1581 20
    if ($table === '') {
1582 1
      $this->_debug->displayError('invalid table name');
1583
1584 1
      return false;
1585
    }
1586
1587 20
    if (is_string($where)) {
1588 5
      $WHERE = $this->escape($where, false);
1589 20
    } elseif (is_array($where)) {
1590 16
      $WHERE = $this->_parseArrayPair($where, 'AND');
1591 16
    } else {
1592 1
      $WHERE = '';
1593
    }
1594
1595 20
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1596
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1597
    }
1598
1599 20
    $sql = 'SELECT * FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE);";
1600
1601 20
    return $this->query($sql);
1602
  }
1603
1604
  /**
1605
   * Get the last sql-error.
1606
   *
1607
   * @return string false on error
1608
   */
1609 1
  public function lastError()
1610
  {
1611 1
    $errors = $this->_debug->getErrors();
1612
1613 1
    return count($errors) > 0 ? end($errors) : false;
1614
  }
1615
1616
  /**
1617
   * @return Debug
1618
   */
1619 9
  public function getDebugger()
1620
  {
1621 9
    return $this->_debug;
1622
  }
1623
1624
  /**
1625
   * __destruct
1626
   *
1627
   */
1628
  public function __destruct()
1629
  {
1630
    // close the connection only if we don't save PHP-SESSION's in DB
1631
    if ($this->session_to_db === false) {
1632
      $this->close();
1633
    }
1634
  }
1635
1636
  /**
1637
   * Closes a previously opened database connection.
1638
   */
1639 2
  public function close()
1640
  {
1641 2
    $this->connected = false;
1642
1643 2
    if ($this->link) {
1644 2
      \mysqli_close($this->link);
1645 2
    }
1646 2
  }
1647
1648
  /**
1649
   * Prevent the instance from being cloned.
1650
   *
1651
   * @return void
1652
   */
1653
  private function __clone()
1654
  {
1655
  }
1656
1657
  /**
1658
   * __wakeup
1659
   *
1660
   * @return void
1661
   */
1662 2
  public function __wakeup()
1663
  {
1664 2
    $this->reconnect();
1665 2
  }
1666
1667
}
1668