Completed
Push — master ( cd7afc...70524f )
by Lars
04:05
created

DB::close()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6.3183

Importance

Changes 0
Metric Value
dl 0
loc 34
ccs 10
cts 16
cp 0.625
rs 9.0648
c 0
b 0
f 0
cc 5
nc 4
nop 0
crap 6.3183
1
<?php
2
3
declare(strict_types=1);
4
5
namespace voku\db;
6
7
use voku\cache\Cache;
8
use voku\db\exceptions\DBConnectException;
9
use voku\db\exceptions\DBGoneAwayException;
10
use voku\db\exceptions\QueryException;
11
use voku\helper\UTF8;
12
13
/**
14
 * DB: This class can handle DB queries via MySQLi.
15
 *
16
 * @package voku\db
17
 */
18
final class DB
19
{
20
21
  /**
22
   * @var int
23
   */
24
  public $query_count = 0;
25
26
  /**
27
   * @var \mysqli|null
28
   */
29
  private $mysqli_link;
30
31
  /**
32
   * @var bool
33
   */
34
  private $connected = false;
35
36
  /**
37
   * @var array
38
   */
39
  private $mysqlDefaultTimeFunctions;
40
41
  /**
42
   * @var string
43
   */
44
  private $hostname = '';
45
46
  /**
47
   * @var string
48
   */
49
  private $username = '';
50
51
  /**
52
   * @var string
53
   */
54
  private $password = '';
55
56
  /**
57
   * @var string
58
   */
59
  private $database = '';
60
61
  /**
62
   * @var int
63
   */
64
  private $port = 3306;
65
66
  /**
67
   * @var string
68
   */
69
  private $charset = 'utf8';
70
71
  /**
72
   * @var string
73
   */
74
  private $socket = '';
75
76
  /**
77
   * @var bool
78
   */
79
  private $session_to_db = false;
80
81
  /**
82
   * @var bool
83
   */
84
  private $in_transaction = false;
85
86
  /**
87
   * @var bool
88
   */
89
  private $convert_null_to_empty_string = false;
90
91
  /**
92
   * @var bool
93
   */
94
  private $ssl = false;
95
96
  /**
97
   * The path name to the key file
98
   *
99
   * @var string
100
   */
101
  private $clientkey;
102
103
  /**
104
   * The path name to the certificate file
105
   *
106
   * @var string
107
   */
108
  private $clientcert;
109
110
  /**
111
   * The path name to the certificate authority file
112
   *
113
   * @var string
114
   */
115
  private $cacert;
116
117
  /**
118
   * @var Debug
119
   */
120
  private $debug;
121
122
  /**
123
   * @var null|\Doctrine\DBAL\Connection
124
   */
125
  private $doctrine_connection;
126
127
  /**
128
   * @var null|int
129
   */
130
  private $affected_rows;
131
132
  /**
133
   * __construct()
134
   *
135
   * @param string $hostname
136
   * @param string $username
137
   * @param string $password
138
   * @param string $database
139
   * @param int    $port
140
   * @param string $charset
141
   * @param bool   $exit_on_error         <p>Throw a 'Exception' when a query failed, otherwise it will return 'false'.
142
   *                                      Use false to disable it.</p>
143
   * @param bool   $echo_on_error         <p>Echo the error if "checkForDev()" returns true.
144
   *                                      Use false to disable it.</p>
145
   * @param string $logger_class_name
146
   * @param string $logger_level          <p>'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'</p>
147
   * @param array  $extra_config          <p>
148
   *                                      'session_to_db' => bool<br>
149
   *                                      'socket'        => 'string (path)'<br>
150
   *                                      'ssl'           => bool<br>
151
   *                                      'clientkey'     => 'string (path)'<br>
152
   *                                      'clientcert'    => 'string (path)'<br>
153
   *                                      'cacert'        => 'string (path)'<br>
154
   *                                      </p>
155
   */
156 23
  private function __construct(string $hostname, string $username, string $password, string $database, $port, string $charset, bool $exit_on_error, bool $echo_on_error, string $logger_class_name, string $logger_level, array $extra_config = [])
157
  {
158 23
    $this->debug = new Debug($this);
159
160 23
    $this->_loadConfig(
161 23
        $hostname,
162 23
        $username,
163 23
        $password,
164 23
        $database,
165 23
        $port,
166 23
        $charset,
167 23
        $exit_on_error,
168 23
        $echo_on_error,
169 23
        $logger_class_name,
170 23
        $logger_level,
171 23
        $extra_config
172
    );
173
174 14
    $this->connect();
175
176 5
    $this->mysqlDefaultTimeFunctions = [
177
      // Returns the current date.
178
      'CURDATE()',
179
      // CURRENT_DATE	| Synonyms for CURDATE()
180
      'CURRENT_DATE()',
181
      // CURRENT_TIME	| Synonyms for CURTIME()
182
      'CURRENT_TIME()',
183
      // CURRENT_TIMESTAMP | Synonyms for NOW()
184
      'CURRENT_TIMESTAMP()',
185
      // Returns the current time.
186
      'CURTIME()',
187
      // Synonym for NOW()
188
      'LOCALTIME()',
189
      // Synonym for NOW()
190
      'LOCALTIMESTAMP()',
191
      // Returns the current date and time.
192
      'NOW()',
193
      // Returns the time at which the function executes.
194
      'SYSDATE()',
195
      // Returns a UNIX timestamp.
196
      'UNIX_TIMESTAMP()',
197
      // Returns the current UTC date.
198
      'UTC_DATE()',
199
      // Returns the current UTC time.
200
      'UTC_TIME()',
201
      // Returns the current UTC date and time.
202
      'UTC_TIMESTAMP()',
203
    ];
204 5
  }
205
206
  /**
207
   * Prevent the instance from being cloned.
208
   *
209
   * @return void
210
   */
211
  private function __clone()
212
  {
213
  }
214
215
  /**
216
   * __destruct
217
   */
218
  public function __destruct()
219
  {
220
    // close the connection only if we don't save PHP-SESSION's in DB
221
    if ($this->session_to_db === false) {
222
      $this->close();
223
    }
224
  }
225
226
  /**
227
   * @param null|string $sql
228
   * @param array       $bindings
229
   *
230
   * @return bool|int|Result|DB           <p>
231
   *                                      "DB" by "$sql" === null<br />
232
   *                                      "Result" by "<b>SELECT</b>"-queries<br />
233
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
234
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
235
   *                                      "true" by e.g. "DROP"-queries<br />
236
   *                                      "false" on error
237
   *                                      </p>
238
   */
239 4
  public function __invoke(string $sql = null, array $bindings = [])
240
  {
241 4
    return null !== $sql ? $this->query($sql, $bindings) : $this;
242
  }
243
244
  /**
245
   * __wakeup
246
   *
247
   * @return void
248
   */
249 4
  public function __wakeup()
250
  {
251 4
    $this->reconnect();
252 4
  }
253
254
  /**
255
   * Load the config from the constructor.
256
   *
257
   * @param string $hostname
258
   * @param string $username
259
   * @param string $password
260
   * @param string $database
261
   * @param int    $port                  <p>default is (int)3306</p>
262
   * @param string $charset               <p>default is 'utf8' or 'utf8mb4' (if supported)</p>
263
   * @param bool   $exit_on_error         <p>Throw a 'Exception' when a query failed, otherwise it will return 'false'.
264
   *                                      Use false to disable it.</p>
265
   * @param bool   $echo_on_error         <p>Echo the error if "checkForDev()" returns true.
266
   *                                      Use false to disable it.</p>
267
   * @param string $logger_class_name
268
   * @param string $logger_level
269
   * @param array  $extra_config          <p>
270
   *                                      'session_to_db' => false|true<br>
271
   *                                      'socket' => 'string (path)'<br>
272
   *                                      'ssl' => 'bool'<br>
273
   *                                      'clientkey' => 'string (path)'<br>
274
   *                                      'clientcert' => 'string (path)'<br>
275
   *                                      'cacert' => 'string (path)'<br>
276
   *                                      </p>
277
   *
278
   * @return bool
279
   */
280 23
  private function _loadConfig(
281
      string $hostname,
282
      string $username,
283
      string $password,
284
      string $database,
285
      $port,
286
      string $charset,
287
      bool $exit_on_error,
288
      bool $echo_on_error,
289
      string $logger_class_name,
290
      string $logger_level,
291
      array $extra_config = []
292
  ): bool
293
  {
294 23
    $this->hostname = $hostname;
295 23
    $this->username = $username;
296 23
    $this->password = $password;
297 23
    $this->database = $database;
298
299 23
    if ($charset) {
300 23
      $this->charset = $charset;
301
    }
302
303 23
    if ($port) {
304 11
      $this->port = (int)$port;
305
    } else {
306
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
307 13
      $this->port = (int)@ini_get('mysqli.default_port');
308
    }
309
310
    // fallback
311 23
    if (!$this->port) {
312
      $this->port = 3306;
313
    }
314
315 23
    if (!$this->socket) {
316
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
317 23
      $this->socket = @ini_get('mysqli.default_socket');
318
    }
319
320 23
    $this->debug->setExitOnError($exit_on_error);
321 23
    $this->debug->setEchoOnError($echo_on_error);
322
323 23
    $this->debug->setLoggerClassName($logger_class_name);
324 23
    $this->debug->setLoggerLevel($logger_level);
325
326 23
    $this->setConfigExtra($extra_config);
327
328 23
    return $this->showConfigError();
329
  }
330
331
  /**
332
   * Parses arrays with value pairs and generates SQL to use in queries.
333
   *
334
   * @param array  $arrayPair
335
   * @param string $glue <p>This is the separator.</p>
336
   *
337
   * @return string
338
   *
339
   * @internal
340
   */
341 72
  public function _parseArrayPair(array $arrayPair, string $glue = ','): string
342
  {
343
    // init
344 72
    $sql = '';
345
346 72
    if (\count($arrayPair) === 0) {
347
      return '';
348
    }
349
350 72
    $arrayPairCounter = 0;
351 72
    foreach ($arrayPair as $_key => $_value) {
352 72
      $_connector = '=';
353 72
      $_glueHelper = '';
354 72
      $_key_upper = \strtoupper($_key);
355
356 72
      if (\strpos($_key_upper, ' NOT') !== false) {
357 6
        $_connector = 'NOT';
358
      }
359
360 72
      if (\strpos($_key_upper, ' IS') !== false) {
361 3
        $_connector = 'IS';
362
      }
363
364 72
      if (\strpos($_key_upper, ' IS NOT') !== false) {
365 3
        $_connector = 'IS NOT';
366
      }
367
368 72
      if (\strpos($_key_upper, ' IN') !== false) {
369 3
        $_connector = 'IN';
370
      }
371
372 72
      if (\strpos($_key_upper, ' NOT IN') !== false) {
373 3
        $_connector = 'NOT IN';
374
      }
375
376 72
      if (\strpos($_key_upper, ' BETWEEN') !== false) {
377 3
        $_connector = 'BETWEEN';
378
      }
379
380 72
      if (\strpos($_key_upper, ' NOT BETWEEN') !== false) {
381 3
        $_connector = 'NOT BETWEEN';
382
      }
383
384 72
      if (\strpos($_key_upper, ' LIKE') !== false) {
385 6
        $_connector = 'LIKE';
386
      }
387
388 72
      if (\strpos($_key_upper, ' NOT LIKE') !== false) {
389 6
        $_connector = 'NOT LIKE';
390
      }
391
392 72 View Code Duplication
      if (\strpos($_key_upper, ' >') !== false && \strpos($_key_upper, ' =') === false) {
393 8
        $_connector = '>';
394
      }
395
396 72 View Code Duplication
      if (\strpos($_key_upper, ' <') !== false && \strpos($_key_upper, ' =') === false) {
397 3
        $_connector = '<';
398
      }
399
400 72
      if (\strpos($_key_upper, ' >=') !== false) {
401 8
        $_connector = '>=';
402
      }
403
404 72
      if (\strpos($_key_upper, ' <=') !== false) {
405 3
        $_connector = '<=';
406
      }
407
408 72
      if (\strpos($_key_upper, ' <>') !== false) {
409 3
        $_connector = '<>';
410
      }
411
412 72
      if (\strpos($_key_upper, ' OR') !== false) {
413 6
        $_glueHelper = 'OR';
414
      }
415
416 72
      if (\strpos($_key_upper, ' AND') !== false) {
417 3
        $_glueHelper = 'AND';
418
      }
419
420 72
      if (\is_array($_value) === true) {
421 6
        foreach ($_value as $oldKey => $oldValue) {
422 6
          $_value[$oldKey] = $this->secure($oldValue);
423
        }
424
425 6
        if ($_connector === 'NOT IN' || $_connector === 'IN') {
426 3
          $_value = '(' . \implode(',', $_value) . ')';
427 6
        } elseif ($_connector === 'NOT BETWEEN' || $_connector === 'BETWEEN') {
428 6
          $_value = '(' . \implode(' AND ', $_value) . ')';
429
        }
430
431
      } else {
432 72
        $_value = $this->secure($_value);
433
      }
434
435 72
      $quoteString = $this->quote_string(
436 72
          \trim(
437 72
              \str_ireplace(
438
                  [
439 72
                      $_connector,
440 72
                      $_glueHelper,
441
                  ],
442 72
                  '',
443 72
                  $_key
444
              )
445
          )
446
      );
447
448 72
      $_value = (array)$_value;
449
450 72
      if (!$_glueHelper) {
451 72
        $_glueHelper = $glue;
452
      }
453
454 72
      $tmpCounter = 0;
455 72
      foreach ($_value as $valueInner) {
456
457 72
        $_glueHelperInner = $_glueHelper;
458
459 72
        if ($arrayPairCounter === 0) {
460
461 72
          if ($tmpCounter === 0 && $_glueHelper === 'OR') {
462 3
            $_glueHelperInner = '1 = 1 AND ('; // first "OR"-query glue
463 72
          } elseif ($tmpCounter === 0) {
464 72
            $_glueHelperInner = ''; // first query glue e.g. for "INSERT"-query -> skip the first ","
465
          }
466
467 68
        } elseif ($tmpCounter === 0 && $_glueHelper === 'OR') {
468 3
          $_glueHelperInner = 'AND ('; // inner-loop "OR"-query glue
469
        }
470
471 72
        if (\is_string($valueInner) && $valueInner === '') {
472
          $valueInner = "''";
473
        }
474
475 72
        $sql .= ' ' . $_glueHelperInner . ' ' . $quoteString . ' ' . $_connector . ' ' . $valueInner . " \n";
476 72
        $tmpCounter++;
477
      }
478
479 72
      if ($_glueHelper === 'OR') {
480 6
        $sql .= ' ) ';
481
      }
482
483 72
      $arrayPairCounter++;
484
    }
485
486 72
    return $sql;
487
  }
488
489
  /**
490
   * _parseQueryParams
491
   *
492
   * @param string $sql
493
   * @param array  $params
494
   *
495
   * @return array <p>with the keys -> 'sql', 'params'</p>
496
   */
497 17
  private function _parseQueryParams(string $sql, array $params = []): array
498
  {
499 17
    $offset = \strpos($sql, '?');
500
501
    // is there anything to parse?
502
    if (
503 17
        $offset === false
504
        ||
505 17
        \count($params) === 0
506
    ) {
507 13
      return ['sql' => $sql, 'params' => $params];
508
    }
509
510 7
    foreach ($params as $key => $param) {
511
512
      // use this only for not named parameters
513 7
      if (!\is_int($key)) {
514 3
        continue;
515
      }
516
517 7
      if ($offset === false) {
518
        continue;
519
      }
520
521 7
      $replacement = $this->secure($param);
522
523 7
      unset($params[$key]);
524
525 7
      $sql = \substr_replace($sql, $replacement, $offset, 1);
526 7
      $offset = \strpos($sql, '?', $offset + \strlen((string)$replacement));
527
    }
528
529 7
    return ['sql' => $sql, 'params' => $params];
530
  }
531
532
  /**
533
   * Returns the SQL by replacing :placeholders with SQL-escaped values.
534
   *
535
   * @param mixed $sql    <p>The SQL string.</p>
536
   * @param array $params <p>An array of key-value bindings.</p>
537
   *
538
   * @return array <p>with the keys -> 'sql', 'params'</p>
539
   */
540 20
  private function _parseQueryParamsByName(string $sql, array $params = []): array
541
  {
542
    // is there anything to parse?
543
    if (
544 20
        \strpos($sql, ':') === false
545
        ||
546 20
        \count($params) === 0
547
    ) {
548 7
      return ['sql' => $sql, 'params' => $params];
549
    }
550
551 16
    $offset = null;
552 16
    $replacement = null;
553 16
    foreach ($params as $name => $param) {
554
555
      // use this only for named parameters
556 16
      if (\is_int($name)) {
557
        continue;
558
      }
559
560
      // add ":" if needed
561 16
      if (\strpos($name, ':') !== 0) {
562 6
        $nameTmp = ':' . $name;
563
      } else {
564 10
        $nameTmp = $name;
565
      }
566
567 16
      if ($offset === null) {
568 16
        $offset = \strpos($sql, $nameTmp);
569
      } else {
570 15
        $offset = \strpos($sql, $nameTmp, $offset + \strlen((string)$replacement));
571
      }
572
573 16
      if ($offset === false) {
574 3
        continue;
575
      }
576
577 16
      $replacement = $this->secure($param);
578
579 16
      unset($params[$name]);
580
581 16
      $sql = \substr_replace($sql, $replacement, $offset, \strlen($nameTmp));
582
    }
583
584 16
    return ['sql' => $sql, 'params' => $params];
585
  }
586
587
  /**
588
   * Gets the number of affected rows in a previous MySQL operation.
589
   *
590
   * @return int
591
   */
592 28
  public function affected_rows(): int
593
  {
594
    if (
595 28
        $this->mysqli_link
596
        &&
597 28
        $this->mysqli_link instanceof \mysqli
598
    ) {
599 28
      return \mysqli_affected_rows($this->mysqli_link);
600
    }
601
602
    return (int)$this->affected_rows;
603
  }
604
605
  /**
606
   * Begins a transaction, by turning off auto commit.
607
   *
608
   * @return bool <p>This will return true or false indicating success of transaction</p>
609
   */
610 18
  public function beginTransaction(): bool
611
  {
612 18
    if ($this->in_transaction === true) {
613 6
      $this->debug->displayError('Error: mysql server already in transaction!', false);
614
615 6
      return false;
616
    }
617
618 18
    $this->clearErrors(); // needed for "$this->endTransaction()"
619 18
    $this->in_transaction = true;
620
621 18
    if ($this->isDoctrinePDOConnection() === true) {
622
      $this->doctrine_connection->setAutoCommit(false);
623
      $this->doctrine_connection->beginTransaction();
624
625
      if ($this->doctrine_connection->isTransactionActive() === true) {
626
        $return = true;
627
      } else {
628
        $return = false;
629
      }
630
    } else {
631 18
      $return = \mysqli_autocommit($this->mysqli_link, false);
632
    }
633
634 18
    if ($return === false) {
635
      $this->in_transaction = false;
636
    }
637
638 18
    return $return;
639
  }
640
641
  /**
642
   * Clear the errors in "_debug->_errors".
643
   *
644
   * @return bool
645
   */
646 18
  public function clearErrors(): bool
647
  {
648 18
    return $this->debug->clearErrors();
649
  }
650
651
  /**
652
   * Closes a previously opened database connection.
653
   *
654
   * @return bool
655
   *              Will return "true", if the connection was closed,
656
   *              otherwise (e.g. if the connection was already closed) "false".
657
   */
658 6
  public function close(): bool
659
  {
660 6
    $this->connected = false;
661
662 6
    if ($this->doctrine_connection) {
663
664
      $connectedBefore = $this->doctrine_connection->isConnected();
665
666
      $this->doctrine_connection->close();
667
668
      $this->mysqli_link = null;
669
670
      if ($connectedBefore === true) {
671
        return !$this->doctrine_connection->isConnected();
672
      }
673
674
      return false;
675
    }
676
677
    if (
678 6
        $this->mysqli_link
679
        &&
680 6
        $this->mysqli_link instanceof \mysqli
681
    ) {
682 6
      $result = \mysqli_close($this->mysqli_link);
683 6
      $this->mysqli_link = null;
684
685 6
      return $result;
686
    }
687
688 3
    $this->mysqli_link = null;
689
690 3
    return false;
691
  }
692
693
  /**
694
   * Commits the current transaction and end the transaction.
695
   *
696
   * @return bool <p>Boolean true on success, false otherwise.</p>
697
   */
698 9 View Code Duplication
  public function commit(): bool
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...
699
  {
700 9
    if ($this->in_transaction === false) {
701
      $this->debug->displayError('Error: mysql server is not in transaction!', false);
702
703
      return false;
704
    }
705
706 9
    if ($this->isDoctrinePDOConnection() === true) {
707
      $this->doctrine_connection->commit();
708
      $this->doctrine_connection->setAutoCommit(true);
709
710
      if ($this->doctrine_connection->isAutoCommit() === true) {
711
        $return = true;
712
      } else {
713
        $return = false;
714
      }
715
    } else {
716 9
      $return = \mysqli_commit($this->mysqli_link);
717 9
      \mysqli_autocommit($this->mysqli_link, true);
718
    }
719
720 9
    $this->in_transaction = false;
721
722 9
    return $return;
723
  }
724
725
  /**
726
   * Open a new connection to the MySQL server.
727
   *
728
   * @return bool
729
   *
730
   * @throws DBConnectException
731
   */
732 20
  public function connect(): bool
733
  {
734 20
    if ($this->isReady()) {
735 3
      return true;
736
    }
737
738 20
    if ($this->doctrine_connection) {
739
      $this->doctrine_connection->connect();
740
741
      $doctrineWrappedConnection = $this->doctrine_connection->getWrappedConnection();
742
743 View Code Duplication
      if ($this->isDoctrineMySQLiConnection() === true) {
744
        /* @var $doctrineWrappedConnection \Doctrine\DBAL\Driver\Mysqli\MysqliConnection */
745
746
        $this->mysqli_link = $doctrineWrappedConnection->getWrappedResourceHandle();
747
748
        $this->connected = $this->doctrine_connection->isConnected();
749
750
        if (!$this->connected) {
751
          $error = 'Error connecting to mysql server: ' . $this->doctrine_connection->errorInfo();
752
          $this->debug->displayError($error, false);
753
          throw new DBConnectException($error, 101);
754
        }
755
756
        $this->set_charset($this->charset);
757
758
        return $this->isReady();
759
      }
760
761 View Code Duplication
      if ($this->isDoctrinePDOConnection() === true) {
762
763
        $this->mysqli_link = null;
764
765
        $this->connected = $this->doctrine_connection->isConnected();
766
767
        if (!$this->connected) {
768
          $error = 'Error connecting to mysql server: ' . $this->doctrine_connection->errorInfo();
769
          $this->debug->displayError($error, false);
770
          throw new DBConnectException($error, 101);
771
        }
772
773
        $this->set_charset($this->charset);
774
775
        return $this->isReady();
776
      }
777
    }
778
779 20
    $flags = null;
780
781 20
    \mysqli_report(MYSQLI_REPORT_STRICT);
782
    try {
783 20
      $this->mysqli_link = \mysqli_init();
0 ignored issues
show
Documentation Bug introduced by
It seems like \mysqli_init() of type object<mysql> is incompatible with the declared type object<mysqli>|null of property $mysqli_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...
784
785 20
      if (Helper::isMysqlndIsUsed() === true) {
786 20
        \mysqli_options($this->mysqli_link, MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
787
      }
788
789 20
      if ($this->ssl === true) {
790
791
        if (empty($this->clientcert)) {
792
          throw new DBConnectException('Error connecting to mysql server: clientcert not defined');
793
        }
794
795
        if (empty($this->clientkey)) {
796
          throw new DBConnectException('Error connecting to mysql server: clientkey not defined');
797
        }
798
799
        if (empty($this->cacert)) {
800
          throw new DBConnectException('Error connecting to mysql server: cacert not defined');
801
        }
802
803
        \mysqli_options($this->mysqli_link, MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, true);
804
805
        /** @noinspection PhpParamsInspection */
806
        \mysqli_ssl_set(
807
            $this->mysqli_link,
808
            $this->clientkey,
809
            $this->clientcert,
810
            $this->cacert,
811
            null,
812
            null
813
        );
814
815
        $flags = MYSQLI_CLIENT_SSL;
816
      }
817
818
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
819 20
      $this->connected = @\mysqli_real_connect(
820 20
          $this->mysqli_link,
821 20
          $this->hostname,
822 20
          $this->username,
823 20
          $this->password,
824 20
          $this->database,
825 20
          $this->port,
826 20
          $this->socket,
827 20
          (int)$flags
828
      );
829
830 9
    } catch (\Exception $e) {
831 9
      $error = 'Error connecting to mysql server: ' . $e->getMessage();
832 9
      $this->debug->displayError($error, false);
833 9
      throw new DBConnectException($error, 100, $e);
834
    }
835 11
    \mysqli_report(MYSQLI_REPORT_OFF);
836
837 11
    $errno = \mysqli_connect_errno();
838 11
    if (!$this->connected || $errno) {
839
      $error = 'Error connecting to mysql server: ' . \mysqli_connect_error() . ' (' . $errno . ')';
840
      $this->debug->displayError($error, false);
841
      throw new DBConnectException($error, 101);
842
    }
843
844 11
    $this->set_charset($this->charset);
845
846 11
    return $this->isReady();
847
  }
848
849
  /**
850
   * Execute a "delete"-query.
851
   *
852
   * @param string       $table
853
   * @param string|array $where
854
   * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
855
   *
856
   * @return false|int <p>false on error</p>
857
   *
858
   * @throws QueryException
859
   */
860 4 View Code Duplication
  public function delete(string $table, $where, string $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...
861
  {
862
    // init
863 4
    $table = \trim($table);
864
865 4
    if ($table === '') {
866 3
      $this->debug->displayError('Invalid table name, table name in empty.', false);
867
868 3
      return false;
869
    }
870
871 4
    if (\is_string($where)) {
872 3
      $WHERE = $this->escape($where, false);
873 4
    } elseif (\is_array($where)) {
874 4
      $WHERE = $this->_parseArrayPair($where, 'AND');
875
    } else {
876 3
      $WHERE = '';
877
    }
878
879 4
    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...
880
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
881
    }
882
883 4
    $sql = 'DELETE FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE)";
884
885 4
    return $this->query($sql);
886
  }
887
888
  /**
889
   * Ends a transaction and commits if no errors, then ends autocommit.
890
   *
891
   * @return bool <p>This will return true or false indicating success of transactions.</p>
892
   */
893 12
  public function endTransaction(): bool
894
  {
895 12
    if ($this->in_transaction === false) {
896
      $this->debug->displayError('Error: mysql server is not in transaction!', false);
897
898
      return false;
899
    }
900
901 12
    if (!$this->errors()) {
902 3
      $return = $this->commit();
903
    } else {
904 9
      $this->rollback();
905 9
      $return = false;
906
    }
907
908 12
    if ($this->isDoctrinePDOConnection() === true) {
909
      $this->doctrine_connection->setAutoCommit(true);
910
911
      if ($this->doctrine_connection->isAutoCommit() === true) {
912
        $return = true;
913
      } else {
914
        $return = false;
915
      }
916
    } else {
917 12
      \mysqli_autocommit($this->mysqli_link, true);
918
    }
919
920 12
    $this->in_transaction = false;
921
922 12
    return $return;
923
  }
924
925
  /**
926
   * Get all errors from "$this->errors".
927
   *
928
   * @return array|false <p>false === on errors</p>
929
   */
930 12
  public function errors()
931
  {
932 12
    $errors = $this->debug->getErrors();
933
934 12
    return \count($errors) > 0 ? $errors : false;
935
  }
936
937
  /**
938
   * Escape: Use "mysqli_real_escape_string" and clean non UTF-8 chars + some extra optional stuff.
939
   *
940
   * @param mixed     $var           boolean: convert into "integer"<br />
941
   *                                 int: int (don't change it)<br />
942
   *                                 float: float (don't change it)<br />
943
   *                                 null: null (don't change it)<br />
944
   *                                 array: run escape() for every key => value<br />
945
   *                                 string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
946
   * @param bool      $stripe_non_utf8
947
   * @param bool      $html_entity_decode
948
   * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
949
   *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
950
   *                                 <strong>null</strong> => Convert the array into null, every time.
951
   *
952
   * @return mixed
953
   */
954 119
  public function escape($var = '', bool $stripe_non_utf8 = true, bool $html_entity_decode = false, $convert_array = false)
955
  {
956
    // [empty]
957 119
    if ($var === '') {
958 6
      return '';
959
    }
960
961
    // ''
962 119
    if ($var === "''") {
963
      return "''";
964
    }
965
966
    // check the type
967 119
    $type = \gettype($var);
968
969 119
    if ($type === 'object') {
970 9
      if ($var instanceof \DateTime) {
971 9
        $var = $var->format('Y-m-d H:i:s');
972 9
        $type = 'string';
973 6
      } elseif (\method_exists($var, '__toString')) {
974 6
        $var = (string)$var;
975 6
        $type = 'string';
976
      }
977
    }
978
979
    switch ($type) {
980 119
      case 'boolean':
981 9
        $var = (int)$var;
982 9
        break;
983
984 119
      case 'double':
985 119
      case 'integer':
986 70
        break;
987
988 113
      case 'string':
989 113
        if ($stripe_non_utf8 === true) {
990 23
          $var = UTF8::cleanup($var);
991
        }
992
993 113
        if ($html_entity_decode === true) {
994 3
          $var = UTF8::html_entity_decode($var);
995
        }
996
997 113
        $var = \get_magic_quotes_gpc() ? \stripslashes($var) : $var;
998
999
        if (
1000 113
            $this->mysqli_link
1001
            &&
1002 113
            $this->mysqli_link instanceof \mysqli
1003
        ) {
1004 113
          $var = \mysqli_real_escape_string($this->mysqli_link, $var);
1005
        } else if ($this->isDoctrinePDOConnection() === true) {
1006
          $var = $this->getDoctrinePDOConnection()->quote($var);
1007
          $var = \substr($var, 1, -1);
1008
        }
1009
1010 113
        break;
1011
1012 9
      case 'array':
1013 6
        if ($convert_array === null) {
1014
1015 3
          if ($this->convert_null_to_empty_string === true) {
1016
            $var = "''";
1017
          } else {
1018 3
            $var = 'NULL';
1019
          }
1020
1021
        } else {
1022
1023 6
          $varCleaned = [];
1024 6
          foreach ((array)$var as $key => $value) {
1025
1026 6
            $key = $this->escape($key, $stripe_non_utf8, $html_entity_decode);
1027 6
            $value = $this->escape($value, $stripe_non_utf8, $html_entity_decode);
1028
1029
            /** @noinspection OffsetOperationsInspection */
1030 6
            $varCleaned[$key] = $value;
1031
          }
1032
1033 6 View Code Duplication
          if ($convert_array === true) {
1034 3
            $varCleaned = \implode(',', $varCleaned);
1035
1036 3
            $var = $varCleaned;
1037
          } else {
1038 6
            $var = $varCleaned;
1039
          }
1040
1041
        }
1042 6
        break;
1043
1044 9
      case 'NULL':
1045 6
        if ($this->convert_null_to_empty_string === true) {
1046
          $var = "''";
1047
        } else {
1048 6
          $var = 'NULL';
1049
        }
1050 6
        break;
1051
1052
      default:
1053 6
        throw new \InvalidArgumentException(sprintf('Not supported value "%s" of type %s.', print_r($var, true), $type));
1054
        break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1055
    }
1056
1057 119
    return $var;
1058
  }
1059
1060
  /**
1061
   * Execute select/insert/update/delete sql-queries.
1062
   *
1063
   * @param string  $query    <p>sql-query</p>
1064
   * @param bool    $useCache optional <p>use cache?</p>
1065
   * @param int     $cacheTTL optional <p>cache-ttl in seconds</p>
1066
   * @param DB|null $db       optional <p>the database connection</p>
1067
   *
1068
   * @return mixed "array" by "<b>SELECT</b>"-queries<br />
1069
   *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
1070
   *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1071
   *               "true" by e.g. "DROP"-queries<br />
1072
   *               "false" on error
1073
   *
1074
   * @throws QueryException
1075
   */
1076 9
  public static function execSQL(string $query, bool $useCache = false, int $cacheTTL = 3600, self $db = null)
1077
  {
1078
    // init
1079 9
    $cacheKey = null;
1080 9
    if (!$db) {
1081 9
      $db = self::getInstance();
1082
    }
1083
1084 9 View Code Duplication
    if ($useCache === true) {
1085 3
      $cache = new Cache(null, null, false, $useCache);
1086 3
      $cacheKey = 'sql-' . \md5($query);
1087
1088
      if (
1089 3
          $cache->getCacheIsReady() === true
1090
          &&
1091 3
          $cache->existsItem($cacheKey)
1092
      ) {
1093 3
        return $cache->getItem($cacheKey);
1094
      }
1095
1096
    } else {
1097 9
      $cache = false;
1098
    }
1099
1100 9
    $result = $db->query($query);
1101
1102 9
    if ($result instanceof Result) {
1103
1104 3
      $return = $result->fetchAllArray();
1105
1106
      // save into the cache
1107 View Code Duplication
      if (
1108 3
          $cacheKey !== null
1109
          &&
1110 3
          $useCache === true
1111
          &&
1112 3
          $cache instanceof Cache
1113
          &&
1114 3
          $cache->getCacheIsReady() === true
1115
      ) {
1116 3
        $cache->setItem($cacheKey, $return, $cacheTTL);
1117
      }
1118
1119
    } else {
1120 6
      $return = $result;
1121
    }
1122
1123 9
    return $return;
1124
  }
1125
1126
  /**
1127
   * Get all table-names via "SHOW TABLES".
1128
   *
1129
   * @return array
1130
   */
1131 3
  public function getAllTables(): array
1132
  {
1133 3
    $query = 'SHOW TABLES';
1134 3
    $result = $this->query($query);
1135
1136 3
    return $result->fetchAllArray();
1137
  }
1138
1139
  /**
1140
   * @return array
1141
   */
1142 8
  public function getConfig()
1143
  {
1144
    $config = [
1145 8
        'hostname'   => $this->hostname,
1146 8
        'username'   => $this->username,
1147 8
        'password'   => $this->password,
1148 8
        'port'       => $this->port,
1149 8
        'database'   => $this->database,
1150 8
        'socket'     => $this->socket,
1151 8
        'charset'    => $this->charset,
1152 8
        'cacert'     => $this->cacert,
1153 8
        'clientcert' => $this->clientcert,
1154 8
        'clientkey'  => $this->clientkey,
1155
    ];
1156
1157 8
    if ($this->doctrine_connection instanceof \Doctrine\DBAL\Connection) {
1158
      $config += $this->doctrine_connection->getParams();
1159
    }
1160
1161 8
    return $config;
1162
  }
1163
1164
  /**
1165
   * @return Debug
1166
   */
1167 9
  public function getDebugger(): Debug
1168
  {
1169 9
    return $this->debug;
1170
  }
1171
1172
  /**
1173
   * @return null|\Doctrine\DBAL\Connection|null
1174
   */
1175 2
  public function getDoctrineConnection()
1176
  {
1177 2
    return $this->doctrine_connection;
1178
  }
1179
1180
  /**
1181
   * @return false|\Doctrine\DBAL\Driver\Connection
1182
   */
1183 View Code Duplication
  private function getDoctrinePDOConnection()
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...
1184
  {
1185
    if ($this->doctrine_connection) {
1186
      $doctrineWrappedConnection = $this->doctrine_connection->getWrappedConnection();
1187
      if ($doctrineWrappedConnection instanceof \Doctrine\DBAL\Driver\PDOConnection) {
1188
        return $doctrineWrappedConnection;
1189
      }
1190
    }
1191
1192
    return false;
1193
  }
1194
1195
  /**
1196
   * Get errors from "$this->errors".
1197
   *
1198
   * @return array
1199
   */
1200 3
  public function getErrors(): array
1201
  {
1202 3
    return $this->debug->getErrors();
1203
  }
1204
1205
  /**
1206
   * @param string $hostname             <p>Hostname of the mysql server</p>
1207
   * @param string $username             <p>Username for the mysql connection</p>
1208
   * @param string $password             <p>Password for the mysql connection</p>
1209
   * @param string $database             <p>Database for the mysql connection</p>
1210
   * @param int    $port                 <p>default is (int)3306</p>
1211
   * @param string $charset              <p>default is 'utf8' or 'utf8mb4' (if supported)</p>
1212
   * @param bool   $exit_on_error        <p>Throw a 'Exception' when a query failed, otherwise it will return 'false'.
1213
   *                                     Use false to disable it.</p>
1214
   * @param bool   $echo_on_error        <p>Echo the error if "checkForDev()" returns true.
1215
   *                                     Use false to disable it.</p>
1216
   * @param string $logger_class_name
1217
   * @param string $logger_level         <p>'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'</p>
1218
   * @param array  $extra_config         <p>
1219
   *                                     're_connect'    => bool<br>
1220
   *                                     'session_to_db' => bool<br>
1221
   *                                     'doctrine'      => \Doctrine\DBAL\Connection<br>
1222
   *                                     'socket'        => 'string (path)'<br>
1223
   *                                     'ssl'           => bool<br>
1224
   *                                     'clientkey'     => 'string (path)'<br>
1225
   *                                     'clientcert'    => 'string (path)'<br>
1226
   *                                     'cacert'        => 'string (path)'<br>
1227
   *                                     </p>
1228
   *
1229
   * @return self
1230
   */
1231 231
  public static function getInstance(
1232
      string $hostname = '',
1233
      string $username = '',
1234
      string $password = '',
1235
      string $database = '',
1236
      $port = 3306,
1237
      string $charset = 'utf8',
1238
      bool $exit_on_error = true,
1239
      bool $echo_on_error = true,
1240
      string $logger_class_name = '',
1241
      string $logger_level = '',
1242
      array $extra_config = []
1243
  ): self
1244
  {
1245
    /**
1246
     * @var $instance self[]
1247
     */
1248 231
    static $instance = [];
1249
1250
    /**
1251
     * @var $firstInstance self
1252
     */
1253 231
    static $firstInstance = null;
1254
1255
    // fallback
1256 231
    if (!$charset) {
1257 121
      $charset = 'utf8';
1258
    }
1259
1260
    if (
1261 231
        '' . $hostname . $username . $password . $database . $port . $charset == '' . $port . $charset
1262
        &&
1263 231
        null !== $firstInstance
1264
    ) {
1265 143
      if (isset($extra_config['re_connect']) && $extra_config['re_connect'] === true) {
1266
        $firstInstance->reconnect(true);
1267
      }
1268
1269 143
      return $firstInstance;
1270
    }
1271
1272 150
    $extra_config_string = '';
1273 150
    foreach ($extra_config as $extra_config_key => $extra_config_value) {
1274
      if (\is_object($extra_config_value)) {
1275
        $extra_config_value_tmp = \spl_object_hash($extra_config_value);
1276
      } else {
1277
        $extra_config_value_tmp = (string)$extra_config_value;
1278
      }
1279
      $extra_config_string .= $extra_config_key . $extra_config_value_tmp;
1280
    }
1281
1282 150
    $connection = \md5(
1283 150
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . $extra_config_string
1284
    );
1285
1286 150
    if (!isset($instance[$connection])) {
1287 23
      $instance[$connection] = new self(
1288 23
          $hostname,
1289 23
          $username,
1290 23
          $password,
1291 23
          $database,
1292 23
          $port,
1293 23
          $charset,
1294 23
          $exit_on_error,
1295 23
          $echo_on_error,
1296 23
          $logger_class_name,
1297 23
          $logger_level,
1298 23
          $extra_config
1299
      );
1300
1301 5
      if (null === $firstInstance) {
1302 1
        $firstInstance = $instance[$connection];
1303
      }
1304
    }
1305
1306 138
    if (isset($extra_config['re_connect']) && $extra_config['re_connect'] === true) {
1307
      $instance[$connection]->reconnect(true);
1308
    }
1309
1310 138
    return $instance[$connection];
1311
  }
1312
1313
  /**
1314
   * @param \Doctrine\DBAL\Connection $doctrine
1315
   * @param string                    $charset       <p>default is 'utf8' or 'utf8mb4' (if supported)</p>
1316
   * @param bool                      $exit_on_error <p>Throw a 'Exception' when a query failed, otherwise it will
1317
   *                                                 return 'false'. Use false to disable it.</p>
1318
   * @param bool                      $echo_on_error <p>Echo the error if "checkForDev()" returns true.
1319
   *                                                 Use false to disable it.</p>
1320
   * @param string                    $logger_class_name
1321
   * @param string                    $logger_level  <p>'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'</p>
1322
   * @param array                     $extra_config  <p>
1323
   *                                                 're_connect'    => bool<br>
1324
   *                                                 'session_to_db' => bool<br>
1325
   *                                                 'doctrine'      => \Doctrine\DBAL\Connection<br>
1326
   *                                                 'socket'        => 'string (path)'<br>
1327
   *                                                 'ssl'           => bool<br>
1328
   *                                                 'clientkey'     => 'string (path)'<br>
1329
   *                                                 'clientcert'    => 'string (path)'<br>
1330
   *                                                 'cacert'        => 'string (path)'<br>
1331
   *                                                 </p>
1332
   *
1333
   * @return self
1334
   */
1335 55
  public static function getInstanceDoctrineHelper(
1336
      \Doctrine\DBAL\Connection $doctrine,
1337
      string $charset = 'utf8',
1338
      bool $exit_on_error = true,
1339
      bool $echo_on_error = true,
1340
      string $logger_class_name = '',
1341
      string $logger_level = '',
1342
      array $extra_config = []
1343
  ): self
1344
  {
1345 55
    $extra_config['doctrine'] = $doctrine;
1346
1347 55
    return self::getInstance(
1348 55
        '',
1349 55
        '',
1350 55
        '',
1351 55
        '',
1352 55
        3306,
1353 55
        $charset,
1354 55
        $exit_on_error,
1355 55
        $echo_on_error,
1356 55
        $logger_class_name,
1357 55
        $logger_level,
1358 55
        $extra_config
1359
    );
1360
  }
1361
1362
  /**
1363
   * Get the mysqli-link (link identifier returned by mysqli-connect).
1364
   *
1365
   * @return null|\mysqli
1366
   */
1367 53
  public function getLink()
1368
  {
1369 53
    return $this->mysqli_link;
1370
  }
1371
1372
  /**
1373
   * Get the current charset.
1374
   *
1375
   * @return string
1376
   */
1377 3
  public function get_charset(): string
1378
  {
1379 3
    return $this->charset;
1380
  }
1381
1382
  /**
1383
   * Check if we are in a transaction.
1384
   *
1385
   * @return bool
1386
   */
1387
  public function inTransaction(): bool
1388
  {
1389
    return $this->in_transaction;
1390
  }
1391
1392
  /**
1393
   * Execute a "insert"-query.
1394
   *
1395
   * @param string      $table
1396
   * @param array       $data
1397
   * @param string|null $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1398
   *
1399
   * @return false|int <p>false on error</p>
1400
   *
1401
   * @throws QueryException
1402
   */
1403 74
  public function insert(string $table, array $data = [], string $databaseName = null)
1404
  {
1405
    // init
1406 74
    $table = \trim($table);
1407
1408 74
    if ($table === '') {
1409 6
      $this->debug->displayError('Invalid table name, table name in empty.', false);
1410
1411 6
      return false;
1412
    }
1413
1414 71
    if (\count($data) === 0) {
1415 9
      $this->debug->displayError('Invalid data for INSERT, data is empty.', false);
1416
1417 9
      return false;
1418
    }
1419
1420 65
    $SET = $this->_parseArrayPair($data);
1421
1422 65
    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...
1423
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
1424
    }
1425
1426 65
    $sql = 'INSERT INTO ' . $databaseName . $this->quote_string($table) . " SET $SET";
1427
1428 65
    return $this->query($sql);
1429
  }
1430
1431
  /**
1432
   * Returns the auto generated id used in the last query.
1433
   *
1434
   * @return int|string
1435
   */
1436 104
  public function insert_id()
1437
  {
1438 104
    return \mysqli_insert_id($this->mysqli_link);
1439
  }
1440
1441
  /**
1442
   * @return bool
1443
   */
1444
  public function isDoctrineMySQLiConnection(): bool
1445
  {
1446
    if ($this->doctrine_connection) {
1447
      $doctrineWrappedConnection = $this->doctrine_connection->getWrappedConnection();
1448
      if ($doctrineWrappedConnection instanceof \Doctrine\DBAL\Driver\Mysqli\MysqliConnection) {
1449
        return true;
1450
      }
1451
    }
1452
1453
    return false;
1454
  }
1455
1456
  /**
1457
   * @return bool
1458
   */
1459 116 View Code Duplication
  public function isDoctrinePDOConnection(): bool
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...
1460
  {
1461 116
    if ($this->doctrine_connection) {
1462
      $doctrineWrappedConnection = $this->doctrine_connection->getWrappedConnection();
1463
      if ($doctrineWrappedConnection instanceof \Doctrine\DBAL\Driver\PDOConnection) {
1464
        return true;
1465
      }
1466
    }
1467
1468 116
    return false;
1469
  }
1470
1471
  /**
1472
   * Check if db-connection is ready.
1473
   *
1474
   * @return bool
1475
   */
1476 184
  public function isReady(): bool
1477
  {
1478 184
    return $this->connected ? true : false;
1479
  }
1480
1481
  /**
1482
   * Get the last sql-error.
1483
   *
1484
   * @return string|false <p>false === there was no error</p>
1485
   */
1486 3
  public function lastError()
1487
  {
1488 3
    $errors = $this->debug->getErrors();
1489
1490 3
    return \count($errors) > 0 ? end($errors) : false;
1491
  }
1492
1493
  /**
1494
   * Execute a sql-multi-query.
1495
   *
1496
   * @param string $sql
1497
   *
1498
   * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
1499
   *                        "boolean" by only "<b>INSERT</b>"-queries<br />
1500
   *                        "boolean" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1501
   *                        "boolean" by only by e.g. "DROP"-queries<br />
1502
   *
1503
   * @throws QueryException
1504
   */
1505 3
  public function multi_query(string $sql)
1506
  {
1507 3
    if (!$this->isReady()) {
1508
      return false;
1509
    }
1510
1511 3 View Code Duplication
    if (!$sql || $sql === '') {
1512 3
      $this->debug->displayError('Can not execute an empty query.', false);
1513
1514 3
      return false;
1515
    }
1516
1517 3
    if ($this->isDoctrinePDOConnection() === true) {
1518
1519
      $query_start_time = microtime(true);
1520
      $queryException = null;
1521
      $query_result_doctrine = false;
1522
1523
      try {
1524
        $query_result_doctrine = $this->doctrine_connection->prepare($sql);
1525
        $resultTmp = $query_result_doctrine->execute();
1526
        $mysqli_field_count = $query_result_doctrine->columnCount();
1527
      } catch (\Exception $e) {
1528
        $resultTmp = false;
1529
        $mysqli_field_count = null;
1530
1531
        $queryException = $e;
1532
      }
1533
1534
      $query_duration = microtime(true) - $query_start_time;
1535
1536
      $this->debug->logQuery($sql, $query_duration, 0);
1537
1538
      $returnTheResult = false;
1539
      $result = [];
1540
1541
      if ($resultTmp) {
1542
1543
        if ($mysqli_field_count) {
1544
1545
          if (
1546
              $query_result_doctrine
1547
              &&
1548
              $query_result_doctrine instanceof \Doctrine\DBAL\Statement
1549
          ) {
1550
            $result = $query_result_doctrine;
1551
          }
1552
1553
        } else {
1554
          $result = $resultTmp;
1555
        }
1556
1557
        if (
1558
            $result instanceof \Doctrine\DBAL\Statement
1559
            &&
1560
            $result->columnCount() > 0
1561
        ) {
1562
          $returnTheResult = true;
1563
1564
          // return query result object
1565
          $result = [new Result($sql, $result)];
1566
        } else {
1567
          $result = [$result];
1568
        }
1569
1570
      } else {
1571
1572
        // log the error query
1573
        $this->debug->logQuery($sql, $query_duration, 0, true);
1574
1575
        if (
1576
            isset($queryException)
1577
            &&
1578
            $queryException instanceof \Doctrine\DBAL\Query\QueryException
1579
        ) {
1580
          return $this->queryErrorHandling($queryException->getMessage(), $queryException->getCode(), $sql, false, true);
1581
        }
1582
      }
1583
1584
    } else {
1585
1586 3
      $query_start_time = microtime(true);
1587 3
      $resultTmp = \mysqli_multi_query($this->mysqli_link, $sql);
1588 3
      $query_duration = microtime(true) - $query_start_time;
1589
1590 3
      $this->debug->logQuery($sql, $query_duration, 0);
1591
1592 3
      $returnTheResult = false;
1593 3
      $result = [];
1594
1595 3
      if ($resultTmp) {
1596
        do {
1597
1598 3
          $resultTmpInner = \mysqli_store_result($this->mysqli_link);
1599
1600 3
          if ($resultTmpInner instanceof \mysqli_result) {
1601
1602 3
            $returnTheResult = true;
1603 3
            $result[] = new Result($sql, $resultTmpInner);
1604
1605
          } else if (
1606 3
              $resultTmpInner === true
1607
              ||
1608 3
              !\mysqli_errno($this->mysqli_link)
1609
          ) {
1610
1611 3
            $result[] = true;
1612
1613
          } else {
1614
1615
            $result[] = false;
1616
1617
          }
1618
1619 3
        } while (\mysqli_more_results($this->mysqli_link) === true ? \mysqli_next_result($this->mysqli_link) : false);
1620
1621
      } else {
1622
1623
        // log the error query
1624 3
        $this->debug->logQuery($sql, $query_duration, 0, true);
1625
1626 3
        return $this->queryErrorHandling(\mysqli_error($this->mysqli_link), \mysqli_errno($this->mysqli_link), $sql, false, true);
1627
      }
1628
1629
    }
1630
1631
    // return the result only if there was a "SELECT"-query
1632 3
    if ($returnTheResult === true) {
1633 3
      return $result;
1634
    }
1635
1636
    if (
1637 3
        \count($result) > 0
1638
        &&
1639 3
        \in_array(false, $result, true) === false
1640
    ) {
1641 3
      return true;
1642
    }
1643
1644
    return false;
1645
  }
1646
1647
  /**
1648
   * Count number of rows found matching a specific query.
1649
   *
1650
   * @param string $query
1651
   *
1652
   * @return int
1653
   */
1654 3
  public function num_rows(string $query): int
1655
  {
1656 3
    $check = $this->query($query);
1657
1658
    if (
1659 3
        $check === false
1660
        ||
1661 3
        !$check instanceof Result
1662
    ) {
1663
      return 0;
1664
    }
1665
1666 3
    return $check->num_rows;
1667
  }
1668
1669
  /**
1670
   * Pings a server connection, or tries to reconnect
1671
   * if the connection has gone down.
1672
   *
1673
   * @return bool
1674
   */
1675 9
  public function ping(): bool
1676
  {
1677 9
    if ($this->connected === false) {
1678 3
      return false;
1679
    }
1680
1681 6
    if ($this->isDoctrinePDOConnection() === true) {
1682
      return $this->doctrine_connection->ping();
1683
    }
1684
1685
    if (
1686 6
        $this->mysqli_link
1687
        &&
1688 6
        $this->mysqli_link instanceof \mysqli
1689
    ) {
1690 6
      return \mysqli_ping($this->mysqli_link);
1691
    }
1692
1693
    return false;
1694
  }
1695
1696
  /**
1697
   * Get a new "Prepare"-Object for your sql-query.
1698
   *
1699
   * @param string $query
1700
   *
1701
   * @return Prepare
1702
   */
1703 2
  public function prepare(string $query): Prepare
1704
  {
1705 2
    return new Prepare($this, $query);
1706
  }
1707
1708
  /**
1709
   * Execute a sql-query and return the result-array for select-statements.
1710
   *
1711
   * @param string $query
1712
   *
1713
   * @return mixed
1714
   * @deprecated
1715
   * @throws \Exception
1716
   */
1717 3
  public static function qry(string $query)
0 ignored issues
show
Unused Code introduced by
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1718
  {
1719 3
    $db = self::getInstance();
1720
1721 3
    $args = \func_get_args();
1722
    /** @noinspection SuspiciousAssignmentsInspection */
1723 3
    $query = \array_shift($args);
1724 3
    $query = \str_replace('?', '%s', $query);
1725 3
    $args = \array_map(
1726
        [
1727 3
            $db,
1728 3
            'escape',
1729
        ],
1730 3
        $args
1731
    );
1732 3
    \array_unshift($args, $query);
1733 3
    $query = \sprintf(...$args);
1734 3
    $result = $db->query($query);
1735
1736 3
    if ($result instanceof Result) {
1737 3
      return $result->fetchAllArray();
1738
    }
1739
1740 3
    return $result;
1741
  }
1742
1743
  /**
1744
   * Execute a sql-query.
1745
   *
1746
   * @param string        $sql            <p>The sql query-string.</p>
1747
   *
1748
   * @param array|boolean $params         <p>
1749
   *                                      "array" of sql-query-parameters<br/>
1750
   *                                      "false" if you don't need any parameter (default)<br/>
1751
   *                                      </p>
1752
   *
1753
   * @return bool|int|Result              <p>
1754
   *                                      "Result" by "<b>SELECT</b>"-queries<br />
1755
   *                                      "int|string" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
1756
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1757
   *                                      "true" by e.g. "DROP"-queries<br />
1758
   *                                      "false" on error
1759
   *                                      </p>
1760
   *
1761
   * @throws QueryException
1762
   */
1763 164
  public function query(string $sql = '', $params = false)
1764
  {
1765 164
    if (!$this->isReady()) {
1766
      return false;
1767
    }
1768
1769 164 View Code Duplication
    if (!$sql || $sql === '') {
1770 12
      $this->debug->displayError('Can not execute an empty query.', false);
1771
1772 12
      return false;
1773
    }
1774
1775
    if (
1776 158
        $params !== false
1777
        &&
1778 158
        \is_array($params)
1779
        &&
1780 158
        \count($params) > 0
1781
    ) {
1782 17
      $parseQueryParams = $this->_parseQueryParams($sql, $params);
1783 17
      $parseQueryParamsByName = $this->_parseQueryParamsByName($parseQueryParams['sql'], $parseQueryParams['params']);
1784 17
      $sql = $parseQueryParamsByName['sql'];
1785
    }
1786
1787
    // DEBUG
1788
    // var_dump($params);
1789
    // echo $sql . "\n";
1790
1791 158
    $query_start_time = microtime(true);
1792 158
    $queryException = null;
1793 158
    $query_result_doctrine = false;
1794
1795 158
    if ($this->doctrine_connection) {
1796
1797
      try {
1798
        $query_result_doctrine = $this->doctrine_connection->prepare($sql);
1799
        $query_result = $query_result_doctrine->execute();
1800
        $mysqli_field_count = $query_result_doctrine->columnCount();
1801
      } catch (\Exception $e) {
1802
        $query_result = false;
1803
        $mysqli_field_count = null;
1804
1805
        $queryException = $e;
1806
      }
1807
1808
    } else {
1809
1810 158
      $query_result = \mysqli_real_query($this->mysqli_link, $sql);
1811 158
      $mysqli_field_count = \mysqli_field_count($this->mysqli_link);
1812
1813
    }
1814
1815 158
    $query_duration = microtime(true) - $query_start_time;
1816
1817 158
    $this->query_count++;
1818
1819 158
    if ($mysqli_field_count) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $mysqli_field_count of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. 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 integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1820
1821 116
      if ($this->doctrine_connection) {
1822
1823
        $result = false;
1824
        if (
1825
            $query_result_doctrine
1826
            &&
1827
            $query_result_doctrine instanceof \Doctrine\DBAL\Statement
1828
        ) {
1829
          $result = $query_result_doctrine;
1830
        }
1831
1832
      } else {
1833
1834 116
        $result = \mysqli_store_result($this->mysqli_link);
1835
1836
      }
1837
    } else {
1838 114
      $result = $query_result;
1839
    }
1840
1841
    if (
1842 158
        $result instanceof \Doctrine\DBAL\Statement
1843
        &&
1844 158
        $result->columnCount() > 0
1845
    ) {
1846
1847
      // log the select query
1848
      $this->debug->logQuery($sql, $query_duration, $mysqli_field_count);
1849
1850
      // return query result object
1851
      return new Result($sql, $result);
1852
    }
1853
1854 158
    if ($result instanceof \mysqli_result) {
1855
1856
      // log the select query
1857 113
      $this->debug->logQuery($sql, $query_duration, $mysqli_field_count);
1858
1859
      // return query result object
1860 113
      return new Result($sql, $result);
1861
    }
1862
1863 120
    if ($query_result === true) {
1864
1865
      // "INSERT" || "REPLACE"
1866 111
      if (preg_match('/^\s*?(?:INSERT|REPLACE)\s+/i', $sql)) {
1867
1868 104
        if ($this->isDoctrinePDOConnection() === true) {
1869
          $insert_id = $this->doctrine_connection->lastInsertId();
1870
        } else {
1871 104
          $insert_id = $this->insert_id();
1872
        }
1873
1874 104
        $this->debug->logQuery($sql, $query_duration, $insert_id);
1875
1876 104
        return $insert_id;
1877
      }
1878
1879
      // "UPDATE" || "DELETE"
1880 54
      if (preg_match('/^\s*?(?:UPDATE|DELETE)\s+/i', $sql)) {
1881
1882 28
        if ($this->mysqli_link) {
1883 28
          $this->affected_rows = $this->affected_rows();
1884
        } else if ($query_result_doctrine) {
1885
          $this->affected_rows = $query_result_doctrine->rowCount();
1886
        }
1887
1888 28
        $this->debug->logQuery($sql, $query_duration, $this->affected_rows);
1889
1890 28
        return $this->affected_rows;
1891
      }
1892
1893
      // log the ? query
1894 27
      $this->debug->logQuery($sql, $query_duration, 0);
1895
1896 27
      return true;
1897
    }
1898
1899
    // log the error query
1900 33
    $this->debug->logQuery($sql, $query_duration, 0, true);
1901
1902 33
    if ($queryException) {
1903
      return $this->queryErrorHandling($queryException->getMessage(), $queryException->getCode(), $sql, $params);
1904
    }
1905
1906 33
    if ($this->mysqli_link) {
1907 33
      return $this->queryErrorHandling(\mysqli_error($this->mysqli_link), \mysqli_errno($this->mysqli_link), $sql, $params);
1908
    }
1909
1910
    return false;
1911
  }
1912
1913
  /**
1914
   * Error-handling for the sql-query.
1915
   *
1916
   * @param string     $errorMessage
1917
   * @param int        $errorNumber
1918
   * @param string     $sql
1919
   * @param array|bool $sqlParams <p>false if there wasn't any parameter</p>
1920
   * @param bool       $sqlMultiQuery
1921
   *
1922
   * @return mixed|false
1923
   *
1924
   * @throws QueryException
1925
   * @throws DBGoneAwayException
1926
   */
1927 39
  private function queryErrorHandling(string $errorMessage, int $errorNumber, string $sql, $sqlParams = false, bool $sqlMultiQuery = false)
1928
  {
1929
    if (
1930 39
        $errorMessage === 'DB server has gone away'
1931
        ||
1932 36
        $errorMessage === 'MySQL server has gone away'
1933
        ||
1934 39
        $errorNumber === 2006
1935
    ) {
1936 3
      static $RECONNECT_COUNTER;
1937
1938
      // exit if we have more then 3 "DB server has gone away"-errors
1939 3
      if ($RECONNECT_COUNTER > 3) {
1940
        $this->debug->mailToAdmin('DB-Fatal-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql, 5);
1941
        throw new DBGoneAwayException($errorMessage);
1942
      }
1943
1944 3
      $this->debug->mailToAdmin('DB-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql);
1945
1946
      // reconnect
1947 3
      $RECONNECT_COUNTER++;
1948 3
      $this->reconnect(true);
1949
1950
      // re-run the current (non multi) query
1951 3
      if ($sqlMultiQuery === false) {
1952 3
        return $this->query($sql, $sqlParams);
1953
      }
1954
1955
      return false;
1956
    }
1957
1958 36
    $this->debug->mailToAdmin('SQL-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql);
1959
1960 36
    $force_exception_after_error = null; // auto
1961 36
    if ($this->in_transaction === true) {
1962 12
      $force_exception_after_error = false;
1963
    }
1964
    // this query returned an error, we must display it (only for dev) !!!
1965
1966 36
    $this->debug->displayError($errorMessage . '(' . $errorNumber . ') ' . ' | ' . $sql, $force_exception_after_error);
1967
1968 36
    return false;
1969
  }
1970
1971
  /**
1972
   * Quote && Escape e.g. a table name string.
1973
   *
1974
   * @param mixed $str
1975
   *
1976
   * @return string
1977
   */
1978 86
  public function quote_string($str): string
1979
  {
1980 86
    $str = \str_replace(
1981 86
        '`',
1982 86
        '``',
1983 86
        \trim(
1984 86
            (string)$this->escape($str, false),
1985 86
            '`'
1986
        )
1987
    );
1988
1989 86
    return '`' . $str . '`';
1990
  }
1991
1992
  /**
1993
   * Reconnect to the MySQL-Server.
1994
   *
1995
   * @param bool $checkViaPing
1996
   *
1997
   * @return bool
1998
   */
1999 7
  public function reconnect(bool $checkViaPing = false): bool
2000
  {
2001 7
    $ping = false;
2002 7
    if ($checkViaPing === true) {
2003 6
      $ping = $this->ping();
2004
    }
2005
2006 7
    if ($ping === false) {
2007 7
      $this->connected = false;
2008 7
      $this->connect();
2009
    }
2010
2011 7
    return $this->isReady();
2012
  }
2013
2014
  /**
2015
   * Execute a "replace"-query.
2016
   *
2017
   * @param string      $table
2018
   * @param array       $data
2019
   * @param null|string $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
2020
   *
2021
   * @return false|int <p>false on error</p>
2022
   *
2023
   * @throws QueryException
2024
   */
2025 3
  public function replace(string $table, array $data = [], string $databaseName = null)
2026
  {
2027
    // init
2028 3
    $table = \trim($table);
2029
2030 3
    if ($table === '') {
2031 3
      $this->debug->displayError('Invalid table name, table name in empty.', false);
2032
2033 3
      return false;
2034
    }
2035
2036 3
    if (\count($data) === 0) {
2037 3
      $this->debug->displayError('Invalid data for REPLACE, data is empty.', false);
2038
2039 3
      return false;
2040
    }
2041
2042
    // extracting column names
2043 3
    $columns = \array_keys($data);
2044 3
    foreach ($columns as $k => $_key) {
2045
      /** @noinspection AlterInForeachInspection */
2046 3
      $columns[$k] = $this->quote_string($_key);
2047
    }
2048
2049 3
    $columns = \implode(',', $columns);
2050
2051
    // extracting values
2052 3
    foreach ($data as $k => $_value) {
2053
      /** @noinspection AlterInForeachInspection */
2054 3
      $data[$k] = $this->secure($_value);
2055
    }
2056 3
    $values = \implode(',', $data);
2057
2058 3
    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...
2059
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
2060
    }
2061
2062 3
    $sql = 'REPLACE INTO ' . $databaseName . $this->quote_string($table) . " ($columns) VALUES ($values)";
2063
2064 3
    return $this->query($sql);
2065
  }
2066
2067
  /**
2068
   * Rollback in a transaction and end the transaction.
2069
   *
2070
   * @return bool <p>Boolean true on success, false otherwise.</p>
2071
   */
2072 12 View Code Duplication
  public function rollback(): bool
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...
2073
  {
2074 12
    if ($this->in_transaction === false) {
2075
      $this->debug->displayError('Error: mysql server is not in transaction!', false);
2076
2077
      return false;
2078
    }
2079
2080 12
    if ($this->isDoctrinePDOConnection() === true) {
2081
      $this->doctrine_connection->rollBack();
2082
      $this->doctrine_connection->setAutoCommit(true);
2083
2084
      if ($this->doctrine_connection->isAutoCommit() === true) {
2085
        $return = true;
2086
      } else {
2087
        $return = false;
2088
      }
2089
    } else {
2090 12
      $return = \mysqli_rollback($this->mysqli_link);
2091 12
      \mysqli_autocommit($this->mysqli_link, true);
2092
    }
2093
2094 12
    $this->in_transaction = false;
2095
2096 12
    return $return;
2097
  }
2098
2099
  /**
2100
   * Try to secure a variable, so can you use it in sql-queries.
2101
   *
2102
   * <p>
2103
   * <strong>int:</strong> (also strings that contains only an int-value)<br />
2104
   * 1. parse into "int"
2105
   * </p><br />
2106
   *
2107
   * <p>
2108
   * <strong>float:</strong><br />
2109
   * 1. return "float"
2110
   * </p><br />
2111
   *
2112
   * <p>
2113
   * <strong>string:</strong><br />
2114
   * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'<br />
2115
   * 2. trim '<br />
2116
   * 3. escape the string (and remove non utf-8 chars)<br />
2117
   * 4. trim ' again (because we maybe removed some chars)<br />
2118
   * 5. add ' around the new string<br />
2119
   * </p><br />
2120
   *
2121
   * <p>
2122
   * <strong>array:</strong><br />
2123
   * 1. return null
2124
   * </p><br />
2125
   *
2126
   * <p>
2127
   * <strong>object:</strong><br />
2128
   * 1. return false
2129
   * </p><br />
2130
   *
2131
   * <p>
2132
   * <strong>null:</strong><br />
2133
   * 1. return null
2134
   * </p>
2135
   *
2136
   * @param mixed     $var
2137
   * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
2138
   *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
2139
   *                                 <strong>null</strong> => Convert the array into null, every time.
2140
   *
2141
   * @return mixed
2142
   */
2143 97
  public function secure($var, $convert_array = true)
2144
  {
2145 97
    if (\is_array($var)) {
2146 6
      if ($convert_array === null) {
2147
2148
        if ($this->convert_null_to_empty_string === true) {
2149
          $var = "''";
2150
        } else {
2151
          $var = 'NULL';
2152
        }
2153
2154
      } else {
2155
2156 6
        $varCleaned = [];
2157 6
        foreach ((array)$var as $key => $value) {
2158
2159 6
          $key = $this->escape($key, false, false, $convert_array);
2160 6
          $value = $this->secure($value);
2161
2162
          /** @noinspection OffsetOperationsInspection */
2163 6
          $varCleaned[$key] = $value;
2164
        }
2165
2166 6 View Code Duplication
        if ($convert_array === true) {
2167 6
          $varCleaned = \implode(',', $varCleaned);
2168
2169 6
          $var = $varCleaned;
2170
        } else {
2171
          $var = $varCleaned;
2172
        }
2173
2174
      }
2175
2176 6
      return $var;
2177
    }
2178
2179 97
    if ($var === '') {
2180 6
      return "''";
2181
    }
2182
2183 97
    if ($var === "''") {
2184 3
      return "''";
2185
    }
2186
2187 97
    if ($var === null) {
2188 4
      if ($this->convert_null_to_empty_string === true) {
2189 3
        return "''";
2190
      }
2191
2192 4
      return 'NULL';
2193
    }
2194
2195 96
    if (\in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
2196 3
      return $var;
2197
    }
2198
2199 96
    if (\is_string($var)) {
2200 83
      $var = \trim($var, "'");
2201
    }
2202
2203 96
    $var = $this->escape($var, false, false, null);
2204
2205 93
    if (\is_string($var)) {
2206 83
      $var = "'" . \trim($var, "'") . "'";
2207
    }
2208
2209 93
    return $var;
2210
  }
2211
2212
  /**
2213
   * Execute a "select"-query.
2214
   *
2215
   * @param string       $table
2216
   * @param string|array $where
2217
   * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
2218
   *
2219
   * @return false|Result <p>false on error</p>
2220
   *
2221
   * @throws QueryException
2222
   */
2223 62 View Code Duplication
  public function select(string $table, $where = '1=1', string $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...
2224
  {
2225
    // init
2226 62
    $table = \trim($table);
2227
2228 62
    if ($table === '') {
2229 3
      $this->debug->displayError('Invalid table name, table name in empty.', false);
2230
2231 3
      return false;
2232
    }
2233
2234 62
    if (\is_string($where)) {
2235 24
      $WHERE = $this->escape($where, false);
2236 41
    } elseif (\is_array($where)) {
2237 41
      $WHERE = $this->_parseArrayPair($where, 'AND');
2238
    } else {
2239 3
      $WHERE = '';
2240
    }
2241
2242 62
    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...
2243
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
2244
    }
2245
2246 62
    $sql = 'SELECT * FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE)";
2247
2248 62
    return $this->query($sql);
2249
  }
2250
2251
  /**
2252
   * Selects a different database than the one specified on construction.
2253
   *
2254
   * @param string $database <p>Database name to switch to.</p>
2255
   *
2256
   * @return bool <p>Boolean true on success, false otherwise.</p>
2257
   */
2258
  public function select_db(string $database): bool
2259
  {
2260
    if (!$this->isReady()) {
2261
      return false;
2262
    }
2263
2264
    return mysqli_select_db($this->mysqli_link, $database);
2265
  }
2266
2267
  /**
2268
   * @param array $extra_config           <p>
2269
   *                                      'session_to_db' => false|true<br>
2270
   *                                      'socket' => 'string (path)'<br>
2271
   *                                      'ssl' => 'bool'<br>
2272
   *                                      'clientkey' => 'string (path)'<br>
2273
   *                                      'clientcert' => 'string (path)'<br>
2274
   *                                      'cacert' => 'string (path)'<br>
2275
   *                                      </p>
2276
   */
2277 23
  public function setConfigExtra(array $extra_config)
2278
  {
2279 23
    if (isset($extra_config['session_to_db'])) {
2280
      $this->session_to_db = (boolean)$extra_config['session_to_db'];
2281
    }
2282
2283
    if (
2284 23
        isset($extra_config['doctrine'])
2285
        &&
2286 23
        $extra_config['doctrine'] instanceof \Doctrine\DBAL\Connection
2287
    ) {
2288
      $this->doctrine_connection = $extra_config['doctrine'];
2289
    }
2290
2291 23
    if (isset($extra_config['socket'])) {
2292
      $this->socket = $extra_config['socket'];
2293
    }
2294
2295 23
    if (isset($extra_config['ssl'])) {
2296
      $this->ssl = $extra_config['ssl'];
2297
    }
2298
2299 23
    if (isset($extra_config['clientkey'])) {
2300
      $this->clientkey = $extra_config['clientkey'];
2301
    }
2302
2303 23
    if (isset($extra_config['clientcert'])) {
2304
      $this->clientcert = $extra_config['clientcert'];
2305
    }
2306
2307 23
    if (isset($extra_config['cacert'])) {
2308
      $this->cacert = $extra_config['cacert'];
2309
    }
2310 23
  }
2311
2312
  /**
2313
   * Set the current charset.
2314
   *
2315
   * @param string $charset
2316
   *
2317
   * @return bool
2318
   */
2319 14
  public function set_charset(string $charset): bool
2320
  {
2321 14
    $charsetLower = strtolower($charset);
2322 14
    if ($charsetLower === 'utf8' || $charsetLower === 'utf-8') {
2323 8
      $charset = 'utf8';
2324
    }
2325 14
    if ($charset === 'utf8' && Helper::isUtf8mb4Supported($this) === true) {
2326 8
      $charset = 'utf8mb4';
2327
    }
2328
2329 14
    $this->charset = $charset;
2330
2331
    if (
2332 14
        $this->mysqli_link
2333
        &&
2334 14
        $this->mysqli_link instanceof \mysqli
2335
    ) {
2336
2337 14
      $return = mysqli_set_charset($this->mysqli_link, $charset);
2338
2339
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
2340 14
      @\mysqli_query($this->mysqli_link, 'SET CHARACTER SET ' . $charset);
2341
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
2342 14
      @\mysqli_query($this->mysqli_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...
2343
2344
    } elseif ($this->isDoctrinePDOConnection() === true) {
2345
2346
      $doctrineWrappedConnection = $this->getDoctrinePDOConnection();
2347
2348
      $doctrineWrappedConnection->exec('SET CHARACTER SET ' . $charset);
2349
      $doctrineWrappedConnection->exec("SET NAMES '" . $charset . "'");
2350
2351
      $return = true;
2352
2353
    } else {
2354
2355
      throw new DBConnectException('Can not set the charset');
2356
2357
    }
2358
2359 14
    return $return;
2360
  }
2361
2362
  /**
2363
   * Set the option to convert null to "''" (empty string).
2364
   *
2365
   * Used in secure() => select(), insert(), update(), delete()
2366
   *
2367
   * @deprecated It's not recommended to convert NULL into an empty string!
2368
   *
2369
   * @param bool $bool
2370
   *
2371
   * @return self
2372
   */
2373 3
  public function set_convert_null_to_empty_string(bool $bool): self
2374
  {
2375 3
    $this->convert_null_to_empty_string = $bool;
2376
2377 3
    return $this;
2378
  }
2379
2380
  /**
2381
   * Enables or disables internal report functions
2382
   *
2383
   * @link http://php.net/manual/en/function.mysqli-report.php
2384
   *
2385
   * @param int $flags <p>
2386
   *                   <table>
2387
   *                   Supported flags
2388
   *                   <tr valign="top">
2389
   *                   <td>Name</td>
2390
   *                   <td>Description</td>
2391
   *                   </tr>
2392
   *                   <tr valign="top">
2393
   *                   <td><b>MYSQLI_REPORT_OFF</b></td>
2394
   *                   <td>Turns reporting off</td>
2395
   *                   </tr>
2396
   *                   <tr valign="top">
2397
   *                   <td><b>MYSQLI_REPORT_ERROR</b></td>
2398
   *                   <td>Report errors from mysqli function calls</td>
2399
   *                   </tr>
2400
   *                   <tr valign="top">
2401
   *                   <td><b>MYSQLI_REPORT_STRICT</b></td>
2402
   *                   <td>
2403
   *                   Throw <b>mysqli_sql_exception</b> for errors
2404
   *                   instead of warnings
2405
   *                   </td>
2406
   *                   </tr>
2407
   *                   <tr valign="top">
2408
   *                   <td><b>MYSQLI_REPORT_INDEX</b></td>
2409
   *                   <td>Report if no index or bad index was used in a query</td>
2410
   *                   </tr>
2411
   *                   <tr valign="top">
2412
   *                   <td><b>MYSQLI_REPORT_ALL</b></td>
2413
   *                   <td>Set all options (report all)</td>
2414
   *                   </tr>
2415
   *                   </table>
2416
   *                   </p>
2417
   *
2418
   * @return bool
2419
   */
2420
  public function set_mysqli_report(int $flags): bool
2421
  {
2422
    return \mysqli_report($flags);
2423
  }
2424
2425
  /**
2426
   * Show config errors by throw exceptions.
2427
   *
2428
   * @return bool
2429
   *
2430
   * @throws \InvalidArgumentException
2431
   */
2432 23
  public function showConfigError(): bool
2433
  {
2434
    // check if a doctrine connection is already open, first
2435
    if (
2436 23
        $this->doctrine_connection
2437
        &&
2438 23
        $this->doctrine_connection->isConnected()
2439
    ) {
2440
      return true;
2441
    }
2442
2443
    if (
2444 23
        !$this->hostname
2445
        ||
2446 20
        !$this->username
2447
        ||
2448 23
        !$this->database
2449
    ) {
2450
2451 9
      if (!$this->hostname) {
2452 3
        throw new \InvalidArgumentException('no-sql-hostname');
2453
      }
2454
2455 6
      if (!$this->username) {
2456 3
        throw new \InvalidArgumentException('no-sql-username');
2457
      }
2458
2459 3
      if (!$this->database) {
2460 3
        throw new \InvalidArgumentException('no-sql-database');
2461
      }
2462
2463
      return false;
2464
    }
2465
2466 14
    return true;
2467
  }
2468
2469
  /**
2470
   * alias: "beginTransaction()"
2471
   */
2472 3
  public function startTransaction(): bool
2473
  {
2474 3
    return $this->beginTransaction();
2475
  }
2476
2477
  /**
2478
   * Determine if database table exists
2479
   *
2480
   * @param string $table
2481
   *
2482
   * @return bool
2483
   */
2484 3
  public function table_exists(string $table): bool
2485
  {
2486 3
    $check = $this->query('SELECT 1 FROM ' . $this->quote_string($table));
2487
2488 3
    return $check !== false
2489
           &&
2490 3
           $check instanceof Result
2491
           &&
2492 3
           $check->num_rows > 0;
2493
  }
2494
2495
  /**
2496
   * Execute a callback inside a transaction.
2497
   *
2498
   * @param callback $callback <p>The callback to run inside the transaction, if it's throws an "Exception" or if it's
2499
   *                           returns "false", all SQL-statements in the callback will be rollbacked.</p>
2500
   *
2501
   * @return bool <p>Boolean true on success, false otherwise.</p>
2502
   */
2503 3
  public function transact($callback): bool
2504
  {
2505
    try {
2506
2507 3
      $beginTransaction = $this->beginTransaction();
2508 3
      if ($beginTransaction === false) {
2509 3
        $this->debug->displayError('Error: transact -> can not start transaction!', false);
2510
2511 3
        return false;
2512
      }
2513
2514 3
      $result = $callback($this);
2515 3
      if ($result === false) {
2516
        /** @noinspection ThrowRawExceptionInspection */
2517 3
        throw new \Exception('call_user_func [' . $callback . '] === false');
2518
      }
2519
2520 3
      return $this->commit();
2521
2522 3
    } catch (\Exception $e) {
2523
2524 3
      $this->rollback();
2525
2526 3
      return false;
2527
    }
2528
  }
2529
2530
  /**
2531
   * Execute a "update"-query.
2532
   *
2533
   * @param string       $table
2534
   * @param array        $data
2535
   * @param array|string $where
2536
   * @param null|string  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
2537
   *
2538
   * @return false|int <p>false on error</p>
2539
   *
2540
   * @throws QueryException
2541
   */
2542 21
  public function update(string $table, array $data = [], $where = '1=1', string $databaseName = null)
2543
  {
2544
    // init
2545 21
    $table = \trim($table);
2546
2547 21
    if ($table === '') {
2548 3
      $this->debug->displayError('Invalid table name, table name in empty.', false);
2549
2550 3
      return false;
2551
    }
2552
2553 21
    if (\count($data) === 0) {
2554 6
      $this->debug->displayError('Invalid data for UPDATE, data is empty.', false);
2555
2556 6
      return false;
2557
    }
2558
2559 21
    $SET = $this->_parseArrayPair($data);
2560
2561 21
    if (\is_string($where)) {
2562 6
      $WHERE = $this->escape($where, false);
2563 18
    } elseif (\is_array($where)) {
2564 15
      $WHERE = $this->_parseArrayPair($where, 'AND');
2565
    } else {
2566 3
      $WHERE = '';
2567
    }
2568
2569 21
    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...
2570
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
2571
    }
2572
2573 21
    $sql = 'UPDATE ' . $databaseName . $this->quote_string($table) . " SET $SET WHERE ($WHERE)";
2574
2575 21
    return $this->query($sql);
2576
  }
2577
2578
}
2579