Completed
Push — master ( 1be798...5156a7 )
by Lars
03:46
created

DB::_parseQueryParams()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4.0058

Importance

Changes 7
Bugs 2 Features 2
Metric Value
c 7
b 2
f 2
dl 0
loc 21
ccs 13
cts 14
cp 0.9286
rs 9.0534
cc 4
eloc 12
nc 3
nop 2
crap 4.0058
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()
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
140 4
      'LOCALTIME()',
141
      // Synonym for NOW()
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
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 7
      $this->port = @ini_get('mysqli.default_port');
191
    }
192
193 10
    if (!$this->socket) {
194
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
195 10
      $this->socket = @ini_get('mysqli.default_socket');
196 10
    }
197
198 10
    if ($exit_on_error === true || $exit_on_error === false) {
199 10
      $this->_debug->setExitOnError($exit_on_error);
200 10
    }
201
202 10
    if ($echo_on_error === true || $echo_on_error === false) {
203 10
      $this->_debug->setEchoOnError($echo_on_error);
204 10
    }
205
206 10
    $this->_debug->setLoggerClassName($logger_class_name);
207 10
    $this->_debug->setLoggerLevel($logger_level);
208
209 10
    $this->session_to_db = (boolean)$session_to_db;
210
211 10
    return $this->showConfigError();
212
  }
213
214
  /**
215
   * Show config errors by throw exceptions.
216
   *
217
   * @return bool
218
   *
219
   * @throws \Exception
220
   */
221 10
  public function showConfigError()
222
  {
223
224
    if (
225 10
        !$this->hostname
226 10
        ||
227 9
        !$this->username
228 9
        ||
229 8
        !$this->database
230 10
    ) {
231
232 3
      if (!$this->hostname) {
233 1
        throw new \Exception('no-sql-hostname');
234
      }
235
236 2
      if (!$this->username) {
237 1
        throw new \Exception('no-sql-username');
238
      }
239
240 1
      if (!$this->database) {
241 1
        throw new \Exception('no-sql-database');
242
      }
243
244
      return false;
245
    }
246
247 7
    return true;
248
  }
249
250
  /**
251
   * Open a new connection to the MySQL server.
252
   *
253
   * @return boolean
254
   */
255 9
  public function connect()
256
  {
257 9
    if ($this->isReady()) {
258 1
      return true;
259
    }
260
261 9
    mysqli_report(MYSQLI_REPORT_STRICT);
262
    try {
263 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...
264
265 9
      if (Helper::isMysqlndIsUsed() === true) {
266 9
        mysqli_options($this->link, MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
267 9
      }
268
269
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
270 9
      $this->connected = @mysqli_real_connect(
271 9
          $this->link,
272 9
          $this->hostname,
273 9
          $this->username,
274 9
          $this->password,
275 9
          $this->database,
276 9
          $this->port,
277 9
          $this->socket
278 9
      );
279 9
    } catch (\Exception $e) {
280 3
      $this->_debug->displayError('Error connecting to mysql server: ' . $e->getMessage(), true);
281
    }
282 6
    mysqli_report(MYSQLI_REPORT_OFF);
283
284 6
    if (!$this->connected) {
285
      $this->_debug->displayError('Error connecting to mysql server: ' . mysqli_connect_error(), true);
286
    } else {
287 6
      $this->set_charset($this->charset);
288
    }
289
290 6
    return $this->isReady();
291
  }
292
293
  /**
294
   * Check if db-connection is ready.
295
   *
296
   * @return boolean
297
   */
298 39
  public function isReady()
299
  {
300 39
    return $this->connected ? true : false;
301
  }
302
303
  /**
304
   * Get a new "Prepare"-Object for your sql-query.
305
   *
306
   * @param string $query
307
   *
308
   * @return Prepare
309
   */
310
  public function prepare($query)
311
  {
312
    return new Prepare($this, $query);
313
  }
314
315
  /**
316
   * Execute a sql-query and return the result-array for select-statements.
317
   *
318
   * @param $query
319
   *
320
   * @return mixed
321
   * @deprecated
322
   * @throws \Exception
323
   */
324
  public static function qry($query)
325
  {
326
    $db = self::getInstance();
327
328
    $args = func_get_args();
329
    $query = array_shift($args);
330
    $query = str_replace('?', '%s', $query);
331
    $args = array_map(
332
        array(
333
            $db,
334
            'escape',
335
        ),
336
        $args
337
    );
338
    array_unshift($args, $query);
339
    $query = call_user_func_array('sprintf', $args);
340
    $result = $db->query($query);
341
342
    if ($result instanceof Result) {
343
      return $result->fetchAllArray();
344
    } else {
345
      return $result;
346
    }
347
  }
348
349
  /**
350
   * getInstance()
351
   *
352
   * @param string      $hostname
353
   * @param string      $username
354
   * @param string      $password
355
   * @param string      $database
356
   * @param string      $port          default is (int)3306
357
   * @param string      $charset       default is 'utf8' or 'utf8mb4' (if supported)
358
   * @param bool|string $exit_on_error use a empty string "" or false to disable it
359
   * @param bool|string $echo_on_error use a empty string "" or false to disable it
360
   * @param string      $logger_class_name
361
   * @param string      $logger_level
362
   * @param bool|string $session_to_db use a empty string "" or false to disable it
363
   *
364
   * @return \voku\db\DB
365
   */
366 50
  public static function getInstance($hostname = '', $username = '', $password = '', $database = '', $port = '', $charset = '', $exit_on_error = '', $echo_on_error = '', $logger_class_name = '', $logger_level = '', $session_to_db = '')
367
  {
368
    /**
369
     * @var $instance DB[]
370
     */
371 50
    static $instance = array();
372
373
    /**
374
     * @var $firstInstance DB
375
     */
376 50
    static $firstInstance = null;
377
378
    if (
379 50
        $hostname . $username . $password . $database . $port . $charset == ''
380 50
        &&
381 9
        null !== $firstInstance
382 50
    ) {
383 9
      return $firstInstance;
384
    }
385
386 50
    $connection = md5(
387 50
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . (int)$session_to_db
388 50
    );
389
390 50
    if (!isset($instance[$connection])) {
391 10
      $instance[$connection] = new self(
392 10
          $hostname,
393 10
          $username,
394 10
          $password,
395 10
          $database,
396 10
          $port,
397 10
          $charset,
398 10
          $exit_on_error,
399 10
          $echo_on_error,
400 10
          $logger_class_name,
401 10
          $logger_level,
402
          $session_to_db
403 10
      );
404
405 4
      if (null === $firstInstance) {
406 1
        $firstInstance = $instance[$connection];
407 1
      }
408 4
    }
409
410 50
    return $instance[$connection];
411
  }
412
413
  /**
414
   * Execute a sql-query.
415
   *
416
   * @param string        $sql            sql-query
417
   *
418
   * @param array|boolean $params         "array" of sql-query-parameters
419
   *                                      "false" if you don't need any parameter (default)
420
   *
421
   * @return bool|int|Result              "Result" by "<b>SELECT</b>"-queries<br />
422
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
423
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
424
   *                                      "true" by e.g. "DROP"-queries<br />
425
   *                                      "false" on error
426
   *
427
   * @throws \Exception
428
   */
429 29
  public function query($sql = '', $params = false)
430
  {
431 29
    if (!$this->isReady()) {
432
      return false;
433
    }
434
435 29 View Code Duplication
    if (!$sql || $sql === '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
436 4
      $this->_debug->displayError('Can\'t execute an empty Query', false);
437
438 4
      return false;
439
    }
440
441
    if (
442
        $params !== false
443 27
        &&
444 2
        is_array($params)
445 27
        &&
446 2
        count($params) > 0
447 27
    ) {
448 2
      $sql = $this->_parseQueryParams($sql, $params);
449 2
    }
450
451 27
    $query_start_time = microtime(true);
452 27
    $query_result = mysqli_real_query($this->link, $sql);
453 27
    $query_duration = microtime(true) - $query_start_time;
454
455 27
    $this->query_count++;
456
457 27
    $mysqli_field_count = mysqli_field_count($this->link);
458 27
    if ($mysqli_field_count) {
459 24
      $result = mysqli_store_result($this->link);
460 24
    } else {
461 18
      $result = $query_result;
462
    }
463
464 27
    if ($result instanceof \mysqli_result) {
465
466
      // log the select query
467 23
      $this->_debug->logQuery($sql, $query_duration, $mysqli_field_count);
468
469
      // return query result object
470 23
      return new Result($sql, $result);
471
472 19
    } elseif ($query_result === true) {
473
474
      // "INSERT" || "REPLACE"
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
475 17 View Code Duplication
      if (preg_match('/^\s*"?(INSERT|REPLACE)\s+/i', $sql)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
476 16
        $insert_id = (int)$this->insert_id();
477 16
        $this->_debug->logQuery($sql, $query_duration, $insert_id);
478
479 16
        return $insert_id;
480
      }
481
482
      // "UPDATE" || "DELETE"
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
483 7 View Code Duplication
      if (preg_match('/^\s*"?(UPDATE|DELETE)\s+/i', $sql)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
484 7
        $affected_rows = (int)$this->affected_rows();
485 7
        $this->_debug->logQuery($sql, $query_duration, $affected_rows);
486
487 7
        return $affected_rows;
488
      }
489
490
      // log the ? query
491
      $this->_debug->logQuery($sql, $query_duration, 0);
492
493
      return true;
494
    }
495
496
    // log the error query
497 8
    $this->_debug->logQuery($sql, $query_duration, 0, true);
498
499 8
    return $this->queryErrorHandling(mysqli_error($this->link), $sql, $params);
500
  }
501
502
  /**
503
   * _parseQueryParams
504
   *
505
   * @param string $sql
506
   * @param array  $params
507
   *
508
   * @return string
509
   */
510 2
  private function _parseQueryParams($sql, array $params)
511
  {
512
    // is there anything to parse?
513 2
    if (strpos($sql, '?') === false) {
514
      return $sql;
515
    }
516
517 2
    if (count($params) > 0) {
518 2
      $parseKey = md5(uniqid(mt_rand(), true));
519 2
      $sql = str_replace('?', $parseKey, $sql);
520
521 2
      $k = 0;
522 2
      while (strpos($sql, $parseKey) !== false) {
523 2
        $value = $this->secure($params[$k]);
524 2
        $sql = preg_replace("/$parseKey/", $value, $sql, 1);
525 2
        $k++;
526 2
      }
527 2
    }
528
529 2
    return $sql;
530
  }
531
532
  /**
533
   * Try to secure a variable, so can you use it in sql-queries.
534
   *
535
   * <p>
536
   * <strong>int:</strong> (also strings that contains only an int-value)<br />
537
   * 1. parse into "int"
538
   * </p><br />
539
   *
540
   * <p>
541
   * <strong>float:</strong><br />
542
   * 1. return "float"
543
   * </p><br />
544
   *
545
   * <p>
546
   * <strong>string:</strong><br />
547
   * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'<br />
548
   * 2. trim whitespace<br />
549
   * 3. trim '<br />
550
   * 4. escape the string (and remove non utf-8 chars)<br />
551
   * 5. trim ' again (because we maybe removed some chars)<br />
552
   * 6. add ' around the new string<br />
553
   * </p><br />
554
   *
555
   * <p>
556
   * <strong>array:</strong><br />
557
   * 1. return null
558
   * </p><br />
559
   *
560
   * <p>
561
   * <strong>object:</strong><br />
562
   * 1. return false
563
   * </p><br />
564
   *
565
   * <p>
566
   * <strong>null:</strong><br />
567
   * 1. return null
568
   * </p>
569
   *
570
   * @param mixed $var
571
   *
572
   * @return mixed
573
   */
574 20
  public function secure($var)
575
  {
576
    if (
577
        $var === ''
578 20
        ||
579 20
        ($this->_convert_null_to_empty_string === true && $var === null)
580 20
    ) {
581 1
      return "''";
582
    }
583
584 20
    if (in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
585 1
      return $var;
586
    }
587
588 20
    if (is_string($var)) {
589 16
      $var = trim(trim($var), "'");
590 16
    }
591
592 20
    $var = $this->escape($var, false, false, null);
593
594 20
    if (is_string($var)) {
595 16
      $var = "'" . trim($var, "'") . "'";
596 16
    }
597
598 20
    return $var;
599
  }
600
601
  /**
602
   * Escape: Use "mysqli_real_escape_string" and clean non UTF-8 chars + some extra optional stuff.
603
   *
604
   * @param mixed     $var           boolean: convert into "integer"<br />
605
   *                                 int: int (don't change it)<br />
606
   *                                 float: float (don't change it)<br />
607
   *                                 null: null (don't change it)<br />
608
   *                                 array: run escape() for every key => value<br />
609
   *                                 string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
610
   * @param bool      $stripe_non_utf8
611
   * @param bool      $html_entity_decode
612
   * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
613
   *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
614
   *                                 <strong>null</strong> => Convert the array into null, every time.
615
   *
616
   * @return mixed
617
   */
618 26
  public function escape($var = '', $stripe_non_utf8 = true, $html_entity_decode = false, $convert_array = false)
619
  {
620 26
    if ($var === null) {
621 2
      return null;
622
    }
623
624
    // save the current value as int (for later usage)
625 26
    if (!is_object($var)) {
626 26
      $varInt = (int)$var;
627 26
    }
628
629
    /** @noinspection TypeUnsafeComparisonInspection */
630
    if (
631 26
        is_int($var)
632
        ||
633 26
        is_bool($var)
634 26
        ||
635 26
        (isset($varInt, $var[0]) && $var[0] != '0' && "$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...
636 26
    ) {
637
638
      // "int" || int || bool
639
640 20
      return (int)$var;
641
642 26
    } elseif (is_float($var)) {
643
644
      // float
645
646 5
      return $var;
647
648 26
    } elseif (is_array($var)) {
649
650
      // array
651
652 3
      if ($convert_array === null) {
653 3
        return null;
654
      }
655
656 1
      $varCleaned = array();
657 1
      foreach ($var as $key => $value) {
658
659 1
        $key = $this->escape($key, $stripe_non_utf8, $html_entity_decode);
660 1
        $value = $this->escape($value, $stripe_non_utf8, $html_entity_decode);
661
662
        /** @noinspection OffsetOperationsInspection */
663 1
        $varCleaned[$key] = $value;
664 1
      }
665
666 1
      if ($convert_array === true) {
667 1
        $varCleaned = implode(',', $varCleaned);
668
669 1
        return $varCleaned;
670
      } else {
671 1
        return (array)$varCleaned;
672
      }
673
    }
674
675
    if (
676 26
        is_string($var)
677
        ||
678 3
        (is_object($var) && method_exists($var, '__toString'))
679 26
    ) {
680
681
      // "string"
682
683 26
      $var = (string)$var;
684
685 26
      if ($stripe_non_utf8 === true) {
686 9
        $var = UTF8::cleanup($var);
687 9
      }
688
689 26
      if ($html_entity_decode === true) {
690
        // use no-html-entity for db
691 1
        $var = UTF8::html_entity_decode($var);
692 1
      }
693
694 26
      $var = get_magic_quotes_gpc() ? stripslashes($var) : $var;
695
696 26
      $var = mysqli_real_escape_string($this->getLink(), $var);
697
698 26
      return (string)$var;
699
700 3
    } elseif ($var instanceof \DateTime) {
701
702
      // "DateTime"-object
703
704
      try {
705 3
        return $this->escape($var->format('Y-m-d H:i:s'), false);
706
      } catch (\Exception $e) {
707
        return null;
708
      }
709
710
    } else {
711 2
      return false;
712
    }
713
  }
714
715
  /**
716
   * Get the mysqli-link (link identifier returned by mysqli-connect).
717
   *
718
   * @return \mysqli
719
   */
720 28
  public function getLink()
721
  {
722 28
    return $this->link;
723
  }
724
725
  /**
726
   * Returns the auto generated id used in the last query.
727
   *
728
   * @return int|string
729
   */
730 16
  public function insert_id()
731
  {
732 16
    return mysqli_insert_id($this->link);
733
  }
734
735
  /**
736
   * Gets the number of affected rows in a previous MySQL operation.
737
   *
738
   * @return int
739
   */
740 7
  public function affected_rows()
741
  {
742 7
    return mysqli_affected_rows($this->link);
743
  }
744
745
  /**
746
   * Error-handling for the sql-query.
747
   *
748
   * @param string     $errorMsg
749
   * @param string     $sql
750
   * @param array|bool $sqlParams false if there wasn't any parameter
751
   *
752
   * @throws \Exception
753
   *
754
   * @return bool
755
   */
756 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...
757
  {
758 9
    if ($errorMsg === 'DB server has gone away' || $errorMsg === 'MySQL server has gone away') {
759 1
      static $reconnectCounter;
760
761
      // exit if we have more then 3 "DB server has gone away"-errors
762 1
      if ($reconnectCounter > 3) {
763
        $this->_debug->mailToAdmin('SQL-Fatal-Error', $errorMsg . ":\n<br />" . $sql, 5);
764
        throw new \Exception($errorMsg);
765
      } else {
766 1
        $this->_debug->mailToAdmin('SQL-Error', $errorMsg . ":\n<br />" . $sql);
767
768
        // reconnect
769 1
        $reconnectCounter++;
770 1
        $this->reconnect(true);
771
772
        // re-run the current query
773 1
        return $this->query($sql, $sqlParams);
774
      }
775
    } else {
776 8
      $this->_debug->mailToAdmin('SQL-Warning', $errorMsg . ":\n<br />" . $sql);
777
778
      // this query returned an error, we must display it (only for dev) !!!
779 8
      $this->_debug->displayError($errorMsg . ' | ' . $sql);
780
    }
781
782 8
    return false;
783
  }
784
785
  /**
786
   * Reconnect to the MySQL-Server.
787
   *
788
   * @param bool $checkViaPing
789
   *
790
   * @return bool
791
   */
792 3
  public function reconnect($checkViaPing = false)
793
  {
794 3
    $ping = false;
795
796 3
    if ($checkViaPing === true) {
797 2
      $ping = $this->ping();
798 2
    }
799
800 3
    if ($ping !== true) {
801 3
      $this->connected = false;
802 3
      $this->connect();
803 3
    }
804
805 3
    return $this->isReady();
806
  }
807
808
  /**
809
   * Pings a server connection, or tries to reconnect
810
   * if the connection has gone down.
811
   *
812
   * @return boolean
813
   */
814 3
  public function ping()
815
  {
816
    if (
817 3
        $this->link
818 3
        &&
819 3
        $this->link instanceof \mysqli
820 3
    ) {
821
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
822 3
      return @mysqli_ping($this->link);
823
    } else {
824
      return false;
825
    }
826
  }
827
828
  /**
829
   * Execute select/insert/update/delete sql-queries.
830
   *
831
   * @param string $query    sql-query
832
   * @param bool   $useCache use cache?
833
   * @param int    $cacheTTL cache-ttl in seconds
834
   *
835
   * @return mixed "array" by "<b>SELECT</b>"-queries<br />
836
   *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
837
   *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
838
   *               "true" by e.g. "DROP"-queries<br />
839
   *               "false" on error
840
   */
841 3
  public static function execSQL($query, $useCache = false, $cacheTTL = 3600)
842
  {
843 3
    $db = self::getInstance();
844
845 3
    if ($useCache === true) {
846 1
      $cache = new Cache(null, null, false, $useCache);
847 1
      $cacheKey = 'sql-' . md5($query);
848
849
      if (
850 1
          $cache->getCacheIsReady() === true
851 1
          &&
852 1
          $cache->existsItem($cacheKey)
853 1
      ) {
854 1
        return $cache->getItem($cacheKey);
855
      }
856
857 1
    } else {
858 3
      $cache = false;
859
    }
860
861 3
    $result = $db->query($query);
862
863 3
    if ($result instanceof Result) {
864
865 1
      $return = $result->fetchAllArray();
866
867
      if (
868 1
          isset($cacheKey)
869 1
          &&
870
          $useCache === true
871 1
          &&
872
          $cache instanceof Cache
873 1
          &&
874 1
          $cache->getCacheIsReady() === true
875 1
      ) {
876 1
        $cache->setItem($cacheKey, $return, $cacheTTL);
877 1
      }
878
879 1
    } else {
880 2
      $return = $result;
881
    }
882
883 3
    return $return;
884
  }
885
886
  /**
887
   * Get the current charset.
888
   *
889
   * @return string
890
   */
891 1
  public function get_charset()
892
  {
893 1
    return $this->charset;
894
  }
895
896
  /**
897
   * Set the current charset.
898
   *
899
   * @param string $charset
900
   *
901
   * @return bool
902
   */
903 7
  public function set_charset($charset)
904
  {
905 7
    $charsetLower = strtolower($charset);
906 7
    if ($charsetLower === 'utf8' || $charsetLower === 'utf-8') {
907 5
      $charset = 'utf8';
908 5
    }
909 7
    if ($charset === 'utf8' && Helper::isUtf8mb4Supported($this) === true) {
910 5
      $charset = 'utf8mb4';
911 5
    }
912
913 7
    $this->charset = (string)$charset;
914
915 7
    $return = mysqli_set_charset($this->link, $charset);
916
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
917 7
    @mysqli_query($this->link, 'SET CHARACTER SET ' . $charset);
918
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
919 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...
920
921 7
    return $return;
922
  }
923
924
  /**
925
   * Set the option to convert null to "''" (empty string).
926
   *
927
   * Used in secure() => select(), insert(), update(), delete()
928
   *
929
   * @param $bool
930
   */
931 1
  public function set_convert_null_to_empty_string($bool)
932
  {
933 1
    $this->_convert_null_to_empty_string = (bool)$bool;
934 1
  }
935
936
  /**
937
   * Get all table-names via "SHOW TABLES".
938
   *
939
   * @return array
940
   */
941 1
  public function getAllTables()
942
  {
943 1
    $query = 'SHOW TABLES';
944 1
    $result = $this->query($query);
945
946 1
    return $result->fetchAllArray();
947
  }
948
949
  /**
950
   * Execute a sql-multi-query.
951
   *
952
   * @param string $sql
953
   *
954
   * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
955
   *                        "boolean" by only "<b>INSERT</b>"-queries<br />
956
   *                        "boolean" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
957
   *                        "boolean" by only by e.g. "DROP"-queries<br />
958
   *
959
   * @throws \Exception
960
   */
961 1
  public function multi_query($sql)
962
  {
963 1
    if (!$this->isReady()) {
964
      return false;
965
    }
966
967 1 View Code Duplication
    if (!$sql || $sql === '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
968 1
      $this->_debug->displayError('Can\'t execute an empty Query', false);
969
970 1
      return false;
971
    }
972
973 1
    $query_start_time = microtime(true);
974 1
    $resultTmp = mysqli_multi_query($this->link, $sql);
975 1
    $query_duration = microtime(true) - $query_start_time;
976
977 1
    $this->_debug->logQuery($sql, $query_duration, 0);
978
979 1
    $returnTheResult = false;
980 1
    $result = array();
981 1
    if ($resultTmp) {
982
      do {
983 1
        $resultTmpInner = mysqli_store_result($this->link);
984
985 1
        if ($resultTmpInner instanceof \mysqli_result) {
986 1
          $returnTheResult = true;
987 1
          $result[] = new Result($sql, $resultTmpInner);
988 1
        } else {
989 1
          $errorMsg = mysqli_error($this->link);
990
991
          // is the query successful
992 1
          if ($resultTmpInner === true || !$errorMsg) {
993 1
            $result[] = true;
994 1
          } else {
995
            $result[] = $this->queryErrorHandling($errorMsg, $sql);
996
          }
997
        }
998 1
      } while (mysqli_more_results($this->link) === true ? mysqli_next_result($this->link) : false);
999
1000 1
    } else {
1001
1002
      $errorMsg = mysqli_error($this->link);
1003
1004
      if ($this->_debug->checkForDev() === true) {
1005
        echo "Info: maybe you have to increase your 'max_allowed_packet = 30M' in the config: 'my.conf' \n<br />";
1006
        echo 'Error:' . $errorMsg;
1007
      }
1008
1009
      $this->_debug->mailToAdmin('SQL-Error in mysqli_multi_query', $errorMsg . ":\n<br />" . $sql);
1010
    }
1011
1012
    // return the result only if there was a "SELECT"-query
1013 1
    if ($returnTheResult === true) {
1014 1
      return $result;
1015
    }
1016
1017 1
    if (!in_array(false, $result, true)) {
1018 1
      return true;
1019
    } else {
1020
      return false;
1021
    }
1022
  }
1023
1024
  /**
1025
   * alias: "beginTransaction()"
1026
   */
1027 1
  public function startTransaction()
1028
  {
1029 1
    $this->beginTransaction();
1030 1
  }
1031
1032
  /**
1033
   * Begins a transaction, by turning off auto commit.
1034
   *
1035
   * @return boolean this will return true or false indicating success of transaction
1036
   */
1037 4
  public function beginTransaction()
1038
  {
1039 4
    $this->clearErrors();
1040
1041 4
    if ($this->inTransaction() === true) {
1042 1
      $this->_debug->displayError('Error mysql server already in transaction!', true);
1043
1044
      return false;
1045 4
    } elseif (mysqli_connect_errno()) {
1046
      $this->_debug->displayError('Error connecting to mysql server: ' . mysqli_connect_error(), true);
1047
1048
      return false;
1049
    } else {
1050 4
      $this->_in_transaction = true;
1051 4
      mysqli_autocommit($this->link, false);
1052
1053 4
      return true;
1054
1055
    }
1056
  }
1057
1058
  /**
1059
   * Clear the errors in "_debug->_errors".
1060
   *
1061
   * @return bool
1062
   */
1063 4
  public function clearErrors()
1064
  {
1065 4
    return $this->_debug->clearErrors();
1066
  }
1067
1068
  /**
1069
   * Check if we are in a transaction.
1070
   *
1071
   * @return boolean
1072
   */
1073 4
  public function inTransaction()
1074
  {
1075 4
    return $this->_in_transaction;
1076
  }
1077
1078
  /**
1079
   * Ends a transaction and commits if no errors, then ends autocommit.
1080
   *
1081
   * @return boolean this will return true or false indicating success of transactions
1082
   */
1083 2
  public function endTransaction()
1084
  {
1085
1086 2
    if (!$this->errors()) {
1087 1
      mysqli_commit($this->link);
1088 1
      $return = true;
1089 1
    } else {
1090 1
      $this->rollback();
1091 1
      $return = false;
1092
    }
1093
1094 2
    mysqli_autocommit($this->link, true);
1095 2
    $this->_in_transaction = false;
1096
1097 2
    return $return;
1098
  }
1099
1100
  /**
1101
   * Get all errors from "$this->_errors".
1102
   *
1103
   * @return array|false false === on errors
1104
   */
1105 2
  public function errors()
1106
  {
1107 2
    $errors = $this->_debug->getErrors();
1108
1109 2
    return count($errors) > 0 ? $errors : false;
1110
  }
1111
1112
  /**
1113
   * Rollback in a transaction.
1114
   */
1115 2
  public function rollback()
1116
  {
1117
    // init
1118 2
    $return = false;
1119
1120 2
    if ($this->_in_transaction === true) {
1121 2
      $return = mysqli_rollback($this->link);
1122 2
      mysqli_autocommit($this->link, true);
1123 2
      $this->_in_transaction = false;
1124 2
    }
1125
1126 2
    return $return;
1127
  }
1128
1129
  /**
1130
   * Execute a "insert"-query.
1131
   *
1132
   * @param string $table
1133
   * @param array  $data
1134
   *
1135
   * @return false|int false on error
1136
   */
1137 15
  public function insert($table, $data = array())
1138
  {
1139 15
    $table = trim($table);
1140
1141 15
    if ($table === '') {
1142 2
      $this->_debug->displayError('invalid table name');
1143
1144 1
      return false;
1145
    }
1146
1147 14
    if (count($data) === 0) {
1148 3
      $this->_debug->displayError('empty data for INSERT');
1149
1150 2
      return false;
1151
    }
1152
1153 12
    $SET = $this->_parseArrayPair($data);
1154
1155 12
    $sql = 'INSERT INTO ' . $this->quote_string($table) . " SET $SET;";
1156
1157 12
    return $this->query($sql);
1158
  }
1159
1160
  /**
1161
   * Parses arrays with value pairs and generates SQL to use in queries.
1162
   *
1163
   * @param array  $arrayPair
1164
   * @param string $glue this is the separator
1165
   *
1166
   * @return string
1167
   */
1168 17
  private function _parseArrayPair($arrayPair, $glue = ',')
1169
  {
1170
    // init
1171 17
    $sql = '';
1172 17
    $pairs = array();
1173
1174
    /** @noinspection IsEmptyFunctionUsageInspection */
1175 17
    if (!empty($arrayPair)) {
1176
1177 17
      foreach ($arrayPair as $_key => $_value) {
1178 17
        $_connector = '=';
1179 17
        $_key_upper = strtoupper($_key);
1180
1181 17
        if (strpos($_key_upper, ' NOT') !== false) {
1182 2
          $_connector = 'NOT';
1183 2
        }
1184
1185 17
        if (strpos($_key_upper, ' IS') !== false) {
1186 1
          $_connector = 'IS';
1187 1
        }
1188
1189 17
        if (strpos($_key_upper, ' IS NOT') !== false) {
1190 1
          $_connector = 'IS NOT';
1191 1
        }
1192
1193 17
        if (strpos($_key_upper, ' IN') !== false) {
1194 1
          $_connector = 'IN';
1195 1
        }
1196
1197 17
        if (strpos($_key_upper, ' NOT IN') !== false) {
1198 1
          $_connector = 'NOT IN';
1199 1
        }
1200
1201 17
        if (strpos($_key_upper, ' BETWEEN') !== false) {
1202 1
          $_connector = 'BETWEEN';
1203 1
        }
1204
1205 17
        if (strpos($_key_upper, ' NOT BETWEEN') !== false) {
1206 1
          $_connector = 'NOT BETWEEN';
1207 1
        }
1208
1209 17
        if (strpos($_key_upper, ' LIKE') !== false) {
1210 2
          $_connector = 'LIKE';
1211 2
        }
1212
1213 17
        if (strpos($_key_upper, ' NOT LIKE') !== false) {
1214 2
          $_connector = 'NOT LIKE';
1215 2
        }
1216
1217 17 View Code Duplication
        if (strpos($_key_upper, ' >') !== false && strpos($_key_upper, ' =') === false) {
1218 2
          $_connector = '>';
1219 2
        }
1220
1221 17 View Code Duplication
        if (strpos($_key_upper, ' <') !== false && strpos($_key_upper, ' =') === false) {
1222 1
          $_connector = '<';
1223 1
        }
1224
1225 17
        if (strpos($_key_upper, ' >=') !== false) {
1226 2
          $_connector = '>=';
1227 2
        }
1228
1229 17
        if (strpos($_key_upper, ' <=') !== false) {
1230 1
          $_connector = '<=';
1231 1
        }
1232
1233 17
        if (strpos($_key_upper, ' <>') !== false) {
1234 1
          $_connector = '<>';
1235 1
        }
1236
1237 17
        if (is_array($_value) === true) {
1238 1
          foreach ($_value as $oldKey => $oldValue) {
1239 1
            $_value[$oldKey] = $this->secure($oldValue);
1240 1
          }
1241
1242 1
          if ($_connector === 'NOT IN' || $_connector === 'IN') {
1243 1
            $_value = '(' . implode(',', $_value) . ')';
1244 1
          } elseif ($_connector === 'NOT BETWEEN' || $_connector === 'BETWEEN') {
1245 1
            $_value = '(' . implode(' AND ', $_value) . ')';
1246 1
          }
1247
1248 1
        } else {
1249 17
          $_value = $this->secure($_value);
1250
        }
1251
1252 17
        $quoteString = $this->quote_string(trim(str_ireplace($_connector, '', $_key)));
1253 17
        $pairs[] = ' ' . $quoteString . ' ' . $_connector . ' ' . $_value . " \n";
1254 17
      }
1255
1256 17
      $sql = implode($glue, $pairs);
1257 17
    }
1258
1259 17
    return $sql;
1260
  }
1261
1262
  /**
1263
   * Quote && Escape e.g. a table name string.
1264
   *
1265
   * @param string $str
1266
   *
1267
   * @return string
1268
   */
1269 20
  public function quote_string($str)
1270
  {
1271 20
    return '`' . $this->escape($str, false) . '`';
1272
  }
1273
1274
  /**
1275
   * Get errors from "$this->_errors".
1276
   *
1277
   * @return array
1278
   */
1279 1
  public function getErrors()
1280
  {
1281 1
    return $this->_debug->getErrors();
1282
  }
1283
1284
  /**
1285
   * Execute a "replace"-query.
1286
   *
1287
   * @param string $table
1288
   * @param array  $data
1289
   *
1290
   * @return false|int false on error
1291
   */
1292 1
  public function replace($table, $data = array())
1293
  {
1294
1295 1
    $table = trim($table);
1296
1297 1
    if ($table === '') {
1298 1
      $this->_debug->displayError('invalid table name');
1299
1300 1
      return false;
1301
    }
1302
1303 1
    if (count($data) === 0) {
1304 1
      $this->_debug->displayError('empty data for REPLACE');
1305
1306 1
      return false;
1307
    }
1308
1309
    // extracting column names
1310 1
    $columns = array_keys($data);
1311 1
    foreach ($columns as $k => $_key) {
1312
      /** @noinspection AlterInForeachInspection */
1313 1
      $columns[$k] = $this->quote_string($_key);
1314 1
    }
1315
1316 1
    $columns = implode(',', $columns);
1317
1318
    // extracting values
1319 1
    foreach ($data as $k => $_value) {
1320
      /** @noinspection AlterInForeachInspection */
1321 1
      $data[$k] = $this->secure($_value);
1322 1
    }
1323 1
    $values = implode(',', $data);
1324
1325 1
    $sql = 'REPLACE INTO ' . $this->quote_string($table) . " ($columns) VALUES ($values);";
1326
1327 1
    return $this->query($sql);
1328
  }
1329
1330
  /**
1331
   * Execute a "update"-query.
1332
   *
1333
   * @param string       $table
1334
   * @param array        $data
1335
   * @param array|string $where
1336
   *
1337
   * @return false|int false on error
1338
   */
1339 6
  public function update($table, $data = array(), $where = '1=1')
1340
  {
1341 6
    $table = trim($table);
1342
1343 6
    if ($table === '') {
1344 1
      $this->_debug->displayError('invalid table name');
1345
1346 1
      return false;
1347
    }
1348
1349 6
    if (count($data) === 0) {
1350 2
      $this->_debug->displayError('empty data for UPDATE');
1351
1352 2
      return false;
1353
    }
1354
1355 6
    $SET = $this->_parseArrayPair($data);
1356
1357 6
    if (is_string($where)) {
1358 2
      $WHERE = $this->escape($where, false);
1359 6
    } elseif (is_array($where)) {
1360 4
      $WHERE = $this->_parseArrayPair($where, 'AND');
1361 4
    } else {
1362 1
      $WHERE = '';
1363
    }
1364
1365 6
    $sql = 'UPDATE ' . $this->quote_string($table) . " SET $SET WHERE ($WHERE);";
1366
1367 6
    return $this->query($sql);
1368
  }
1369
1370
  /**
1371
   * Execute a "delete"-query.
1372
   *
1373
   * @param string       $table
1374
   * @param string|array $where
1375
   *
1376
   * @return false|int false on error
1377
   */
1378 1 View Code Duplication
  public function delete($table, $where)
1379
  {
1380
1381 1
    $table = trim($table);
1382
1383 1
    if ($table === '') {
1384 1
      $this->_debug->displayError('invalid table name');
1385
1386 1
      return false;
1387
    }
1388
1389 1
    if (is_string($where)) {
1390 1
      $WHERE = $this->escape($where, false);
1391 1
    } elseif (is_array($where)) {
1392 1
      $WHERE = $this->_parseArrayPair($where, 'AND');
1393 1
    } else {
1394 1
      $WHERE = '';
1395
    }
1396
1397 1
    $sql = 'DELETE FROM ' . $this->quote_string($table) . " WHERE ($WHERE);";
1398
1399 1
    return $this->query($sql);
1400
  }
1401
1402
  /**
1403
   * Execute a "select"-query.
1404
   *
1405
   * @param string       $table
1406
   * @param string|array $where
1407
   *
1408
   * @return false|Result false on error
1409
   */
1410 18 View Code Duplication
  public function select($table, $where = '1=1')
1411
  {
1412
1413 18
    if ($table === '') {
1414 1
      $this->_debug->displayError('invalid table name');
1415
1416 1
      return false;
1417
    }
1418
1419 18
    if (is_string($where)) {
1420 3
      $WHERE = $this->escape($where, false);
1421 18
    } elseif (is_array($where)) {
1422 16
      $WHERE = $this->_parseArrayPair($where, 'AND');
1423 16
    } else {
1424 1
      $WHERE = '';
1425
    }
1426
1427 18
    $sql = 'SELECT * FROM ' . $this->quote_string($table) . " WHERE ($WHERE);";
1428
1429 18
    return $this->query($sql);
1430
  }
1431
1432
  /**
1433
   * Get the last sql-error.
1434
   *
1435
   * @return string false on error
1436
   */
1437 1
  public function lastError()
1438
  {
1439 1
    $errors = $this->_debug->getErrors();
1440
1441 1
    return count($errors) > 0 ? end($errors) : false;
1442
  }
1443
1444
  /**
1445
   * @return Debug
1446
   */
1447 7
  public function getDebugger()
1448
  {
1449 7
    return $this->_debug;
1450
  }
1451
1452
  /**
1453
   * __destruct
1454
   *
1455
   */
1456
  public function __destruct()
1457
  {
1458
    // close the connection only if we don't save PHP-SESSION's in DB
1459
    if ($this->session_to_db === false) {
1460
      $this->close();
1461
    }
1462
  }
1463
1464
  /**
1465
   * Closes a previously opened database connection.
1466
   */
1467 2
  public function close()
1468
  {
1469 2
    $this->connected = false;
1470
1471 2
    if ($this->link) {
1472 2
      mysqli_close($this->link);
1473 2
    }
1474 2
  }
1475
1476
  /**
1477
   * Prevent the instance from being cloned.
1478
   *
1479
   * @return void
1480
   */
1481
  private function __clone()
1482
  {
1483
  }
1484
1485
  /**
1486
   * __wakeup
1487
   *
1488
   * @return void
1489
   */
1490 2
  public function __wakeup()
1491
  {
1492 2
    $this->reconnect();
1493 2
  }
1494
1495
}
1496