Completed
Push — master ( 766562...9055a2 )
by Lars
03:30
created

DB::getLink()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
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
    if (count($arrayPair) === 0) {
361 30
      return '';
362
    }
363
364
    $arrayPairCounter = 0;
365 30
    foreach ($arrayPair as $_key => $_value) {
366 30
      $_connector = '=';
367 30
      $_glueHelper = '';
368 30
      $_key_upper = strtoupper($_key);
369 30
370
      if (strpos($_key_upper, ' NOT') !== false) {
371 30
        $_connector = 'NOT';
372 2
      }
373 2
374
      if (strpos($_key_upper, ' IS') !== false) {
375 30
        $_connector = 'IS';
376 1
      }
377 1
378
      if (strpos($_key_upper, ' IS NOT') !== false) {
379 30
        $_connector = 'IS NOT';
380 1
      }
381 1
382
      if (strpos($_key_upper, ' IN') !== false) {
383 30
        $_connector = 'IN';
384 1
      }
385 1
386
      if (strpos($_key_upper, ' NOT IN') !== false) {
387 30
        $_connector = 'NOT IN';
388 1
      }
389 1
390
      if (strpos($_key_upper, ' BETWEEN') !== false) {
391 30
        $_connector = 'BETWEEN';
392 1
      }
393 1
394
      if (strpos($_key_upper, ' NOT BETWEEN') !== false) {
395 30
        $_connector = 'NOT BETWEEN';
396 1
      }
397 1
398
      if (strpos($_key_upper, ' LIKE') !== false) {
399 30
        $_connector = 'LIKE';
400 2
      }
401 2
402
      if (strpos($_key_upper, ' NOT LIKE') !== false) {
403 30
        $_connector = 'NOT LIKE';
404 2
      }
405 2
406 View Code Duplication
      if (strpos($_key_upper, ' >') !== false && strpos($_key_upper, ' =') === false) {
407 30
        $_connector = '>';
408 4
      }
409 4
410 View Code Duplication
      if (strpos($_key_upper, ' <') !== false && strpos($_key_upper, ' =') === false) {
411 30
        $_connector = '<';
412 1
      }
413 1
414
      if (strpos($_key_upper, ' >=') !== false) {
415 30
        $_connector = '>=';
416 4
      }
417 4
418
      if (strpos($_key_upper, ' <=') !== false) {
419 30
        $_connector = '<=';
420 1
      }
421 1
422
      if (strpos($_key_upper, ' <>') !== false) {
423 30
        $_connector = '<>';
424 1
      }
425 1
426
      if (strpos($_key_upper, ' OR') !== false) {
427 30
        $_glueHelper = 'OR';
428 2
      }
429 2
430
      if (strpos($_key_upper, ' AND') !== false) {
431 30
        $_glueHelper = 'AND';
432 1
      }
433 1
434
      if (is_array($_value) === true) {
435 30
        foreach ($_value as $oldKey => $oldValue) {
436 2
          $_value[$oldKey] = $this->secure($oldValue);
437 2
        }
438 2
439
        if ($_connector === 'NOT IN' || $_connector === 'IN') {
440 2
          $_value = '(' . implode(',', $_value) . ')';
441 1
        } elseif ($_connector === 'NOT BETWEEN' || $_connector === 'BETWEEN') {
442 2
          $_value = '(' . implode(' AND ', $_value) . ')';
443 1
        }
444 1
445
      } else {
446 2
        $_value = $this->secure($_value);
447 30
      }
448
449
      $quoteString = $this->quote_string(
450 30
          trim(
451 30
              str_ireplace(
452 30
                  array(
453
                      $_connector,
454 30
                      $_glueHelper,
455 30
                  ),
456 30
                  '',
457 30
                  $_key
458
              )
459 30
          )
460 30
      );
461 30
462
      if (!is_array($_value)) {
463 30
        $_value = array($_value);
464 30
      }
465 30
466
      if (!$_glueHelper) {
467 30
        $_glueHelper = $glue;
468 30
      }
469 30
470
      $tmpCounter = 0;
471 30
      foreach ($_value as $valueInner) {
472 30
473
        $_glueHelperInner = $_glueHelper;
474 30
475
        if ($arrayPairCounter === 0) {
476 30
477
          if ($tmpCounter === 0 && $_glueHelper === 'OR') {
478 30
            $_glueHelperInner = '1 = 1 AND ('; // first "OR"-query glue
479 1
          } elseif ($tmpCounter === 0) {
480 30
            $_glueHelperInner = ''; // first query glue e.g. for "INSERT"-query -> skip the first ","
481 30
          }
482 30
483
        } elseif ($tmpCounter === 0 && $_glueHelper === 'OR') {
484 30
          $_glueHelperInner = 'AND ('; // inner-loop "OR"-query glue
485 1
        }
486 1
487
        $sql .= ' ' . $_glueHelperInner . ' ' . $quoteString . ' ' . $_connector . ' ' . $valueInner . " \n";
488 30
        $tmpCounter++;
489 30
      }
490 30
491
      if ($_glueHelper === 'OR') {
492 30
        $sql .= ' ) ';
493 2
      }
494 2
495
      $arrayPairCounter++;
496 30
    }
497 30
498
    return $sql;
499 30
  }
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
  private function _parseQueryParams($sql, array $params = array())
510 13
  {
511
    // is there anything to parse?
512 View Code Duplication
    if (
513
        strpos($sql, '?') === false
514 13
        ||
515 13
        count($params) === 0
516 3
    ) {
517 13
      return array('sql' => $sql, 'params' => $params);
518 11
    }
519
520
    $parseKey = md5(uniqid((string)mt_rand(), true));
521 3
    $sql = str_replace('?', $parseKey, $sql);
522 3
523
    $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 3
          $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
      );
530 3
531
      if (isset($params[$k])) {
532 3
        unset($params[$k]);
533 3
      }
534 3
535
      $k++;
536 3
    }
537 3
538
    return array('sql' => $sql, 'params' => $params);
539 3
  }
540
541
  /**
542
   * Gets the number of affected rows in a previous MySQL operation.
543
   *
544
   * @return int
545
   */
546
  public function affected_rows()
547 12
  {
548
    return \mysqli_affected_rows($this->link);
549 12
  }
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 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 6
  {
558
    if ($this->_in_transaction === true) {
559 6
      $this->_debug->displayError('Error: mysql server already in transaction!', false);
560 2
561
      return false;
562 2
    }
563
564
    $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 6
      $this->_in_transaction = false;
569
    }
570
571
    return $return;
572 6
  }
573
574
  /**
575
   * Clear the errors in "_debug->_errors".
576
   *
577
   * @return bool
578
   */
579
  public function clearErrors()
580 6
  {
581
    return $this->_debug->clearErrors();
582 6
  }
583
584
  /**
585
   * Closes a previously opened database connection.
586
   */
587
  public function close()
588 2
  {
589
    $this->connected = false;
590 2
591
    if ($this->link) {
592 2
      \mysqli_close($this->link);
593 2
    }
594 2
  }
595 2
596
  /**
597
   * Commits the current transaction and end the transaction.
598
   *
599
   * @return bool <p>Boolean true on success, false otherwise.</p>
600
   */
601 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 2
  {
603
    if ($this->_in_transaction === false) {
604 2
      $this->_debug->displayError('Error: mysql server is not in transaction!', false);
605
606
      return false;
607
    }
608
609
    $return = mysqli_commit($this->link);
610 2
    \mysqli_autocommit($this->link, true);
611 2
    $this->_in_transaction = false;
612 2
613
    return $return;
614 2
  }
615
616
  /**
617
   * Open a new connection to the MySQL server.
618
   *
619
   * @return bool
620
   *
621
   * @throws DBConnectException
622
   */
623
  public function connect()
624 10
  {
625
    if ($this->isReady()) {
626 10
      return true;
627 1
    }
628
629
    $flags = null;
630 10
631
    \mysqli_report(MYSQLI_REPORT_STRICT);
632 10
    try {
633
      $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 10
635
      if (Helper::isMysqlndIsUsed() === true) {
636 10
        \mysqli_options($this->link, MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
637 10
      }
638 10
639
      if ($this->_ssl === true) {
640 10
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
      $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 10
          $flags
678
      );
679 10
680
    } catch (\Exception $e) {
681 10
      $error = 'Error connecting to mysql server: ' . $e->getMessage();
682 3
      $this->_debug->displayError($error, true);
683 3
      throw new DBConnectException($error, 100, $e);
684
    }
685
    \mysqli_report(MYSQLI_REPORT_OFF);
686 7
687
    $errno = mysqli_connect_errno();
688 7
    if (!$this->connected || $errno) {
689 7
      $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
    $this->set_charset($this->charset);
695 7
696
    return $this->isReady();
697 7
  }
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 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 2
  {
712
    // init
713
    $table = trim($table);
714 2
715
    if ($table === '') {
716 2
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
717 1
718
      return false;
719 1
    }
720
721
    if (is_string($where)) {
722 2
      $WHERE = $this->escape($where, false);
723 1
    } elseif (is_array($where)) {
724 2
      $WHERE = $this->_parseArrayPair($where, 'AND');
725 2
    } else {
726 2
      $WHERE = '';
727 1
    }
728
729
    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 2
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
731
    }
732
733
    $sql = 'DELETE FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE);";
734 2
735
    return $this->query($sql);
736 2
  }
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 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 4
  {
745
    if ($this->_in_transaction === false) {
746 4
      $this->_debug->displayError('Error: mysql server is not in transaction!', false);
747
748
      return false;
749
    }
750
751
    if (!$this->errors()) {
752 4
      $return = \mysqli_commit($this->link);
753 1
    } else {
754 1
      $this->rollback();
755 3
      $return = false;
756 3
    }
757
758
    \mysqli_autocommit($this->link, true);
759 4
    $this->_in_transaction = false;
760 4
761
    return $return;
762 4
  }
763
764
  /**
765
   * Get all errors from "$this->_errors".
766
   *
767
   * @return array|false <p>false === on errors</p>
768
   */
769
  public function errors()
770 4
  {
771
    $errors = $this->_debug->getErrors();
772 4
773
    return count($errors) > 0 ? $errors : false;
774 4
  }
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
  public function _parseQueryParamsByName($sql, array $params = array())
785 14
  {
786
    // is there anything to parse?
787 View Code Duplication
    if (
788
        strpos($sql, ':') === false
789 14
        ||
790 14
        count($params) === 0
791 12
    ) {
792 14
      return array('sql' => $sql, 'params' => $params);
793 3
    }
794
795
    $parseKey = md5(uniqid((string)mt_rand(), true));
796 12
797
    foreach ($params as $name => $value) {
798 12
      $nameTmp = $name;
799 12
      if (strpos($name, ':') === 0) {
800 12
        $nameTmp = substr($name, 1);
801 10
      }
802 10
803
      $parseKeyInner = $nameTmp . '-' . $parseKey;
804 12
      $sql = str_replace(':' . $nameTmp, $parseKeyInner, $sql);
805 12
    }
806 12
807
    foreach ($params as $name => $value) {
808 12
      $nameTmp = $name;
809 12
      if (strpos($name, ':') === 0) {
810 12
        $nameTmp = substr($name, 1);
811 10
      }
812 10
813
      $parseKeyInner = $nameTmp . '-' . $parseKey;
814 12
      $sqlBefore = $sql;
815 12
      $secureParamValue = $this->secure($params[$name]);
816 12
817
      while (strpos($sql, $parseKeyInner) !== false) {
818 12
        $sql = UTF8::str_replace_first(
819 12
            $parseKeyInner,
820 12
            $secureParamValue,
821 12
            $sql
822
        );
823 12
      }
824 12
825
      if ($sqlBefore !== $sql) {
826 12
        unset($params[$name]);
827 12
      }
828 12
    }
829 12
830
    return array('sql' => $sql, 'params' => $params);
831 12
  }
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
  public function escape($var = '', $stripe_non_utf8 = true, $html_entity_decode = false, $convert_array = false)
851 55
  {
852
    if ($var === '' || $var === "''") {
853 55
      return "''";
854 2
    }
855
856
    if ($var === null) {
857 55
      if (
858 2
          $this->_convert_null_to_empty_string === true
859
      ) {
860
        return "''";
861
      }
862 55
863 55
      return 'NULL';
864 55
    }
865
866
    // save the current value as int (for later usage)
867
    if (!is_object($var)) {
868 55
      $varInt = (int)$var;
869
    }
870 52
871 52
    /** @noinspection TypeUnsafeComparisonInspection */
872
    if (
873 52
        is_int($var)
874 52
        ||
875 52
        is_bool($var)
876 52
        ||
877
        (
878 52
            isset($varInt, $var[0])
879 55
            &&
880
            $var[0] != '0'
881
            &&
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 32
        )
884
    ) {
885
886 52
      // "int" || int || bool
887
888
      return (int)$var;
889
    }
890 5
891
    if (is_float($var)) {
892
893 52
      // float
894
895
      return $var;
896
    }
897 4
898 3
    if (is_array($var)) {
899
900
      // array
901 2
902 2
      if ($convert_array === null) {
903
        return null;
904 2
      }
905 2
906
      $varCleaned = array();
907
      foreach ((array)$var as $key => $value) {
908 2
909 2
        $key = $this->escape($key, $stripe_non_utf8, $html_entity_decode);
910
        $value = $this->escape($value, $stripe_non_utf8, $html_entity_decode);
911 2
912 1
        /** @noinspection OffsetOperationsInspection */
913
        $varCleaned[$key] = $value;
914 1
      }
915
916
      if ($convert_array === true) {
917 2
        $varCleaned = implode(',', $varCleaned);
918
919
        return $varCleaned;
920
      }
921 52
922
      return (array)$varCleaned;
923
    }
924 3
925 3
    if (
926 3
        is_string($var)
927 3
        ||
928 52
        (
929
            is_object($var)
930
            &&
931
            method_exists($var, '__toString')
932 52
        )
933
    ) {
934 52
935 11
      // "string"
936 11
937
      $var = (string)$var;
938 52
939
      if ($stripe_non_utf8 === true) {
940 1
        $var = UTF8::cleanup($var);
941 1
      }
942
943 52
      if ($html_entity_decode === true) {
944
        // use no-html-entity for db
945 52
        $var = UTF8::html_entity_decode($var);
946
      }
947 52
948
      $var = get_magic_quotes_gpc() ? stripslashes($var) : $var;
949
950
      $var = \mysqli_real_escape_string($this->getLink(), $var);
951 3
952
      return (string)$var;
953
954
    }
955
956 3
    if ($var instanceof \DateTime) {
957
958
      // "DateTime"-object
959
960
      try {
961
        return $this->escape($var->format('Y-m-d H:i:s'), false);
962 2
      } catch (\Exception $e) {
963
        return null;
964
      }
965
966
    } else {
967
      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 3
   *               "true" by e.g. "DROP"-queries<br />
983
   *               "false" on error
984
   *
985 3
   * @throws QueryException
986 3
   */
987 3
  public static function execSQL($query, $useCache = false, $cacheTTL = 3600, DB $db = null)
988 3
  {
989
    // init
990 3
    $cacheKey = null;
991 1
    if (!$db) {
992 1
      $db = self::getInstance();
993
    }
994
995 1 View Code Duplication
    if ($useCache === true) {
996 1
      $cache = new Cache(null, null, false, $useCache);
997 1
      $cacheKey = 'sql-' . md5($query);
998 1
999 1
      if (
1000
          $cache->getCacheIsReady() === true
1001
          &&
1002 1
          $cache->existsItem($cacheKey)
1003 3
      ) {
1004
        return $cache->getItem($cacheKey);
1005
      }
1006 3
1007
    } else {
1008 3
      $cache = false;
1009
    }
1010 1
1011
    $result = $db->query($query);
1012
1013
    if ($result instanceof Result) {
1014
1015 1
      $return = $result->fetchAllArray();
1016
1017 1
      // save into the cache
1018 View Code Duplication
      if (
1019 1
          $cacheKey !== null
1020 1
          &&
1021 1
          $useCache === true
1022 1
          &&
1023 1
          $cache instanceof Cache
1024
          &&
1025 1
          $cache->getCacheIsReady() === true
1026 2
      ) {
1027
        $cache->setItem($cacheKey, $return, $cacheTTL);
1028
      }
1029 3
1030
    } else {
1031
      $return = $result;
1032
    }
1033
1034
    return $return;
1035
  }
1036
1037 1
  /**
1038
   * Get all table-names via "SHOW TABLES".
1039 1
   *
1040 1
   * @return array
1041
   */
1042 1
  public function getAllTables()
1043
  {
1044
    $query = 'SHOW TABLES';
1045
    $result = $this->query($query);
1046
1047
    return $result->fetchAllArray();
1048 9
  }
1049
1050 9
  /**
1051
   * @return Debug
1052
   */
1053
  public function getDebugger()
1054
  {
1055
    return $this->_debug;
1056
  }
1057
1058 1
  /**
1059
   * Get errors from "$this->_errors".
1060 1
   *
1061
   * @return array
1062
   */
1063
  public function getErrors()
1064
  {
1065
    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 97
   *                                     '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 97
     * @var $firstInstance DB
1103 97
     */
1104 14
    static $firstInstance = null;
1105 97
1106 14
    if (
1107
        $hostname . $username . $password . $database . $port . $charset == ''
1108
        &&
1109 97
        null !== $firstInstance
1110 97
    ) {
1111 97
      return $firstInstance;
1112
    }
1113 97
1114 97
    $extra_config_string = '';
1115
    if (is_array($extra_config) === true) {
1116
      foreach ($extra_config as $extra_config_key => $extra_config_value) {
1117
        $extra_config_string .= $extra_config_key . (string)$extra_config_value;
1118
      }
1119 97
    } else {
1120 97
      // only for backward compatibility
1121 97
      $extra_config_string = (int)$extra_config;
1122
    }
1123 97
1124 11
    $connection = md5(
1125 11
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . $extra_config_string
1126 11
    );
1127 11
1128 11
    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
          $charset,
1136 11
          $exit_on_error,
1137
          $echo_on_error,
1138 5
          $logger_class_name,
1139 1
          $logger_level,
1140 1
          $extra_config
1141 5
      );
1142
1143 97
      if (null === $firstInstance) {
1144
        $firstInstance = $instance[$connection];
1145
      }
1146
    }
1147
1148
    return $instance[$connection];
1149
  }
1150
1151 56
  /**
1152
   * Get the mysqli-link (link identifier returned by mysqli-connect).
1153 56
   *
1154
   * @return \mysqli
1155
   */
1156
  public function getLink()
1157
  {
1158
    return $this->link;
1159
  }
1160
1161 1
  /**
1162
   * Get the current charset.
1163 1
   *
1164
   * @return string
1165
   */
1166
  public function get_charset()
1167
  {
1168
    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 28
   *
1188
   * @return false|int <p>false on error</p>
1189
   *
1190 28
   * @throws QueryException
1191
   */
1192 28
  public function insert($table, array $data = array(), $databaseName = null)
1193 2
  {
1194
    // init
1195 2
    $table = trim($table);
1196
1197
    if ($table === '') {
1198 27
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1199 3
1200
      return false;
1201 3
    }
1202
1203
    if (count($data) === 0) {
1204 25
      $this->_debug->displayError('Invalid data for INSERT, data is empty.', false);
1205
1206 25
      return false;
1207
    }
1208
1209
    $SET = $this->_parseArrayPair($data);
1210 25
1211
    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 25
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1213
    }
1214
1215
    $sql = 'INSERT INTO ' . $databaseName . $this->quote_string($table) . " SET $SET;";
1216
1217
    return $this->query($sql);
1218
  }
1219
1220 58
  /**
1221
   * Returns the auto generated id used in the last query.
1222 58
   *
1223
   * @return int|string
1224
   */
1225
  public function insert_id()
1226
  {
1227
    return \mysqli_insert_id($this->link);
1228
  }
1229
1230 106
  /**
1231
   * Check if db-connection is ready.
1232 106
   *
1233
   * @return boolean
1234
   */
1235
  public function isReady()
1236
  {
1237
    return $this->connected ? true : false;
1238
  }
1239
1240 1
  /**
1241
   * Get the last sql-error.
1242 1
   *
1243
   * @return string|false <p>false === there was no error</p>
1244 1
   */
1245
  public function lastError()
1246
  {
1247
    $errors = $this->_debug->getErrors();
1248
1249
    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 1
   *                        "boolean" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1260
   *                        "boolean" by only by e.g. "DROP"-queries<br />
1261 1
   *
1262
   * @throws QueryException
1263
   */
1264
  public function multi_query($sql)
1265 1
  {
1266 1
    if (!$this->isReady()) {
1267
      return false;
1268 1
    }
1269
1270 View Code Duplication
    if (!$sql || $sql === '') {
1271 1
      $this->_debug->displayError('Can not execute an empty query.', false);
1272 1
1273 1
      return false;
1274
    }
1275 1
1276
    $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
    $returnTheResult = false;
1283 1
    $result = array();
1284
1285 1
    if ($resultTmp) {
1286
      do {
1287 1
1288 1
        $resultTmpInner = \mysqli_store_result($this->link);
1289
1290 1
        if ($resultTmpInner instanceof \mysqli_result) {
1291
1292
          $returnTheResult = true;
1293 1
          $result[] = new Result($sql, $resultTmpInner);
1294 1
1295 1
        } else {
1296
1297
          // is the query successful
1298
          if ($resultTmpInner === true || !\mysqli_errno($this->link)) {
1299
            $result[] = true;
1300
          } else {
1301 1
            $result[] = false;
1302
          }
1303 1
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
      $this->_debug->logQuery($sql, $query_duration, 0, true);
1312 1
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 1
    }
1320 1
1321 1
    if (
1322
        count($result) > 0
1323
        &&
1324
        in_array(false, $result, true) === false
1325
    ) {
1326
      return true;
1327
    }
1328
1329
    return false;
1330
  }
1331
1332
  /**
1333 3
   * Pings a server connection, or tries to reconnect
1334
   * if the connection has gone down.
1335
   *
1336 3
   * @return boolean
1337 3
   */
1338 3
  public function ping()
1339 3
  {
1340
    if (
1341
        $this->link
1342 3
        &&
1343
        $this->link instanceof \mysqli
1344
    ) {
1345
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
1346
      /** @noinspection UsageOfSilenceOperatorInspection */
1347
      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 2
   *
1356
   * @param string $query
1357 2
   *
1358
   * @return Prepare
1359
   */
1360
  public function prepare($query)
1361
  {
1362
    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 96
   *                                      "false" on error
1416
   *                                      </p>
1417 96
   *
1418
   * @throws QueryException
1419
   */
1420
  public function query($sql = '', $params = false)
1421 96
  {
1422 4
    if (!$this->isReady()) {
1423
      return false;
1424 4
    }
1425
1426 View Code Duplication
    if (!$sql || $sql === '') {
1427
      $this->_debug->displayError('Can not execute an empty query.', false);
1428
1429 94
      return false;
1430 29
    }
1431 94
1432 29
    if (
1433 94
        $params !== false
1434 13
        &&
1435 13
        is_array($params)
1436 13
        &&
1437 13
        count($params) > 0
1438
    ) {
1439
      $parseQueryParams = $this->_parseQueryParams($sql, $params);
1440
      $parseQueryParamsByName = $this->_parseQueryParamsByName($parseQueryParams['sql'], $parseQueryParams['params']);
1441
      $sql = $parseQueryParamsByName['sql'];
1442
    }
1443 94
1444 94
    // DEBUG
1445 94
    // var_dump($params);
1446
    // echo $sql . "\n";
1447 94
1448
    $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 62
1452 62
    $this->query_count++;
1453 64
1454
    $mysqli_field_count = \mysqli_field_count($this->link);
1455
    if ($mysqli_field_count) {
1456 94
      $result = \mysqli_store_result($this->link);
1457
    } else {
1458
      $result = $query_result;
1459 61
    }
1460
1461
    if ($result instanceof \mysqli_result) {
1462 61
1463
      // log the select query
1464
      $this->_debug->logQuery($sql, $query_duration, $mysqli_field_count);
1465 66
1466
      // return query result object
1467
      return new Result($sql, $result);
1468 63
    }
1469 58
1470 58
    if ($query_result === true) {
1471
1472 58
      // "INSERT" || "REPLACE"
1473 View Code Duplication
      if (preg_match('/^\s*?(?:INSERT|REPLACE)\s+/i', $sql)) {
1474
        $insert_id = (int)$this->insert_id();
1475
        $this->_debug->logQuery($sql, $query_duration, $insert_id);
1476 38
1477 12
        return $insert_id;
1478 12
      }
1479
1480 12
      // "UPDATE" || "DELETE"
1481 View Code Duplication
      if (preg_match('/^\s*?(?:UPDATE|DELETE)\s+/i', $sql)) {
1482
        $affected_rows = (int)$this->affected_rows();
1483
        $this->_debug->logQuery($sql, $query_duration, $affected_rows);
1484 27
1485
        return $affected_rows;
1486 27
      }
1487
1488
      // log the ? query
1489
      $this->_debug->logQuery($sql, $query_duration, 0);
1490 11
1491
      return true;
1492 11
    }
1493
1494
    // log the error query
1495
    $this->_debug->logQuery($sql, $query_duration, 0, true);
1496
1497
    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 13
   * @throws QueryException
1510
   * @throws DBGoneAwayException
1511 13
   *
1512
   * @return bool
1513
   */
1514
  private function queryErrorHandling($errorMessage, $errorNumber, $sql, $sqlParams = false, $sqlMultiQuery = false)
1515 13
  {
1516
    $errorNumber = (int)$errorNumber;
1517 12
1518
    if (
1519 13
        $errorMessage === 'DB server has gone away'
1520 1
        ||
1521
        $errorMessage === 'MySQL server has gone away'
1522
        ||
1523 1
        $errorNumber === 2006
1524
    ) {
1525
      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 1
      }
1532 1
1533
      $this->_debug->mailToAdmin('DB-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql);
1534
1535 1
      // reconnect
1536 1
      $RECONNECT_COUNTER++;
1537
      $this->reconnect(true);
1538
1539
      // re-run the current (non multi) query
1540
      if ($sqlMultiQuery === false) {
1541
        return $this->query($sql, $sqlParams);
1542 12
      }
1543
1544 12
      return false;
1545 12
    }
1546 4
1547 4
    $this->_debug->mailToAdmin('SQL-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql);
1548
1549
    $force_exception_after_error = null; // auto
1550 12
    if ($this->_in_transaction === true) {
1551
      $force_exception_after_error = false;
1552 12
    }
1553
    // this query returned an error, we must display it (only for dev) !!!
1554
1555
    $this->_debug->displayError($errorMessage . '(' . $errorNumber . ') ' . ' | ' . $sql, $force_exception_after_error);
1556
1557
    return false;
1558
  }
1559
1560
  /**
1561
   * Quote && Escape e.g. a table name string.
1562 36
   *
1563
   * @param string $str
1564 36
   *
1565 36
   * @return string
1566 36
   */
1567 36
  public function quote_string($str)
1568 36
  {
1569
    $str = str_replace(
1570 36
        '`',
1571 36
        '``',
1572
        trim(
1573 36
            $this->escape($str, false),
1574
            '`'
1575
        )
1576
    );
1577
1578
    return '`' . $str . '`';
1579
  }
1580
1581
  /**
1582
   * Reconnect to the MySQL-Server.
1583 3
   *
1584
   * @param bool $checkViaPing
1585 3
   *
1586
   * @return bool
1587 3
   */
1588 2
  public function reconnect($checkViaPing = false)
1589 2
  {
1590
    $ping = false;
1591 3
1592 3
    if ($checkViaPing === true) {
1593 3
      $ping = $this->ping();
1594 3
    }
1595
1596 3
    if ($ping !== true) {
1597
      $this->connected = false;
1598
      $this->connect();
1599
    }
1600
1601
    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 1
   *
1611
   * @return false|int <p>false on error</p>
1612
   *
1613 1
   * @throws QueryException
1614
   */
1615 1
  public function replace($table, array $data = array(), $databaseName = null)
1616 1
  {
1617
    // init
1618 1
    $table = trim($table);
1619
1620
    if ($table === '') {
1621 1
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1622 1
1623
      return false;
1624 1
    }
1625
1626
    if (count($data) === 0) {
1627
      $this->_debug->displayError('Invalid data for REPLACE, data is empty.', false);
1628 1
1629 1
      return false;
1630
    }
1631 1
1632 1
    // extracting column names
1633
    $columns = array_keys($data);
1634 1
    foreach ($columns as $k => $_key) {
1635
      /** @noinspection AlterInForeachInspection */
1636
      $columns[$k] = $this->quote_string($_key);
1637 1
    }
1638
1639 1
    $columns = implode(',', $columns);
1640 1
1641 1
    // extracting values
1642
    foreach ($data as $k => $_value) {
1643 1
      /** @noinspection AlterInForeachInspection */
1644
      $data[$k] = $this->secure($_value);
1645
    }
1646
    $values = implode(',', $data);
1647 1
1648
    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 1
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1650
    }
1651
1652
    $sql = 'REPLACE INTO ' . $databaseName . $this->quote_string($table) . " ($columns) VALUES ($values);";
1653
1654
    return $this->query($sql);
1655
  }
1656
1657 4
  /**
1658
   * Rollback in a transaction and end the transaction.
1659 4
   *
1660
   * @return bool <p>Boolean true on success, false otherwise.</p>
1661
   */
1662 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
    if ($this->_in_transaction === false) {
1665 4
      $this->_debug->displayError('Error: mysql server is not in transaction!', false);
1666 4
1667 4
      return false;
1668
    }
1669 4
1670
    $return = \mysqli_rollback($this->link);
1671
    \mysqli_autocommit($this->link, true);
1672
    $this->_in_transaction = false;
1673
1674
    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 44
   *
1715
   * @param mixed $var
1716
   *
1717
   * @return mixed
1718 44
   */
1719
  public function secure($var)
1720 44
  {
1721 44
    if ($var === '' || $var === "''") {
1722
      return "''";
1723 1
    }
1724 44
1725 1
    if ($var === null) {
1726
      if (
1727
          $this->_convert_null_to_empty_string === true
1728
      ) {
1729 44
        return "''";
1730 44
      }
1731
1732 44
      return 'NULL';
1733 2
    }
1734
1735
    if (in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
1736 43
      return $var;
1737 1
    }
1738
1739
    if (is_string($var)) {
1740 43
      $var = trim(trim($var), "'");
1741 36
    }
1742 36
1743
    $var = $this->escape($var, false, false, null);
1744 43
1745
    if (is_string($var)) {
1746 43
      $var = "'" . trim($var, "'") . "'";
1747 36
    }
1748 36
1749
    return $var;
1750 43
  }
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 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 24
  {
1765
    // init
1766
    $table = trim($table);
1767 24
1768
    if ($table === '') {
1769 24
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1770 1
1771
      return false;
1772 1
    }
1773
1774
    if (is_string($where)) {
1775 24
      $WHERE = $this->escape($where, false);
1776 8
    } elseif (is_array($where)) {
1777 24
      $WHERE = $this->_parseArrayPair($where, 'AND');
1778 17
    } else {
1779 17
      $WHERE = '';
1780 1
    }
1781
1782
    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 24
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1784
    }
1785
1786
    $sql = 'SELECT * FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE);";
1787 24
1788
    return $this->query($sql);
1789 24
  }
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
  public function set_charset($charset)
1815 8
  {
1816
    $charsetLower = strtolower($charset);
1817 8
    if ($charsetLower === 'utf8' || $charsetLower === 'utf-8') {
1818 8
      $charset = 'utf8';
1819 6
    }
1820 6
    if ($charset === 'utf8' && Helper::isUtf8mb4Supported($this) === true) {
1821 8
      $charset = 'utf8mb4';
1822 6
    }
1823 6
1824
    $this->charset = (string)$charset;
1825 8
1826
    $return = mysqli_set_charset($this->link, $charset);
1827 8
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
1828
    /** @noinspection UsageOfSilenceOperatorInspection */
1829
    @\mysqli_query($this->link, 'SET CHARACTER SET ' . $charset);
1830 8
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
1831
    /** @noinspection UsageOfSilenceOperatorInspection */
1832
    @\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 8
1834
    return $return;
1835 8
  }
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
  public function showConfigError()
1908 11
  {
1909
1910
    if (
1911
        !$this->hostname
1912 11
        ||
1913 11
        !$this->username
1914 10
        ||
1915 10
        !$this->database
1916 9
    ) {
1917 11
1918
      if (!$this->hostname) {
1919 3
        throw new \InvalidArgumentException('no-sql-hostname');
1920 1
      }
1921
1922
      if (!$this->username) {
1923 2
        throw new \InvalidArgumentException('no-sql-username');
1924 1
      }
1925
1926
      if (!$this->database) {
1927 1
        throw new \InvalidArgumentException('no-sql-database');
1928 1
      }
1929
1930
      return false;
1931
    }
1932
1933
    return true;
1934 8
  }
1935
1936
  /**
1937
   * alias: "beginTransaction()"
1938
   */
1939
  public function startTransaction()
1940 1
  {
1941
    $this->beginTransaction();
1942 1
  }
1943 1
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
  public function transact($callback)
1953 1
  {
1954
    try {
1955
1956
      $beginTransaction = $this->beginTransaction();
1957 1
      if ($beginTransaction === false) {
1958 1
        $this->_debug->displayError('Error: transact -> can not start transaction!', false);
1959 1
1960
        return false;
1961 1
      }
1962
1963
      $result = call_user_func($callback, $this);
1964 1
      if ($result === false) {
1965 1
        /** @noinspection ThrowRawExceptionInspection */
1966
        throw new \Exception('call_user_func [' . $callback . '] === false');
1967 1
      }
1968
1969
      return $this->commit();
1970 1
1971
    } catch (\Exception $e) {
1972 1
1973
      $this->rollback();
1974 1
1975
      return false;
1976 1
    }
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
  public function update($table, array $data = array(), $where = '1=1', $databaseName = null)
1992 7
  {
1993
    // init
1994
    $table = trim($table);
1995 7
1996
    if ($table === '') {
1997 7
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1998 1
1999
      return false;
2000 1
    }
2001
2002
    if (count($data) === 0) {
2003 7
      $this->_debug->displayError('Invalid data for UPDATE, data is empty.', false);
2004 2
2005
      return false;
2006 2
    }
2007
2008
    $SET = $this->_parseArrayPair($data);
2009 7
2010
    if (is_string($where)) {
2011 7
      $WHERE = $this->escape($where, false);
2012 2
    } elseif (is_array($where)) {
2013 7
      $WHERE = $this->_parseArrayPair($where, 'AND');
2014 5
    } else {
2015 5
      $WHERE = '';
2016 1
    }
2017
2018
    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 7
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
2020
    }
2021
2022
    $sql = 'UPDATE ' . $databaseName . $this->quote_string($table) . " SET $SET WHERE ($WHERE);";
2023 7
2024
    return $this->query($sql);
2025 7
  }
2026
2027
  /**
2028
   * Determine if database table exists
2029
   *
2030
   * @param string $table
2031
   *
2032
   * @return bool
2033
   */
2034
  public function table_exists($table)
2035 1
  {
2036
    $check = $this->query('SELECT 1 FROM ' . $this->quote_string($table));
2037 1
    if (
2038
        $check !== false
2039
        &&
2040 1
        $check instanceof Result
2041
        &&
2042 1
        $check->num_rows > 0
2043 1
    ) {
2044 1
      return true;
2045 1
    }
2046
2047
    return false;
2048 1
  }
2049
2050
  /**
2051
   * Count number of rows found matching a specific query.
2052
   *
2053
   * @param string
2054
   *
2055
   * @return int
2056
   */
2057
  public function num_rows($query)
2058 1
  {
2059
    $check = $this->query($query);
2060 1
2061
    if (
2062
        $check === false
2063
        ||
2064 1
        !$check instanceof Result
2065
    ) {
2066 1
      return 0;
2067
    }
2068
2069
    return $check->num_rows;
2070 1
  }
2071
}
2072