Completed
Push — master ( db4a8c...45870c )
by Lars
16:59 queued 01:30
created

DB::query()   C

Complexity

Conditions 12
Paths 22

Size

Total Lines 79
Code Lines 40

Duplication

Lines 17
Ratio 21.52 %

Code Coverage

Tests 39
CRAP Score 12.0022

Importance

Changes 0
Metric Value
dl 17
loc 79
ccs 39
cts 40
cp 0.975
rs 5.1215
c 0
b 0
f 0
cc 12
eloc 40
nc 22
nop 2
crap 12.0022

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\db\exceptions\DBConnectException;
7
use voku\db\exceptions\DBGoneAwayException;
8
use voku\db\exceptions\QueryException;
9
use voku\helper\UTF8;
10
11
/**
12
 * DB: This class can handle DB queries via MySQLi.
13
 *
14
 * @package voku\db
15
 */
16
final class DB
17
{
18
19
  /**
20
   * @var int
21
   */
22
  public $query_count = 0;
23
24
  /**
25
   * @var \mysqli|null
26
   */
27
  private $link = null;
28
29
  /**
30
   * @var bool
31
   */
32
  private $connected = false;
33
34
  /**
35
   * @var array
36
   */
37
  private $mysqlDefaultTimeFunctions;
38
39
  /**
40
   * @var string
41
   */
42
  private $hostname = '';
43
44
  /**
45
   * @var string
46
   */
47
  private $username = '';
48
49
  /**
50
   * @var string
51
   */
52
  private $password = '';
53
54
  /**
55
   * @var string
56
   */
57
  private $database = '';
58
59
  /**
60
   * @var int
61
   */
62
  private $port = 3306;
63
64
  /**
65
   * @var string
66
   */
67
  private $charset = 'utf8';
68
69
  /**
70
   * @var string
71
   */
72
  private $socket = '';
73
74
  /**
75
   * @var bool
76
   */
77
  private $session_to_db = false;
78
79
  /**
80
   * @var bool
81
   */
82
  private $_in_transaction = false;
83
84
  /**
85
   * @var bool
86
   */
87
  private $_convert_null_to_empty_string = false;
88
89
  /**
90
   * @var bool
91
   */
92
  private $_ssl = false;
93
94
  /**
95
   * The path name to the key file
96
   *
97
   * @var string
98
   */
99
  private $_clientkey;
100
101
  /**
102
   * The path name to the certificate file
103
   *
104
   * @var string
105
   */
106
  private $_clientcert;
107
108
  /**
109
   * The path name to the certificate authority file
110
   *
111
   * @var string
112
   */
113
  private $_cacert;
114
115
  /**
116
   * @var Debug
117
   */
118
  private $_debug;
119
120
  /**
121
   * __construct()
122
   *
123
   * @param string      $hostname
124
   * @param string      $username
125
   * @param string      $password
126
   * @param string      $database
127
   * @param int         $port
128
   * @param string      $charset
129
   * @param bool|string $exit_on_error    <p>Throw a 'Exception' when a query failed, otherwise it will return 'false'.
130
   *                                      Use a empty string "" or false to disable it.</p>
131
   * @param bool|string $echo_on_error    <p>Echo the error if "checkForDev()" returns true.
132
   *                                      Use a empty string "" or false to disable it.</p>
133
   * @param string      $logger_class_name
134
   * @param string      $logger_level     <p>'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'</p>
135
   * @param array       $extra_config     <p>
136
   *                                      'session_to_db' => false|true<br>
137
   *                                      'socket' => 'string (path)'<br>
138
   *                                      'ssl' => 'bool'<br>
139
   *                                      'clientkey' => 'string (path)'<br>
140
   *                                      'clientcert' => 'string (path)'<br>
141
   *                                      'cacert' => 'string (path)'<br>
142
   *                                      </p>
143
   */
144 11
  protected function __construct($hostname, $username, $password, $database, $port, $charset, $exit_on_error, $echo_on_error, $logger_class_name, $logger_level, $extra_config = array())
145
  {
146 11
    $this->connected = false;
147
148 11
    $this->_debug = new Debug($this);
149
150 11
    $this->_loadConfig(
151 11
        $hostname,
152 11
        $username,
153 11
        $password,
154 11
        $database,
155 11
        $port,
156 11
        $charset,
157 11
        $exit_on_error,
158 11
        $echo_on_error,
159 11
        $logger_class_name,
160 11
        $logger_level,
161
        $extra_config
162 11
    );
163
164 8
    $this->connect();
165
166 5
    $this->mysqlDefaultTimeFunctions = array(
167
      // Returns the current date.
168 5
      'CURDATE()',
169
      // CURRENT_DATE	| Synonyms for CURDATE()
170 5
      'CURRENT_DATE()',
171
      // CURRENT_TIME	| Synonyms for CURTIME()
172 5
      'CURRENT_TIME()',
173
      // CURRENT_TIMESTAMP | Synonyms for NOW()
174 5
      'CURRENT_TIMESTAMP()',
175
      // Returns the current time.
176 5
      'CURTIME()',
177
      // Synonym for NOW()
178 5
      'LOCALTIME()',
179
      // Synonym for NOW()
180 5
      'LOCALTIMESTAMP()',
181
      // Returns the current date and time.
182 5
      'NOW()',
183
      // Returns the time at which the function executes.
184 5
      'SYSDATE()',
185
      // Returns a UNIX timestamp.
186 5
      'UNIX_TIMESTAMP()',
187
      // Returns the current UTC date.
188 5
      'UTC_DATE()',
189
      // Returns the current UTC time.
190 5
      'UTC_TIME()',
191
      // Returns the current UTC date and time.
192 5
      'UTC_TIMESTAMP()',
193
    );
194 5
  }
195
196
  /**
197
   * Prevent the instance from being cloned.
198
   *
199
   * @return void
200
   */
201
  private function __clone()
202
  {
203
  }
204
205
  /**
206
   * __destruct
207
   *
208
   */
209
  public function __destruct()
210
  {
211
    // close the connection only if we don't save PHP-SESSION's in DB
212
    if ($this->session_to_db === false) {
213
      $this->close();
214
    }
215
  }
216
217
  /**
218
   * @param null|string $sql
219
   * @param array       $bindings
220
   *
221
   * @return bool|int|Result|DB           <p>
222
   *                                      "DB" by "$sql" === null<br />
223
   *                                      "Result" by "<b>SELECT</b>"-queries<br />
224
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
225
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
226
   *                                      "true" by e.g. "DROP"-queries<br />
227
   *                                      "false" on error
228
   *                                      </p>
229
   */
230 2
  public function __invoke($sql = null, array $bindings = array())
231
  {
232 2
    return isset($sql) ? $this->query($sql, $bindings) : $this;
233
  }
234
235
  /**
236
   * __wakeup
237
   *
238
   * @return void
239
   */
240 2
  public function __wakeup()
241
  {
242 2
    $this->reconnect();
243 2
  }
244
245
  /**
246
   * Load the config from the constructor.
247
   *
248
   * @param string      $hostname
249
   * @param string      $username
250
   * @param string      $password
251
   * @param string      $database
252
   * @param int|string  $port             <p>default is (int)3306</p>
253
   * @param string      $charset          <p>default is 'utf8' or 'utf8mb4' (if supported)</p>
254
   * @param bool|string $exit_on_error    <p>Throw a 'Exception' when a query failed, otherwise it will return 'false'.
255
   *                                      Use a empty string "" or false to disable it.</p>
256
   * @param bool|string $echo_on_error    <p>Echo the error if "checkForDev()" returns true.
257
   *                                      Use a empty string "" or false to disable it.</p>
258
   * @param string      $logger_class_name
259
   * @param string      $logger_level
260
   * @param array       $extra_config     <p>
261
   *                                      'session_to_db' => false|true<br>
262
   *                                      'socket' => 'string (path)'<br>
263
   *                                      'ssl' => 'bool'<br>
264
   *                                      'clientkey' => 'string (path)'<br>
265
   *                                      'clientcert' => 'string (path)'<br>
266
   *                                      'cacert' => 'string (path)'<br>
267
   *                                      </p>
268
   *
269
   * @return bool
270
   */
271 11
  private function _loadConfig($hostname, $username, $password, $database, $port, $charset, $exit_on_error, $echo_on_error, $logger_class_name, $logger_level, $extra_config)
272
  {
273 11
    $this->hostname = (string)$hostname;
274 11
    $this->username = (string)$username;
275 11
    $this->password = (string)$password;
276 11
    $this->database = (string)$database;
277
278 11
    if ($charset) {
279 5
      $this->charset = (string)$charset;
280 5
    }
281
282 11
    if ($port) {
283 5
      $this->port = (int)$port;
284 5
    } else {
285
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
286
      /** @noinspection UsageOfSilenceOperatorInspection */
287 7
      $this->port = (int)@ini_get('mysqli.default_port');
288
    }
289
290
    // fallback
291 11
    if (!$this->port) {
292
      $this->port = 3306;
293
    }
294
295 11
    if (!$this->socket) {
296
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
297 11
      $this->socket = @ini_get('mysqli.default_socket');
298 11
    }
299
300 11
    if ($exit_on_error === true || $exit_on_error === false) {
301 11
      $this->_debug->setExitOnError($exit_on_error);
302 11
    }
303
304 11
    if ($echo_on_error === true || $echo_on_error === false) {
305 11
      $this->_debug->setEchoOnError($echo_on_error);
306 11
    }
307
308 11
    $this->_debug->setLoggerClassName($logger_class_name);
309 11
    $this->_debug->setLoggerLevel($logger_level);
310
311 11
    if (is_array($extra_config) === true) {
312
313 11
      if (isset($extra_config['session_to_db'])) {
314
        $this->session_to_db = (boolean)$extra_config['session_to_db'];
315
      }
316
317 11
      if (isset($extra_config['socket'])) {
318
        $this->socket = $extra_config['socket'];
319
      }
320
321 11
      if (isset($extra_config['ssl'])) {
322
        $this->_ssl = $extra_config['ssl'];
323
      }
324
325 11
      if (isset($extra_config['clientkey'])) {
326
        $this->_clientkey = $extra_config['clientkey'];
327
      }
328
329 11
      if (isset($extra_config['clientcert'])) {
330
        $this->_clientcert = $extra_config['clientcert'];
331
      }
332
333 11
      if (isset($extra_config['cacert'])) {
334
        $this->_cacert = $extra_config['cacert'];
335
      }
336
337 11
    } else {
338
      // only for backward compatibility
339
      $this->session_to_db = (boolean)$extra_config;
340
    }
341
342 11
    return $this->showConfigError();
343
  }
344
345
  /**
346
   * Parses arrays with value pairs and generates SQL to use in queries.
347
   *
348
   * @param array  $arrayPair
349
   * @param string $glue <p>This is the separator.</p>
350
   *
351
   * @return string
352
   *
353
   * @internal
354
   */
355 30
  public function _parseArrayPair(array $arrayPair, $glue = ',')
356
  {
357
    // init
358 30
    $sql = '';
359
360 30
    if (count($arrayPair) === 0) {
361
      return '';
362
    }
363
364 30
    $arrayPairCounter = 0;
365 30
    foreach ($arrayPair as $_key => $_value) {
366 30
      $_connector = '=';
367 30
      $_glueHelper = '';
368 30
      $_key_upper = strtoupper($_key);
369
370 30
      if (strpos($_key_upper, ' NOT') !== false) {
371 2
        $_connector = 'NOT';
372 2
      }
373
374 30
      if (strpos($_key_upper, ' IS') !== false) {
375 1
        $_connector = 'IS';
376 1
      }
377
378 30
      if (strpos($_key_upper, ' IS NOT') !== false) {
379 1
        $_connector = 'IS NOT';
380 1
      }
381
382 30
      if (strpos($_key_upper, ' IN') !== false) {
383 1
        $_connector = 'IN';
384 1
      }
385
386 30
      if (strpos($_key_upper, ' NOT IN') !== false) {
387 1
        $_connector = 'NOT IN';
388 1
      }
389
390 30
      if (strpos($_key_upper, ' BETWEEN') !== false) {
391 1
        $_connector = 'BETWEEN';
392 1
      }
393
394 30
      if (strpos($_key_upper, ' NOT BETWEEN') !== false) {
395 1
        $_connector = 'NOT BETWEEN';
396 1
      }
397
398 30
      if (strpos($_key_upper, ' LIKE') !== false) {
399 2
        $_connector = 'LIKE';
400 2
      }
401
402 30
      if (strpos($_key_upper, ' NOT LIKE') !== false) {
403 2
        $_connector = 'NOT LIKE';
404 2
      }
405
406 30 View Code Duplication
      if (strpos($_key_upper, ' >') !== false && strpos($_key_upper, ' =') === false) {
407 4
        $_connector = '>';
408 4
      }
409
410 30 View Code Duplication
      if (strpos($_key_upper, ' <') !== false && strpos($_key_upper, ' =') === false) {
411 1
        $_connector = '<';
412 1
      }
413
414 30
      if (strpos($_key_upper, ' >=') !== false) {
415 4
        $_connector = '>=';
416 4
      }
417
418 30
      if (strpos($_key_upper, ' <=') !== false) {
419 1
        $_connector = '<=';
420 1
      }
421
422 30
      if (strpos($_key_upper, ' <>') !== false) {
423 1
        $_connector = '<>';
424 1
      }
425
426 30
      if (strpos($_key_upper, ' OR') !== false) {
427 2
        $_glueHelper = 'OR';
428 2
      }
429
430 30
      if (strpos($_key_upper, ' AND') !== false) {
431 1
        $_glueHelper = 'AND';
432 1
      }
433
434 30
      if (is_array($_value) === true) {
435 2
        foreach ($_value as $oldKey => $oldValue) {
436 2
          $_value[$oldKey] = $this->secure($oldValue);
437 2
        }
438
439 2
        if ($_connector === 'NOT IN' || $_connector === 'IN') {
440 1
          $_value = '(' . implode(',', $_value) . ')';
441 2
        } elseif ($_connector === 'NOT BETWEEN' || $_connector === 'BETWEEN') {
442 1
          $_value = '(' . implode(' AND ', $_value) . ')';
443 1
        }
444
445 2
      } else {
446 30
        $_value = $this->secure($_value);
447
      }
448
449 30
      $quoteString = $this->quote_string(
450 30
          trim(
451 30
              str_ireplace(
452
                  array(
453 30
                      $_connector,
454 30
                      $_glueHelper,
455 30
                  ),
456 30
                  '',
457
                  $_key
458 30
              )
459 30
          )
460 30
      );
461
462 30
      if (!is_array($_value)) {
463 30
        $_value = array($_value);
464 30
      }
465
466 30
      if (!$_glueHelper) {
467 30
        $_glueHelper = $glue;
468 30
      }
469
470 30
      $tmpCounter = 0;
471 30
      foreach ($_value as $valueInner) {
472
473 30
        $_glueHelperInner = $_glueHelper;
474
475 30
        if ($arrayPairCounter === 0) {
476
477 30
          if ($tmpCounter === 0 && $_glueHelper === 'OR') {
478 1
            $_glueHelperInner = '1 = 1 AND ('; // first "OR"-query glue
479 30
          } elseif ($tmpCounter === 0) {
480 30
            $_glueHelperInner = ''; // first query glue e.g. for "INSERT"-query -> skip the first ","
481 30
          }
482
483 30
        } elseif ($tmpCounter === 0 && $_glueHelper === 'OR') {
484 1
          $_glueHelperInner = 'AND ('; // inner-loop "OR"-query glue
485 1
        }
486
487 30
        if (is_string($valueInner) && $valueInner === '') {
488 1
          $valueInner = "''";
489 1
        }
490
491 30
        $sql .= ' ' . $_glueHelperInner . ' ' . $quoteString . ' ' . $_connector . ' ' . $valueInner . " \n";
492 30
        $tmpCounter++;
493 30
      }
494
495 30
      if ($_glueHelper === 'OR') {
496 2
        $sql .= ' ) ';
497 2
      }
498
499 30
      $arrayPairCounter++;
500 30
    }
501
502 30
    return $sql;
503
  }
504
505
  /**
506
   * _parseQueryParams
507
   *
508
   * @param string $sql
509
   * @param array  $params
510
   *
511
   * @return array <p>with the keys -> 'sql', 'params'</p>
512
   */
513 13
  private function _parseQueryParams($sql, array $params = array())
514
  {
515
    // is there anything to parse?
516 View Code Duplication
    if (
517 13
        strpos($sql, '?') === false
518 13
        ||
519 3
        count($params) === 0
520 13
    ) {
521 11
      return array('sql' => $sql, 'params' => $params);
522
    }
523
524 3
    $parseKey = md5(uniqid((string)mt_rand(), true));
525 3
    $sql = str_replace('?', $parseKey, $sql);
526
527 3
    $k = 0;
528 3
    while (strpos($sql, $parseKey) !== false) {
529 3
      $sql = UTF8::str_replace_first(
530 3
          $parseKey,
531 3
          isset($params[$k]) ? $this->secure($params[$k]) : '',
532
          $sql
0 ignored issues
show
Bug introduced by
It seems like $sql defined by \voku\helper\UTF8::str_r...params[$k]) : '', $sql) on line 529 can also be of type array<integer,string>; 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...
533 3
      );
534
535 3
      if (isset($params[$k])) {
536 3
        unset($params[$k]);
537 3
      }
538
539 3
      $k++;
540 3
    }
541
542 3
    return array('sql' => $sql, 'params' => $params);
543
  }
544
545
  /**
546
   * Gets the number of affected rows in a previous MySQL operation.
547
   *
548
   * @return int
549
   */
550 12
  public function affected_rows()
551
  {
552 12
    return \mysqli_affected_rows($this->link);
553
  }
554
555
  /**
556
   * Begins a transaction, by turning off auto commit.
557
   *
558
   * @return bool <p>This will return true or false indicating success of transaction</p>
559
   */
560 6 View Code Duplication
  public function beginTransaction()
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...
561
  {
562 6
    if ($this->_in_transaction === true) {
563 2
      $this->_debug->displayError('Error: mysql server already in transaction!', false);
564
565 2
      return false;
566
    }
567
568 6
    $this->clearErrors(); // needed for "$this->endTransaction()"
569 6
    $this->_in_transaction = true;
570 6
    $return = \mysqli_autocommit($this->link, false);
571 6
    if ($return === false) {
572
      $this->_in_transaction = false;
573
    }
574
575 6
    return $return;
576
  }
577
578
  /**
579
   * Clear the errors in "_debug->_errors".
580
   *
581
   * @return bool
582
   */
583 6
  public function clearErrors()
584
  {
585 6
    return $this->_debug->clearErrors();
586
  }
587
588
  /**
589
   * Closes a previously opened database connection.
590
   */
591 2
  public function close()
592
  {
593 2
    $this->connected = false;
594
595
    if (
596 2
        $this->link
597 2
        &&
598 2
        $this->link instanceof \mysqli
599 2
    ) {
600 2
      $result = \mysqli_close($this->link);
601 2
      $this->link = null;
602
603 2
      return $result;
604
    }
605
606 1
    return false;
607
  }
608
609
  /**
610
   * Commits the current transaction and end the transaction.
611
   *
612
   * @return bool <p>Boolean true on success, false otherwise.</p>
613
   */
614 2 View Code Duplication
  public function commit()
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...
615
  {
616 2
    if ($this->_in_transaction === false) {
617
      $this->_debug->displayError('Error: mysql server is not in transaction!', false);
618
619
      return false;
620
    }
621
622 2
    $return = mysqli_commit($this->link);
623 2
    \mysqli_autocommit($this->link, true);
624 2
    $this->_in_transaction = false;
625
626 2
    return $return;
627
  }
628
629
  /**
630
   * Open a new connection to the MySQL server.
631
   *
632
   * @return bool
633
   *
634
   * @throws DBConnectException
635
   */
636 10
  public function connect()
637
  {
638 10
    if ($this->isReady()) {
639 1
      return true;
640
    }
641
642 10
    $flags = null;
643
644 10
    \mysqli_report(MYSQLI_REPORT_STRICT);
645
    try {
646 10
      $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>|null 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...
647
648 10
      if (Helper::isMysqlndIsUsed() === true) {
649 10
        \mysqli_options($this->link, MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
650 10
      }
651
652 10
      if ($this->_ssl === true) {
653
654
        if (empty($this->clientcert)) {
0 ignored issues
show
Bug introduced by
The property clientcert does not seem to exist. Did you mean _clientcert?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
655
          throw new DBConnectException('Error connecting to mysql server: clientcert not defined');
656
        }
657
658
        if (empty($this->clientkey)) {
0 ignored issues
show
Bug introduced by
The property clientkey does not seem to exist. Did you mean _clientkey?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
659
          throw new DBConnectException('Error connecting to mysql server: clientkey not defined');
660
        }
661
662
        if (empty($this->cacert)) {
0 ignored issues
show
Bug introduced by
The property cacert does not seem to exist. Did you mean _cacert?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
663
          throw new DBConnectException('Error connecting to mysql server: cacert not defined');
664
        }
665
666
        \mysqli_options($this->link, MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, true);
667
668
        /** @noinspection PhpParamsInspection */
669
        \mysqli_ssl_set(
670
            $this->link,
671
            $this->_clientkey,
672
            $this->_clientcert,
673
            $this->_cacert,
674
            null,
675
            null
676
        );
677
678
        $flags = MYSQLI_CLIENT_SSL;
679
      }
680
681
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
682 10
      $this->connected = @\mysqli_real_connect(
683 10
          $this->link,
684 10
          $this->hostname,
685 10
          $this->username,
686 10
          $this->password,
687 10
          $this->database,
688 10
          $this->port,
689 10
          $this->socket,
690
          $flags
691 10
      );
692
693 10
    } catch (\Exception $e) {
694 3
      $error = 'Error connecting to mysql server: ' . $e->getMessage();
695 3
      $this->_debug->displayError($error, true);
696
      throw new DBConnectException($error, 100, $e);
697
    }
698 7
    \mysqli_report(MYSQLI_REPORT_OFF);
699
700 7
    $errno = mysqli_connect_errno();
701 7
    if (!$this->connected || $errno) {
702
      $error = 'Error connecting to mysql server: ' . \mysqli_connect_error() . ' (' . $errno . ')';
703
      $this->_debug->displayError($error, true);
704
      throw new DBConnectException($error, 101);
705
    }
706
707 7
    $this->set_charset($this->charset);
708
709 7
    return $this->isReady();
710
  }
711
712
  /**
713
   * Execute a "delete"-query.
714
   *
715
   * @param string       $table
716
   * @param string|array $where
717
   * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
718
   *
719
   * @return false|int <p>false on error</p>
720
   *
721
   *    * @throws QueryException
722
   */
723 2 View Code Duplication
  public function delete($table, $where, $databaseName = null)
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...
724
  {
725
    // init
726 2
    $table = trim($table);
727
728 2
    if ($table === '') {
729 1
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
730
731 1
      return false;
732
    }
733
734 2
    if (is_string($where)) {
735 1
      $WHERE = $this->escape($where, false);
736 2
    } elseif (is_array($where)) {
737 2
      $WHERE = $this->_parseArrayPair($where, 'AND');
738 2
    } else {
739 1
      $WHERE = '';
740
    }
741
742 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...
743
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
744
    }
745
746 2
    $sql = 'DELETE FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE);";
747
748 2
    return $this->query($sql);
749
  }
750
751
  /**
752
   * Ends a transaction and commits if no errors, then ends autocommit.
753
   *
754
   * @return bool <p>This will return true or false indicating success of transactions.</p>
755
   */
756 4 View Code Duplication
  public function endTransaction()
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 4
    if ($this->_in_transaction === false) {
759
      $this->_debug->displayError('Error: mysql server is not in transaction!', false);
760
761
      return false;
762
    }
763
764 4
    if (!$this->errors()) {
765 1
      $return = \mysqli_commit($this->link);
766 1
    } else {
767 3
      $this->rollback();
768 3
      $return = false;
769
    }
770
771 4
    \mysqli_autocommit($this->link, true);
772 4
    $this->_in_transaction = false;
773
774 4
    return $return;
775
  }
776
777
  /**
778
   * Get all errors from "$this->_errors".
779
   *
780
   * @return array|false <p>false === on errors</p>
781
   */
782 4
  public function errors()
783
  {
784 4
    $errors = $this->_debug->getErrors();
785
786 4
    return count($errors) > 0 ? $errors : false;
787
  }
788
789
  /**
790
   * Returns the SQL by replacing :placeholders with SQL-escaped values.
791
   *
792
   * @param mixed $sql    <p>The SQL string.</p>
793
   * @param array $params <p>An array of key-value bindings.</p>
794
   *
795
   * @return array <p>with the keys -> 'sql', 'params'</p>
796
   */
797 14
  public function _parseQueryParamsByName($sql, array $params = array())
798
  {
799
    // is there anything to parse?
800 View Code Duplication
    if (
801 14
        strpos($sql, ':') === false
802 14
        ||
803 12
        count($params) === 0
804 14
    ) {
805 3
      return array('sql' => $sql, 'params' => $params);
806
    }
807
808 12
    $parseKey = md5(uniqid((string)mt_rand(), true));
809
810 12
    foreach ($params as $name => $value) {
811 12
      $nameTmp = $name;
812 12
      if (strpos($name, ':') === 0) {
813 10
        $nameTmp = substr($name, 1);
814 10
      }
815
816 12
      $parseKeyInner = $nameTmp . '-' . $parseKey;
817 12
      $sql = str_replace(':' . $nameTmp, $parseKeyInner, $sql);
818 12
    }
819
820 12
    foreach ($params as $name => $value) {
821 12
      $nameTmp = $name;
822 12
      if (strpos($name, ':') === 0) {
823 10
        $nameTmp = substr($name, 1);
824 10
      }
825
826 12
      $parseKeyInner = $nameTmp . '-' . $parseKey;
827 12
      $sqlBefore = $sql;
828 12
      $secureParamValue = $this->secure($params[$name]);
829
830 12
      while (strpos($sql, $parseKeyInner) !== false) {
831 12
        $sql = UTF8::str_replace_first(
832 12
            $parseKeyInner,
833 12
            $secureParamValue,
834
            $sql
835 12
        );
836 12
      }
837
838 12
      if ($sqlBefore !== $sql) {
839 12
        unset($params[$name]);
840 12
      }
841 12
    }
842
843 12
    return array('sql' => $sql, 'params' => $params);
844
  }
845
846
  /**
847
   * Escape: Use "mysqli_real_escape_string" and clean non UTF-8 chars + some extra optional stuff.
848
   *
849
   * @param mixed     $var           boolean: convert into "integer"<br />
850
   *                                 int: int (don't change it)<br />
851
   *                                 float: float (don't change it)<br />
852
   *                                 null: null (don't change it)<br />
853
   *                                 array: run escape() for every key => value<br />
854
   *                                 string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
855
   * @param bool      $stripe_non_utf8
856
   * @param bool      $html_entity_decode
857
   * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
858
   *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
859
   *                                 <strong>null</strong> => Convert the array into null, every time.
860
   *
861
   * @return mixed
862
   */
863 55
  public function escape($var = '', $stripe_non_utf8 = true, $html_entity_decode = false, $convert_array = false)
864
  {
865 55
    if ($var === '') {
866 2
      return '';
867
    }
868
869 55
    if ($var === "''") {
870
      return "''";
871
    }
872
873 55
    if ($var === null) {
874
      if (
875 2
          $this->_convert_null_to_empty_string === true
876 2
      ) {
877
        return "''";
878
      }
879
880 2
      return 'NULL';
881
    }
882
883
    // save the current value as int (for later usage)
884 55
    if (!is_object($var)) {
885 55
      $varInt = (int)$var;
886 55
    }
887
888
    /** @noinspection TypeUnsafeComparisonInspection */
889
    if (
890 55
        is_int($var)
891
        ||
892 52
        is_bool($var)
893 52
        ||
894
        (
895 52
            isset($varInt, $var[0])
896 52
            &&
897 52
            $var[0] != '0'
898 52
            &&
899
            "$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...
900 52
        )
901 55
    ) {
902
903
      // "int" || int || bool
904
905 32
      return (int)$var;
906
    }
907
908 52
    if (is_float($var)) {
909
910
      // float
911
912 5
      return $var;
913
    }
914
915 52
    if (is_array($var)) {
916
917
      // array
918
919 4
      if ($convert_array === null) {
920 3
        return null;
921
      }
922
923 2
      $varCleaned = array();
924 2
      foreach ((array)$var as $key => $value) {
925
926 2
        $key = $this->escape($key, $stripe_non_utf8, $html_entity_decode);
927 2
        $value = $this->escape($value, $stripe_non_utf8, $html_entity_decode);
928
929
        /** @noinspection OffsetOperationsInspection */
930 2
        $varCleaned[$key] = $value;
931 2
      }
932
933 2
      if ($convert_array === true) {
934 1
        $varCleaned = implode(',', $varCleaned);
935
936 1
        return $varCleaned;
937
      }
938
939 2
      return (array)$varCleaned;
940
    }
941
942
    if (
943 52
        is_string($var)
944
        ||
945
        (
946 3
            is_object($var)
947 3
            &&
948 3
            method_exists($var, '__toString')
949 3
        )
950 52
    ) {
951
952
      // "string"
953
954 52
      $var = (string)$var;
955
956 52
      if ($stripe_non_utf8 === true) {
957 11
        $var = UTF8::cleanup($var);
958 11
      }
959
960 52
      if ($html_entity_decode === true) {
961
        // use no-html-entity for db
962 1
        $var = UTF8::html_entity_decode($var);
963 1
      }
964
965 52
      $var = get_magic_quotes_gpc() ? stripslashes($var) : $var;
966
967 52
      $var = \mysqli_real_escape_string($this->getLink(), $var);
968
969 52
      return (string)$var;
970
971
    }
972
973 3
    if ($var instanceof \DateTime) {
974
975
      // "DateTime"-object
976
977
      try {
978 3
        return $this->escape($var->format('Y-m-d H:i:s'), false);
979
      } catch (\Exception $e) {
980
        return null;
981
      }
982
983
    } else {
984 2
      return false;
985
    }
986
  }
987
988
  /**
989
   * Execute select/insert/update/delete sql-queries.
990
   *
991
   * @param string $query    <p>sql-query</p>
992
   * @param bool   $useCache <p>use cache?</p>
993
   * @param int    $cacheTTL <p>cache-ttl in seconds</p>
994
   * @param DB     $db       optional <p>the database connection</p>
995
   *
996
   * @return mixed "array" by "<b>SELECT</b>"-queries<br />
997
   *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
998
   *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
999
   *               "true" by e.g. "DROP"-queries<br />
1000
   *               "false" on error
1001
   *
1002
   * @throws QueryException
1003
   */
1004 3
  public static function execSQL($query, $useCache = false, $cacheTTL = 3600, DB $db = null)
1005
  {
1006
    // init
1007 3
    $cacheKey = null;
1008 3
    if (!$db) {
1009 3
      $db = self::getInstance();
1010 3
    }
1011
1012 3 View Code Duplication
    if ($useCache === true) {
1013 1
      $cache = new Cache(null, null, false, $useCache);
1014 1
      $cacheKey = 'sql-' . md5($query);
1015
1016
      if (
1017 1
          $cache->getCacheIsReady() === true
1018 1
          &&
1019 1
          $cache->existsItem($cacheKey)
1020 1
      ) {
1021 1
        return $cache->getItem($cacheKey);
1022
      }
1023
1024 1
    } else {
1025 3
      $cache = false;
1026
    }
1027
1028 3
    $result = $db->query($query);
1029
1030 3
    if ($result instanceof Result) {
1031
1032 1
      $return = $result->fetchAllArray();
1033
1034
      // save into the cache
1035 View Code Duplication
      if (
1036
          $cacheKey !== null
1037 1
          &&
1038
          $useCache === true
1039 1
          &&
1040
          $cache instanceof Cache
1041 1
          &&
1042 1
          $cache->getCacheIsReady() === true
1043 1
      ) {
1044 1
        $cache->setItem($cacheKey, $return, $cacheTTL);
1045 1
      }
1046
1047 1
    } else {
1048 2
      $return = $result;
1049
    }
1050
1051 3
    return $return;
1052
  }
1053
1054
  /**
1055
   * Get all table-names via "SHOW TABLES".
1056
   *
1057
   * @return array
1058
   */
1059 1
  public function getAllTables()
1060
  {
1061 1
    $query = 'SHOW TABLES';
1062 1
    $result = $this->query($query);
1063
1064 1
    return $result->fetchAllArray();
1065
  }
1066
1067
  /**
1068
   * @return Debug
1069
   */
1070 9
  public function getDebugger()
1071
  {
1072 9
    return $this->_debug;
1073
  }
1074
1075
  /**
1076
   * Get errors from "$this->_errors".
1077
   *
1078
   * @return array
1079
   */
1080 1
  public function getErrors()
1081
  {
1082 1
    return $this->_debug->getErrors();
1083
  }
1084
1085
  /**
1086
   * getInstance()
1087
   *
1088
   * @param string      $hostname
1089
   * @param string      $username
1090
   * @param string      $password
1091
   * @param string      $database
1092
   * @param int|string  $port            <p>default is (int)3306</p>
1093
   * @param string      $charset         <p>default is 'utf8' or 'utf8mb4' (if supported)</p>
1094
   * @param bool|string $exit_on_error   <p>Throw a 'Exception' when a query failed, otherwise it will return 'false'.
1095
   *                                     Use a empty string "" or false to disable it.</p>
1096
   * @param bool|string $echo_on_error   <p>Echo the error if "checkForDev()" returns true.
1097
   *                                     Use a empty string "" or false to disable it.</p>
1098
   * @param string      $logger_class_name
1099
   * @param string      $logger_level    <p>'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'</p>
1100
   * @param array       $extra_config    <p>
1101
   *                                     'session_to_db' => false|true<br>
1102
   *                                     'socket' => 'string (path)'<br>
1103
   *                                     'ssl' => 'bool'<br>
1104
   *                                     'clientkey' => 'string (path)'<br>
1105
   *                                     'clientcert' => 'string (path)'<br>
1106
   *                                     'cacert' => 'string (path)'<br>
1107
   *                                     </p>
1108
   *
1109
   * @return \voku\db\DB
1110
   */
1111 97
  public static function getInstance($hostname = '', $username = '', $password = '', $database = '', $port = '', $charset = '', $exit_on_error = true, $echo_on_error = true, $logger_class_name = '', $logger_level = '', $extra_config = array())
1112
  {
1113
    /**
1114
     * @var $instance DB[]
1115
     */
1116 97
    static $instance = array();
1117
1118
    /**
1119
     * @var $firstInstance DB
1120
     */
1121 97
    static $firstInstance = null;
1122
1123
    if (
1124 97
        $hostname . $username . $password . $database . $port . $charset == ''
1125 97
        &&
1126 14
        null !== $firstInstance
1127 97
    ) {
1128 14
      return $firstInstance;
1129
    }
1130
1131 97
    $extra_config_string = '';
1132 97
    if (is_array($extra_config) === true) {
1133 97
      foreach ($extra_config as $extra_config_key => $extra_config_value) {
1134
        $extra_config_string .= $extra_config_key . (string)$extra_config_value;
1135 97
      }
1136 97
    } else {
1137
      // only for backward compatibility
1138
      $extra_config_string = (int)$extra_config;
1139
    }
1140
1141 97
    $connection = md5(
1142 97
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . $extra_config_string
1143 97
    );
1144
1145 97
    if (!isset($instance[$connection])) {
1146 11
      $instance[$connection] = new self(
1147 11
          $hostname,
1148 11
          $username,
1149 11
          $password,
1150 11
          $database,
1151 11
          $port,
1152 11
          $charset,
1153 11
          $exit_on_error,
1154 11
          $echo_on_error,
1155 11
          $logger_class_name,
1156 11
          $logger_level,
1157
          $extra_config
1158 11
      );
1159
1160 5
      if (null === $firstInstance) {
1161 1
        $firstInstance = $instance[$connection];
1162 1
      }
1163 5
    }
1164
1165 97
    return $instance[$connection];
1166
  }
1167
1168
  /**
1169
   * Get the mysqli-link (link identifier returned by mysqli-connect).
1170
   *
1171
   * @return \mysqli
1172
   */
1173 56
  public function getLink()
1174
  {
1175 56
    return $this->link;
1176
  }
1177
1178
  /**
1179
   * Get the current charset.
1180
   *
1181
   * @return string
1182
   */
1183 1
  public function get_charset()
1184
  {
1185 1
    return $this->charset;
1186
  }
1187
1188
  /**
1189
   * Check if we are in a transaction.
1190
   *
1191
   * @return bool
1192
   */
1193
  public function inTransaction()
1194
  {
1195
    return $this->_in_transaction;
1196
  }
1197
1198
  /**
1199
   * Execute a "insert"-query.
1200
   *
1201
   * @param string      $table
1202
   * @param array       $data
1203
   * @param string|null $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1204
   *
1205
   * @return false|int <p>false on error</p>
1206
   *
1207
   * @throws QueryException
1208
   */
1209 28
  public function insert($table, array $data = array(), $databaseName = null)
1210
  {
1211
    // init
1212 28
    $table = trim($table);
1213
1214 28
    if ($table === '') {
1215 2
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1216
1217 2
      return false;
1218
    }
1219
1220 27
    if (count($data) === 0) {
1221 3
      $this->_debug->displayError('Invalid data for INSERT, data is empty.', false);
1222
1223 3
      return false;
1224
    }
1225
1226 25
    $SET = $this->_parseArrayPair($data);
1227
1228 25
    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...
1229
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1230
    }
1231
1232 25
    $sql = 'INSERT INTO ' . $databaseName . $this->quote_string($table) . " SET $SET;";
1233
1234 25
    return $this->query($sql);
1235
  }
1236
1237
  /**
1238
   * Returns the auto generated id used in the last query.
1239
   *
1240
   * @return int|string
1241
   */
1242 58
  public function insert_id()
1243
  {
1244 58
    return \mysqli_insert_id($this->link);
1245
  }
1246
1247
  /**
1248
   * Check if db-connection is ready.
1249
   *
1250
   * @return boolean
1251
   */
1252 106
  public function isReady()
1253
  {
1254 106
    return $this->connected ? true : false;
1255
  }
1256
1257
  /**
1258
   * Get the last sql-error.
1259
   *
1260
   * @return string|false <p>false === there was no error</p>
1261
   */
1262 1
  public function lastError()
1263
  {
1264 1
    $errors = $this->_debug->getErrors();
1265
1266 1
    return count($errors) > 0 ? end($errors) : false;
1267
  }
1268
1269
  /**
1270
   * Execute a sql-multi-query.
1271
   *
1272
   * @param string $sql
1273
   *
1274
   * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
1275
   *                        "boolean" by only "<b>INSERT</b>"-queries<br />
1276
   *                        "boolean" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1277
   *                        "boolean" by only by e.g. "DROP"-queries<br />
1278
   *
1279
   * @throws QueryException
1280
   */
1281 1
  public function multi_query($sql)
1282
  {
1283 1
    if (!$this->isReady()) {
1284
      return false;
1285
    }
1286
1287 1 View Code Duplication
    if (!$sql || $sql === '') {
1288 1
      $this->_debug->displayError('Can not execute an empty query.', false);
1289
1290 1
      return false;
1291
    }
1292
1293 1
    $query_start_time = microtime(true);
1294 1
    $resultTmp = \mysqli_multi_query($this->link, $sql);
1295 1
    $query_duration = microtime(true) - $query_start_time;
1296
1297 1
    $this->_debug->logQuery($sql, $query_duration, 0);
1298
1299 1
    $returnTheResult = false;
1300 1
    $result = array();
1301
1302 1
    if ($resultTmp) {
1303
      do {
1304
1305 1
        $resultTmpInner = \mysqli_store_result($this->link);
1306
1307 1
        if ($resultTmpInner instanceof \mysqli_result) {
1308
1309 1
          $returnTheResult = true;
1310 1
          $result[] = new Result($sql, $resultTmpInner);
1311
1312 1
        } else {
1313
1314
          // is the query successful
1315 1
          if ($resultTmpInner === true || !\mysqli_errno($this->link)) {
1316 1
            $result[] = true;
1317 1
          } else {
1318
            $result[] = false;
1319
          }
1320
1321
        }
1322
1323 1
      } while (\mysqli_more_results($this->link) === true ? \mysqli_next_result($this->link) : false);
1324
1325 1
    } else {
1326
1327
      // log the error query
1328 1
      $this->_debug->logQuery($sql, $query_duration, 0, true);
1329
1330 1
      return $this->queryErrorHandling(\mysqli_error($this->link), \mysqli_errno($this->link), $sql, false, true);
1331
    }
1332
1333
    // return the result only if there was a "SELECT"-query
1334 1
    if ($returnTheResult === true) {
1335 1
      return $result;
1336
    }
1337
1338
    if (
1339 1
        count($result) > 0
1340 1
        &&
1341 1
        in_array(false, $result, true) === false
1342 1
    ) {
1343 1
      return true;
1344
    }
1345
1346
    return false;
1347
  }
1348
1349
  /**
1350
   * Pings a server connection, or tries to reconnect
1351
   * if the connection has gone down.
1352
   *
1353
   * @return boolean
1354
   */
1355 3
  public function ping()
1356
  {
1357
    if (
1358 3
        $this->link
1359 3
        &&
1360 2
        $this->link instanceof \mysqli
1361 3
    ) {
1362
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
1363
      /** @noinspection UsageOfSilenceOperatorInspection */
1364 2
      return (bool)@\mysqli_ping($this->link);
1365
    }
1366
1367 1
    return false;
1368
  }
1369
1370
  /**
1371
   * Get a new "Prepare"-Object for your sql-query.
1372
   *
1373
   * @param string $query
1374
   *
1375
   * @return Prepare
1376
   */
1377 2
  public function prepare($query)
1378
  {
1379 2
    return new Prepare($this, $query);
1380
  }
1381
1382
  /**
1383
   * Execute a sql-query and return the result-array for select-statements.
1384
   *
1385
   * @param string $query
1386
   *
1387
   * @return mixed
1388
   * @deprecated
1389
   * @throws \Exception
1390
   */
1391
  public static function qry($query)
1392
  {
1393
    $db = self::getInstance();
1394
1395
    $args = func_get_args();
1396
    /** @noinspection SuspiciousAssignmentsInspection */
1397
    $query = array_shift($args);
1398
    $query = str_replace('?', '%s', $query);
1399
    $args = array_map(
1400
        array(
1401
            $db,
1402
            'escape',
1403
        ),
1404
        $args
1405
    );
1406
    array_unshift($args, $query);
1407
    $query = call_user_func_array('sprintf', $args);
1408
    $result = $db->query($query);
1409
1410
    if ($result instanceof Result) {
1411
      return $result->fetchAllArray();
1412
    }
1413
1414
    return $result;
1415
  }
1416
1417
  /**
1418
   * Execute a sql-query.
1419
   *
1420
   * @param string        $sql            <p>The sql query-string.</p>
1421
   *
1422
   * @param array|boolean $params         <p>
1423
   *                                      "array" of sql-query-parameters<br/>
1424
   *                                      "false" if you don't need any parameter (default)<br/>
1425
   *                                      </p>
1426
   *
1427
   * @return bool|int|Result              <p>
1428
   *                                      "Result" by "<b>SELECT</b>"-queries<br />
1429
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
1430
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1431
   *                                      "true" by e.g. "DROP"-queries<br />
1432
   *                                      "false" on error
1433
   *                                      </p>
1434
   *
1435
   * @throws QueryException
1436
   */
1437 96
  public function query($sql = '', $params = false)
1438
  {
1439 96
    if (!$this->isReady()) {
1440
      return false;
1441
    }
1442
1443 96 View Code Duplication
    if (!$sql || $sql === '') {
1444 4
      $this->_debug->displayError('Can not execute an empty query.', false);
1445
1446 4
      return false;
1447
    }
1448
1449
    if (
1450
        $params !== false
1451 94
        &&
1452 29
        is_array($params)
1453 94
        &&
1454 29
        count($params) > 0
1455 94
    ) {
1456 13
      $parseQueryParams = $this->_parseQueryParams($sql, $params);
1457 13
      $parseQueryParamsByName = $this->_parseQueryParamsByName($parseQueryParams['sql'], $parseQueryParams['params']);
1458 13
      $sql = $parseQueryParamsByName['sql'];
1459 13
    }
1460
1461
    // DEBUG
1462
    // var_dump($params);
1463
    // echo $sql . "\n";
1464
1465 94
    $query_start_time = microtime(true);
1466 94
    $query_result = \mysqli_real_query($this->link, $sql);
1467 94
    $query_duration = microtime(true) - $query_start_time;
1468
1469 94
    $this->query_count++;
1470
1471 94
    $mysqli_field_count = \mysqli_field_count($this->link);
1472 94
    if ($mysqli_field_count) {
1473 62
      $result = \mysqli_store_result($this->link);
1474 62
    } else {
1475 64
      $result = $query_result;
1476
    }
1477
1478 94
    if ($result instanceof \mysqli_result) {
1479
1480
      // log the select query
1481 61
      $this->_debug->logQuery($sql, $query_duration, $mysqli_field_count);
1482
1483
      // return query result object
1484 61
      return new Result($sql, $result);
1485
    }
1486
1487 66
    if ($query_result === true) {
1488
1489
      // "INSERT" || "REPLACE"
1490 63 View Code Duplication
      if (preg_match('/^\s*?(?:INSERT|REPLACE)\s+/i', $sql)) {
1491 58
        $insert_id = (int)$this->insert_id();
1492 58
        $this->_debug->logQuery($sql, $query_duration, $insert_id);
1493
1494 58
        return $insert_id;
1495
      }
1496
1497
      // "UPDATE" || "DELETE"
1498 38 View Code Duplication
      if (preg_match('/^\s*?(?:UPDATE|DELETE)\s+/i', $sql)) {
1499 12
        $affected_rows = (int)$this->affected_rows();
1500 12
        $this->_debug->logQuery($sql, $query_duration, $affected_rows);
1501
1502 12
        return $affected_rows;
1503
      }
1504
1505
      // log the ? query
1506 27
      $this->_debug->logQuery($sql, $query_duration, 0);
1507
1508 27
      return true;
1509
    }
1510
1511
    // log the error query
1512 11
    $this->_debug->logQuery($sql, $query_duration, 0, true);
1513
1514 11
    return $this->queryErrorHandling(\mysqli_error($this->link), \mysqli_errno($this->link), $sql, $params);
1515
  }
1516
1517
  /**
1518
   * Error-handling for the sql-query.
1519
   *
1520
   * @param string     $errorMessage
1521
   * @param int        $errorNumber
1522
   * @param string     $sql
1523
   * @param array|bool $sqlParams <p>false if there wasn't any parameter</p>
1524
   * @param bool       $sqlMultiQuery
1525
   *
1526
   * @throws QueryException
1527
   * @throws DBGoneAwayException
1528
   *
1529
   * @return bool
1530
   */
1531 13
  private function queryErrorHandling($errorMessage, $errorNumber, $sql, $sqlParams = false, $sqlMultiQuery = false)
1532
  {
1533 13
    $errorNumber = (int)$errorNumber;
1534
1535
    if (
1536
        $errorMessage === 'DB server has gone away'
1537 13
        ||
1538
        $errorMessage === 'MySQL server has gone away'
1539 12
        ||
1540
        $errorNumber === 2006
1541 13
    ) {
1542 1
      static $RECONNECT_COUNTER;
1543
1544
      // exit if we have more then 3 "DB server has gone away"-errors
1545 1
      if ($RECONNECT_COUNTER > 3) {
1546
        $this->_debug->mailToAdmin('DB-Fatal-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql, 5);
1547
        throw new DBGoneAwayException($errorMessage);
1548
      }
1549
1550 1
      $this->_debug->mailToAdmin('DB-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql);
1551
1552
      // reconnect
1553 1
      $RECONNECT_COUNTER++;
1554 1
      $this->reconnect(true);
1555
1556
      // re-run the current (non multi) query
1557 1
      if ($sqlMultiQuery === false) {
1558 1
        return $this->query($sql, $sqlParams);
1559
      }
1560
1561
      return false;
1562
    }
1563
1564 12
    $this->_debug->mailToAdmin('SQL-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql);
1565
1566 12
    $force_exception_after_error = null; // auto
1567 12
    if ($this->_in_transaction === true) {
1568 4
      $force_exception_after_error = false;
1569 4
    }
1570
    // this query returned an error, we must display it (only for dev) !!!
1571
1572 12
    $this->_debug->displayError($errorMessage . '(' . $errorNumber . ') ' . ' | ' . $sql, $force_exception_after_error);
1573
1574 12
    return false;
1575
  }
1576
1577
  /**
1578
   * Quote && Escape e.g. a table name string.
1579
   *
1580
   * @param string $str
1581
   *
1582
   * @return string
1583
   */
1584 36
  public function quote_string($str)
1585
  {
1586 36
    $str = str_replace(
1587 36
        '`',
1588 36
        '``',
1589 36
        trim(
1590 36
            $this->escape($str, false),
1591
            '`'
1592 36
        )
1593 36
    );
1594
1595 36
    return '`' . $str . '`';
1596
  }
1597
1598
  /**
1599
   * Reconnect to the MySQL-Server.
1600
   *
1601
   * @param bool $checkViaPing
1602
   *
1603
   * @return bool
1604
   */
1605 3
  public function reconnect($checkViaPing = false)
1606
  {
1607 3
    $ping = false;
1608
1609 3
    if ($checkViaPing === true) {
1610 2
      $ping = $this->ping();
1611 2
    }
1612
1613 3
    if ($ping !== true) {
1614 3
      $this->connected = false;
1615 3
      $this->connect();
1616 3
    }
1617
1618 3
    return $this->isReady();
1619
  }
1620
1621
  /**
1622
   * Execute a "replace"-query.
1623
   *
1624
   * @param string      $table
1625
   * @param array       $data
1626
   * @param null|string $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1627
   *
1628
   * @return false|int <p>false on error</p>
1629
   *
1630
   * @throws QueryException
1631
   */
1632 1
  public function replace($table, array $data = array(), $databaseName = null)
1633
  {
1634
    // init
1635 1
    $table = trim($table);
1636
1637 1
    if ($table === '') {
1638 1
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1639
1640 1
      return false;
1641
    }
1642
1643 1
    if (count($data) === 0) {
1644 1
      $this->_debug->displayError('Invalid data for REPLACE, data is empty.', false);
1645
1646 1
      return false;
1647
    }
1648
1649
    // extracting column names
1650 1
    $columns = array_keys($data);
1651 1
    foreach ($columns as $k => $_key) {
1652
      /** @noinspection AlterInForeachInspection */
1653 1
      $columns[$k] = $this->quote_string($_key);
1654 1
    }
1655
1656 1
    $columns = implode(',', $columns);
1657
1658
    // extracting values
1659 1
    foreach ($data as $k => $_value) {
1660
      /** @noinspection AlterInForeachInspection */
1661 1
      $data[$k] = $this->secure($_value);
1662 1
    }
1663 1
    $values = implode(',', $data);
1664
1665 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...
1666
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1667
    }
1668
1669 1
    $sql = 'REPLACE INTO ' . $databaseName . $this->quote_string($table) . " ($columns) VALUES ($values);";
1670
1671 1
    return $this->query($sql);
1672
  }
1673
1674
  /**
1675
   * Rollback in a transaction and end the transaction.
1676
   *
1677
   * @return bool <p>Boolean true on success, false otherwise.</p>
1678
   */
1679 4 View Code Duplication
  public function rollback()
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...
1680
  {
1681 4
    if ($this->_in_transaction === false) {
1682
      $this->_debug->displayError('Error: mysql server is not in transaction!', false);
1683
1684
      return false;
1685
    }
1686
1687 4
    $return = \mysqli_rollback($this->link);
1688 4
    \mysqli_autocommit($this->link, true);
1689 4
    $this->_in_transaction = false;
1690
1691 4
    return $return;
1692
  }
1693
1694
  /**
1695
   * Try to secure a variable, so can you use it in sql-queries.
1696
   *
1697
   * <p>
1698
   * <strong>int:</strong> (also strings that contains only an int-value)<br />
1699
   * 1. parse into "int"
1700
   * </p><br />
1701
   *
1702
   * <p>
1703
   * <strong>float:</strong><br />
1704
   * 1. return "float"
1705
   * </p><br />
1706
   *
1707
   * <p>
1708
   * <strong>string:</strong><br />
1709
   * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'<br />
1710
   * 2. trim whitespace<br />
1711
   * 3. trim '<br />
1712
   * 4. escape the string (and remove non utf-8 chars)<br />
1713
   * 5. trim ' again (because we maybe removed some chars)<br />
1714
   * 6. add ' around the new string<br />
1715
   * </p><br />
1716
   *
1717
   * <p>
1718
   * <strong>array:</strong><br />
1719
   * 1. return null
1720
   * </p><br />
1721
   *
1722
   * <p>
1723
   * <strong>object:</strong><br />
1724
   * 1. return false
1725
   * </p><br />
1726
   *
1727
   * <p>
1728
   * <strong>null:</strong><br />
1729
   * 1. return null
1730
   * </p>
1731
   *
1732
   * @param mixed $var
1733
   *
1734
   * @return mixed
1735
   */
1736 44
  public function secure($var)
1737
  {
1738 44
    if ($var === '') {
1739 2
      return '';
1740
    }
1741
1742 44
    if ($var === "''") {
1743 1
      return "''";
1744
    }
1745
1746 44
    if ($var === null) {
1747
      if (
1748 2
          $this->_convert_null_to_empty_string === true
1749 2
      ) {
1750 1
        return "''";
1751
      }
1752
1753 2
      return 'NULL';
1754
    }
1755
1756 43
    if (in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
1757 1
      return $var;
1758
    }
1759
1760 43
    if (is_string($var)) {
1761 36
      $var = trim(trim($var), "'");
1762 36
    }
1763
1764 43
    $var = $this->escape($var, false, false, null);
1765
1766 43
    if (is_string($var)) {
1767 36
      $var = "'" . trim($var, "'") . "'";
1768 36
    }
1769
1770 43
    return $var;
1771
  }
1772
1773
  /**
1774
   * Execute a "select"-query.
1775
   *
1776
   * @param string       $table
1777
   * @param string|array $where
1778
   * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1779
   *
1780
   * @return false|Result <p>false on error</p>
1781
   *
1782
   * @throws QueryException
1783
   */
1784 24 View Code Duplication
  public function select($table, $where = '1=1', $databaseName = null)
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...
1785
  {
1786
    // init
1787 24
    $table = trim($table);
1788
1789 24
    if ($table === '') {
1790 1
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1791
1792 1
      return false;
1793
    }
1794
1795 24
    if (is_string($where)) {
1796 8
      $WHERE = $this->escape($where, false);
1797 24
    } elseif (is_array($where)) {
1798 17
      $WHERE = $this->_parseArrayPair($where, 'AND');
1799 17
    } else {
1800 1
      $WHERE = '';
1801
    }
1802
1803 24
    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...
1804
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1805
    }
1806
1807 24
    $sql = 'SELECT * FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE);";
1808
1809 24
    return $this->query($sql);
1810
  }
1811
1812
  /**
1813
   * Selects a different database than the one specified on construction.
1814
   *
1815
   * @param string $database <p>Database name to switch to.</p>
1816
   *
1817
   * @return bool <p>Boolean true on success, false otherwise.</p>
1818
   */
1819
  public function select_db($database)
1820
  {
1821
    if (!$this->isReady()) {
1822
      return false;
1823
    }
1824
1825
    return mysqli_select_db($this->link, $database);
1826
  }
1827
1828
  /**
1829
   * Set the current charset.
1830
   *
1831
   * @param string $charset
1832
   *
1833
   * @return bool
1834
   */
1835 8
  public function set_charset($charset)
1836
  {
1837 8
    $charsetLower = strtolower($charset);
1838 8
    if ($charsetLower === 'utf8' || $charsetLower === 'utf-8') {
1839 6
      $charset = 'utf8';
1840 6
    }
1841 8
    if ($charset === 'utf8' && Helper::isUtf8mb4Supported($this) === true) {
1842 6
      $charset = 'utf8mb4';
1843 6
    }
1844
1845 8
    $this->charset = (string)$charset;
1846
1847 8
    $return = mysqli_set_charset($this->link, $charset);
1848
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
1849
    /** @noinspection UsageOfSilenceOperatorInspection */
1850 8
    @\mysqli_query($this->link, 'SET CHARACTER SET ' . $charset);
1851
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
1852
    /** @noinspection UsageOfSilenceOperatorInspection */
1853 8
    @\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...
1854
1855 8
    return $return;
1856
  }
1857
1858
  /**
1859
   * Set the option to convert null to "''" (empty string).
1860
   *
1861
   * Used in secure() => select(), insert(), update(), delete()
1862
   *
1863
   * @deprecated It's not recommended to convert NULL into an empty string!
1864
   *
1865
   * @param $bool
1866
   *
1867
   * @return $this
1868
   */
1869
  public function set_convert_null_to_empty_string($bool)
1870
  {
1871
    $this->_convert_null_to_empty_string = (bool)$bool;
1872
1873
    return $this;
1874
  }
1875
1876
  /**
1877
   * Enables or disables internal report functions
1878
   *
1879
   * @link http://php.net/manual/en/function.mysqli-report.php
1880
   *
1881
   * @param int $flags <p>
1882
   *                   <table>
1883
   *                   Supported flags
1884
   *                   <tr valign="top">
1885
   *                   <td>Name</td>
1886
   *                   <td>Description</td>
1887
   *                   </tr>
1888
   *                   <tr valign="top">
1889
   *                   <td><b>MYSQLI_REPORT_OFF</b></td>
1890
   *                   <td>Turns reporting off</td>
1891
   *                   </tr>
1892
   *                   <tr valign="top">
1893
   *                   <td><b>MYSQLI_REPORT_ERROR</b></td>
1894
   *                   <td>Report errors from mysqli function calls</td>
1895
   *                   </tr>
1896
   *                   <tr valign="top">
1897
   *                   <td><b>MYSQLI_REPORT_STRICT</b></td>
1898
   *                   <td>
1899
   *                   Throw <b>mysqli_sql_exception</b> for errors
1900
   *                   instead of warnings
1901
   *                   </td>
1902
   *                   </tr>
1903
   *                   <tr valign="top">
1904
   *                   <td><b>MYSQLI_REPORT_INDEX</b></td>
1905
   *                   <td>Report if no index or bad index was used in a query</td>
1906
   *                   </tr>
1907
   *                   <tr valign="top">
1908
   *                   <td><b>MYSQLI_REPORT_ALL</b></td>
1909
   *                   <td>Set all options (report all)</td>
1910
   *                   </tr>
1911
   *                   </table>
1912
   *                   </p>
1913
   *
1914
   * @return bool
1915
   */
1916
  public function set_mysqli_report($flags)
1917
  {
1918
    return \mysqli_report($flags);
1919
  }
1920
1921
  /**
1922
   * Show config errors by throw exceptions.
1923
   *
1924
   * @return bool
1925
   *
1926
   * @throws \InvalidArgumentException
1927
   */
1928 11
  public function showConfigError()
1929
  {
1930
1931
    if (
1932 11
        !$this->hostname
1933 11
        ||
1934 10
        !$this->username
1935 10
        ||
1936 9
        !$this->database
1937 11
    ) {
1938
1939 3
      if (!$this->hostname) {
1940 1
        throw new \InvalidArgumentException('no-sql-hostname');
1941
      }
1942
1943 2
      if (!$this->username) {
1944 1
        throw new \InvalidArgumentException('no-sql-username');
1945
      }
1946
1947 1
      if (!$this->database) {
1948 1
        throw new \InvalidArgumentException('no-sql-database');
1949
      }
1950
1951
      return false;
1952
    }
1953
1954 8
    return true;
1955
  }
1956
1957
  /**
1958
   * alias: "beginTransaction()"
1959
   */
1960 1
  public function startTransaction()
1961
  {
1962 1
    $this->beginTransaction();
1963 1
  }
1964
1965
  /**
1966
   * Execute a callback inside a transaction.
1967
   *
1968
   * @param callback $callback <p>The callback to run inside the transaction, if it's throws an "Exception" or if it's
1969
   *                           returns "false", all SQL-statements in the callback will be rollbacked.</p>
1970
   *
1971
   * @return bool <p>Boolean true on success, false otherwise.</p>
1972
   */
1973 1
  public function transact($callback)
1974
  {
1975
    try {
1976
1977 1
      $beginTransaction = $this->beginTransaction();
1978 1
      if ($beginTransaction === false) {
1979 1
        $this->_debug->displayError('Error: transact -> can not start transaction!', false);
1980
1981 1
        return false;
1982
      }
1983
1984 1
      $result = call_user_func($callback, $this);
1985 1
      if ($result === false) {
1986
        /** @noinspection ThrowRawExceptionInspection */
1987 1
        throw new \Exception('call_user_func [' . $callback . '] === false');
1988
      }
1989
1990 1
      return $this->commit();
1991
1992 1
    } catch (\Exception $e) {
1993
1994 1
      $this->rollback();
1995
1996 1
      return false;
1997
    }
1998
  }
1999
2000
  /**
2001
   * Execute a "update"-query.
2002
   *
2003
   * @param string       $table
2004
   * @param array        $data
2005
   * @param array|string $where
2006
   * @param null|string  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
2007
   *
2008
   * @return false|int <p>false on error</p>
2009
   *
2010
   * @throws QueryException
2011
   */
2012 7
  public function update($table, array $data = array(), $where = '1=1', $databaseName = null)
2013
  {
2014
    // init
2015 7
    $table = trim($table);
2016
2017 7
    if ($table === '') {
2018 1
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
2019
2020 1
      return false;
2021
    }
2022
2023 7
    if (count($data) === 0) {
2024 2
      $this->_debug->displayError('Invalid data for UPDATE, data is empty.', false);
2025
2026 2
      return false;
2027
    }
2028
2029 7
    $SET = $this->_parseArrayPair($data);
2030
2031 7
    if (is_string($where)) {
2032 2
      $WHERE = $this->escape($where, false);
2033 7
    } elseif (is_array($where)) {
2034 5
      $WHERE = $this->_parseArrayPair($where, 'AND');
2035 5
    } else {
2036 1
      $WHERE = '';
2037
    }
2038
2039 7
    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...
2040
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
2041
    }
2042
2043 7
    $sql = 'UPDATE ' . $databaseName . $this->quote_string($table) . " SET $SET WHERE ($WHERE);";
2044
2045 7
    return $this->query($sql);
2046
  }
2047
2048
  /**
2049
   * Determine if database table exists
2050
   *
2051
   * @param string $table
2052
   *
2053
   * @return bool
2054
   */
2055 1
  public function table_exists($table)
2056
  {
2057 1
    $check = $this->query('SELECT 1 FROM ' . $this->quote_string($table));
2058
    if (
2059
        $check !== false
2060 1
        &&
2061
        $check instanceof Result
2062 1
        &&
2063 1
        $check->num_rows > 0
2064 1
    ) {
2065 1
      return true;
2066
    }
2067
2068 1
    return false;
2069
  }
2070
2071
  /**
2072
   * Count number of rows found matching a specific query.
2073
   *
2074
   * @param string
2075
   *
2076
   * @return int
2077
   */
2078 1
  public function num_rows($query)
2079
  {
2080 1
    $check = $this->query($query);
2081
2082
    if (
2083
        $check === false
2084 1
        ||
2085
        !$check instanceof Result
2086 1
    ) {
2087
      return 0;
2088
    }
2089
2090 1
    return $check->num_rows;
2091
  }
2092
}
2093