Completed
Push — master ( 9055a2...5efc13 )
by Lars
03:52
created

DB::reconnect()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

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