Completed
Push — master ( 6bc12c...0f5448 )
by Lars
12:41 queued 04:44
created

DB::inTransaction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
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 handles 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 = false;
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 Debug
91
   */
92
  private $_debug;
93
94
  /**
95
   * __construct()
96
   *
97
   * @param string         $hostname
98
   * @param string         $username
99
   * @param string         $password
100
   * @param string         $database
101
   * @param int            $port
102
   * @param string         $charset
103
   * @param boolean|string $exit_on_error use a empty string "" or false to disable it
104
   * @param boolean|string $echo_on_error use a empty string "" or false to disable it
105
   * @param string         $logger_class_name
106
   * @param string         $logger_level  'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'
107
   * @param boolean|string $session_to_db use a empty string "" or false to disable it
108
   */
109 10
  protected function __construct($hostname, $username, $password, $database, $port, $charset, $exit_on_error, $echo_on_error, $logger_class_name, $logger_level, $session_to_db)
110
  {
111 10
    $this->connected = false;
112
113 10
    $this->_debug = new Debug($this);
114
115 10
    $this->_loadConfig(
116 10
        $hostname,
117 10
        $username,
118 10
        $password,
119 10
        $database,
120 10
        $port,
121 10
        $charset,
122 10
        $exit_on_error,
123 10
        $echo_on_error,
124 10
        $logger_class_name,
125 10
        $logger_level,
126
        $session_to_db
127 10
    );
128
129 7
    $this->connect();
130
131 4
    $this->mysqlDefaultTimeFunctions = array(
132
      // Returns the current date.
133 4
      'CURDATE()',
134
      // CURRENT_DATE	| Synonyms for CURDATE()
135 4
      'CURRENT_DATE()',
136
      // CURRENT_TIME	| Synonyms for CURTIME()
137 4
      'CURRENT_TIME()',
138
      // CURRENT_TIMESTAMP | Synonyms for NOW()
139 4
      'CURRENT_TIMESTAMP()',
140
      // Returns the current time.
141 4
      'CURTIME()',
142
      // Synonym for NOW()
143 4
      'LOCALTIME()',
144
      // Synonym for NOW()
145 4
      'LOCALTIMESTAMP()',
146
      // Returns the current date and time.
147 4
      'NOW()',
148
      // Returns the time at which the function executes.
149 4
      'SYSDATE()',
150
      // Returns a UNIX timestamp.
151 4
      'UNIX_TIMESTAMP()',
152
      // Returns the current UTC date.
153 4
      'UTC_DATE()',
154
      // Returns the current UTC time.
155 4
      'UTC_TIME()',
156
      // Returns the current UTC date and time.
157 4
      'UTC_TIMESTAMP()',
158
    );
159 4
  }
160
161
  /**
162
   * Prevent the instance from being cloned.
163
   *
164
   * @return void
165
   */
166
  private function __clone()
167
  {
168
  }
169
170
  /**
171
   * __destruct
172
   *
173
   */
174
  public function __destruct()
175
  {
176
    // close the connection only if we don't save PHP-SESSION's in DB
177
    if ($this->session_to_db === false) {
178 10
      $this->close();
179
    }
180 10
  }
181 10
182 10
  /**
183 10
   * __wakeup
184
   *
185 10
   * @return void
186 4
   */
187 4
  public function __wakeup()
188
  {
189 10
    $this->reconnect();
190 4
  }
191 4
192
  /**
193
   * Load the config from the constructor.
194 7
   *
195
   * @param string         $hostname
196
   * @param string         $username
197
   * @param string         $password
198 10
   * @param string         $database
199
   * @param int            $port
200
   * @param string         $charset
201
   * @param boolean|string $exit_on_error <p>Use a empty string "" or false to disable it.</p>
202 10
   * @param boolean|string $echo_on_error <p>Use a empty string "" or false to disable it.</p>
203
   * @param string         $logger_class_name
204 10
   * @param string         $logger_level
205 10
   * @param boolean|string $session_to_db <p>Use a empty string "" or false to disable it.</p>
206
   *
207 10
   * @return bool
208 10
   */
209 10
  private function _loadConfig($hostname, $username, $password, $database, $port, $charset, $exit_on_error, $echo_on_error, $logger_class_name, $logger_level, $session_to_db)
210
  {
211 10
    $this->hostname = (string)$hostname;
212 10
    $this->username = (string)$username;
213 10
    $this->password = (string)$password;
214
    $this->database = (string)$database;
215 10
216 10
    if ($charset) {
217
      $this->charset = (string)$charset;
218 10
    }
219
220 10
    if ($port) {
221
      $this->port = (int)$port;
222
    } else {
223
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
224
      /** @noinspection UsageOfSilenceOperatorInspection */
225
      $this->port = (int)@ini_get('mysqli.default_port');
226
    }
227
228
    // fallback
229
    if (!$this->port) {
230 10
      $this->port = 3306;
231
    }
232
233
    if (!$this->socket) {
234 10
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
235 10
      $this->socket = @ini_get('mysqli.default_socket');
236 9
    }
237 9
238 8
    if ($exit_on_error === true || $exit_on_error === false) {
239 10
      $this->_debug->setExitOnError($exit_on_error);
240
    }
241 3
242 1
    if ($echo_on_error === true || $echo_on_error === false) {
243
      $this->_debug->setEchoOnError($echo_on_error);
244
    }
245 2
246 1
    $this->_debug->setLoggerClassName($logger_class_name);
247
    $this->_debug->setLoggerLevel($logger_level);
248
249 1
    $this->session_to_db = (boolean)$session_to_db;
250 1
251
    return $this->showConfigError();
252
  }
253
254
  /**
255
   * Parses arrays with value pairs and generates SQL to use in queries.
256 7
   *
257
   * @param array  $arrayPair
258
   * @param string $glue <p>This is the separator.</p>
259
   *
260
   * @return string
261
   *
262
   * @internal
263
   */
264
  public function _parseArrayPair($arrayPair, $glue = ',')
265
  {
266 9
    // init
267
    $sql = '';
268 9
269 1
    /** @noinspection IsEmptyFunctionUsageInspection */
270
    if (empty($arrayPair)) {
271
      return '';
272 9
    }
273
274 9
    $arrayPairCounter = 0;
275
    foreach ($arrayPair as $_key => $_value) {
276 9
      $_connector = '=';
277 9
      $_glueHelper = '';
278 9
      $_key_upper = strtoupper($_key);
279
280
      if (strpos($_key_upper, ' NOT') !== false) {
281 9
        $_connector = 'NOT';
282 9
      }
283 9
284 9
      if (strpos($_key_upper, ' IS') !== false) {
285 9
        $_connector = 'IS';
286 9
      }
287 9
288 9
      if (strpos($_key_upper, ' IS NOT') !== false) {
289 9
        $_connector = 'IS NOT';
290 9
      }
291 3
292 3
      if (strpos($_key_upper, ' IN') !== false) {
293 3
        $_connector = 'IN';
294
      }
295 6
296
      if (strpos($_key_upper, ' NOT IN') !== false) {
297 6
        $_connector = 'NOT IN';
298
      }
299
300
      if (strpos($_key_upper, ' BETWEEN') !== false) {
301
        $_connector = 'BETWEEN';
302
      }
303
304 6
      if (strpos($_key_upper, ' NOT BETWEEN') !== false) {
305
        $_connector = 'NOT BETWEEN';
306 6
      }
307
308
      if (strpos($_key_upper, ' LIKE') !== false) {
309
        $_connector = 'LIKE';
310
      }
311
312
      if (strpos($_key_upper, ' NOT LIKE') !== false) {
313
        $_connector = 'NOT LIKE';
314 45
      }
315
316 45 View Code Duplication
      if (strpos($_key_upper, ' >') !== false && strpos($_key_upper, ' =') === false) {
317
        $_connector = '>';
318
      }
319
320 View Code Duplication
      if (strpos($_key_upper, ' <') !== false && strpos($_key_upper, ' =') === false) {
321
        $_connector = '<';
322
      }
323
324
      if (strpos($_key_upper, ' >=') !== false) {
325
        $_connector = '>=';
326 2
      }
327
328 2
      if (strpos($_key_upper, ' <=') !== false) {
329
        $_connector = '<=';
330
      }
331
332
      if (strpos($_key_upper, ' <>') !== false) {
333
        $_connector = '<>';
334
      }
335
336
      if (strpos($_key_upper, ' OR') !== false) {
337
        $_glueHelper = 'OR';
338
      }
339
340
      if (strpos($_key_upper, ' AND') !== false) {
341
        $_glueHelper = 'AND';
342
      }
343
344
      if (is_array($_value) === true) {
345
        foreach ($_value as $oldKey => $oldValue) {
346
          $_value[$oldKey] = $this->secure($oldValue);
347
        }
348
349
        if ($_connector === 'NOT IN' || $_connector === 'IN') {
350
          $_value = '(' . implode(',', $_value) . ')';
351
        } elseif ($_connector === 'NOT BETWEEN' || $_connector === 'BETWEEN') {
352
          $_value = '(' . implode(' AND ', $_value) . ')';
353
        }
354
355
      } else {
356
        $_value = $this->secure($_value);
357
      }
358
359
      $quoteString = $this->quote_string(
360
          trim(
361
              str_ireplace(
362
                  array(
363
                      $_connector,
364
                      $_glueHelper,
365
                  ),
366
                  '',
367
                  $_key
368
              )
369
          )
370
      );
371
372
      if (!is_array($_value)) {
373
        $_value = array($_value);
374
      }
375
376
      if (!$_glueHelper) {
377
        $_glueHelper = $glue;
378
      }
379
380
      $tmpCounter = 0;
381
      foreach ($_value as $valueInner) {
382
383 57
        $_glueHelperInner = $_glueHelper;
384
385
        if ($arrayPairCounter === 0) {
386
387
          if ($tmpCounter === 0 && $_glueHelper === 'OR') {
388 57
            $_glueHelperInner = '1 = 1 AND ('; // first "OR"-query glue
389
          } elseif ($tmpCounter === 0) {
390
            $_glueHelperInner = ''; // first query glue e.g. for "INSERT"-query -> skip the first ","
391
          }
392
393 57
        } elseif ($tmpCounter === 0 && $_glueHelper === 'OR') {
394
          $_glueHelperInner = 'AND ('; // inner-loop "OR"-query glue
395
        }
396 57
397 57
        $sql .= ' ' . $_glueHelperInner . ' ' . $quoteString . ' ' . $_connector . ' ' . $valueInner . " \n";
398 11
        $tmpCounter++;
399 57
      }
400 11
401
      if ($_glueHelper === 'OR') {
402
        $sql .= ' ) ';
403 57
      }
404 57
405 57
      $arrayPairCounter++;
406
    }
407 57
408 10
    return $sql;
409 10
  }
410 10
411 10
  /**
412 10
   * _parseQueryParams
413 10
   *
414 10
   * @param string $sql
415 10
   * @param array  $params
416 10
   *
417 10
   * @return string
418 10
   */
419
  private function _parseQueryParams($sql, array $params)
420 10
  {
421
    // is there anything to parse?
422 4
    if (strpos($sql, '?') === false) {
423 1
      return $sql;
424 1
    }
425 4
426
    if (count($params) > 0) {
427 57
      $parseKey = md5(uniqid((string)mt_rand(), true));
428
      $sql = str_replace('?', $parseKey, $sql);
429
430
      $k = 0;
431
      while (strpos($sql, $parseKey) !== false) {
432
        $value = $this->secure($params[$k]);
433
        $sql = UTF8::str_replace_first($parseKey, $value, $sql);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by \voku\helper\UTF8::str_r...parseKey, $value, $sql) on line 433 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...
434
        $k++;
435
      }
436
    }
437
438
    return $sql;
439
  }
440
441
  /**
442
   * Gets the number of affected rows in a previous MySQL operation.
443
   *
444
   * @return int
445
   */
446
  public function affected_rows()
447
  {
448
    return \mysqli_affected_rows($this->link);
449
  }
450 35
451
  /**
452 35
   * Begins a transaction, by turning off auto commit.
453
   *
454
   * @return bool <p>This will return true or false indicating success of transaction</p>
455
   */
456 35
  public function beginTransaction()
457 4
  {
458
    $this->clearErrors();
459 4
460
    if ($this->inTransaction() === true) {
461
      $this->_debug->displayError('Error mysql server already in transaction!', false);
462
463
      return false;
464 33
    }
465 3
466 33
    if (\mysqli_connect_errno()) {
467 3
      $this->_debug->displayError('Error connecting to mysql server: ' . \mysqli_connect_error(), false);
468 33
469 3
      return false;
470 3
    }
471
472 33
    $this->_in_transaction = true;
473 33
    \mysqli_autocommit($this->link, false);
474 33
475
    return true;
476 33
  }
477
478 33
  /**
479 33
   * Clear the errors in "_debug->_errors".
480 28
   *
481 28
   * @return bool
482 24
   */
483
  public function clearErrors()
484
  {
485 33
    return $this->_debug->clearErrors();
486
  }
487
488 27
  /**
489
   * Closes a previously opened database connection.
490
   */
491 27
  public function close()
492
  {
493
    $this->connected = false;
494
495 25
    if ($this->link) {
496
      \mysqli_close($this->link);
497
    }
498 23
  }
499 22
500 22
  /**
501
   * Open a new connection to the MySQL server.
502 22
   *
503
   * @return bool
504
   *
505
   * @throws DBConnectException
506 8
   */
507 8
  public function connect()
508 8
  {
509
    if ($this->isReady()) {
510 8
      return true;
511
    }
512
513
    \mysqli_report(MYSQLI_REPORT_STRICT);
514
    try {
515
      $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...
516
517
      if (Helper::isMysqlndIsUsed() === true) {
518
        \mysqli_options($this->link, MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
519
      }
520 8
521
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
522 8
      $this->connected = @\mysqli_real_connect(
523
          $this->link,
524
          $this->hostname,
525
          $this->username,
526
          $this->password,
527
          $this->database,
528
          $this->port,
529
          $this->socket
530
      );
531
    } catch (\Exception $e) {
532
      $error = 'Error connecting to mysql server: ' . $e->getMessage();
533 3
      $this->_debug->displayError($error, false);
534
      throw new DBConnectException($error, 100, $e);
535
    }
536 3
    \mysqli_report(MYSQLI_REPORT_OFF);
537
538
    if (!$this->connected) {
539
      $error = 'Error connecting to mysql server: ' . \mysqli_connect_error();
540 3
      $this->_debug->displayError($error, false);
541 3
      /** @noinspection ThrowRawExceptionInspection */
542 3
      throw new DBConnectException($error, 101);
543
    }
544 3
545 3
    $this->set_charset($this->charset);
546 3
547 3
    return $this->isReady();
548 3
  }
549 3
550 3
  /**
551
   * Execute a "delete"-query.
552 3
   *
553
   * @param string       $table
554
   * @param string|array $where
555
   * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
556
   *
557
   * @return false|int <p>false on error</p>
558
   *
559
   *    * @throws QueryException
560
   */
561 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...
562
  {
563
    // init
564
    $table = trim($table);
565
566
    if ($table === '') {
567
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
568
569
      return false;
570
    }
571
572
    if (is_string($where)) {
573
      $WHERE = $this->escape($where, false);
574
    } elseif (is_array($where)) {
575
      $WHERE = $this->_parseArrayPair($where, 'AND');
576
    } else {
577
      $WHERE = '';
578
    }
579
580
    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...
581
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
582
    }
583
584
    $sql = 'DELETE FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE);";
585
586
    return $this->query($sql);
587
  }
588
589
  /**
590
   * Ends a transaction and commits if no errors, then ends autocommit.
591
   *
592
   * @return bool <p>This will return true or false indicating success of transactions.</p>
593
   */
594
  public function endTransaction()
595
  {
596
597 26
    if (!$this->errors()) {
598
      \mysqli_commit($this->link);
599
      $return = true;
600
    } else {
601 26
      $this->rollback();
602 26
      $return = false;
603 26
    }
604 1
605
    \mysqli_autocommit($this->link, true);
606
    $this->_in_transaction = false;
607 26
608 1
    return $return;
609
  }
610
611 26
  /**
612 22
   * Get all errors from "$this->_errors".
613 22
   *
614
   * @return array|false <p>false === on errors</p>
615 26
   */
616
  public function errors()
617 26
  {
618 22
    $errors = $this->_debug->getErrors();
619 22
620
    return count($errors) > 0 ? $errors : false;
621 26
  }
622
623
  /**
624
   * Escape: Use "mysqli_real_escape_string" and clean non UTF-8 chars + some extra optional stuff.
625
   *
626
   * @param mixed     $var           boolean: convert into "integer"<br />
627
   *                                 int: int (don't change it)<br />
628
   *                                 float: float (don't change it)<br />
629
   *                                 null: null (don't change it)<br />
630
   *                                 array: run escape() for every key => value<br />
631
   *                                 string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
632
   * @param bool      $stripe_non_utf8
633
   * @param bool      $html_entity_decode
634
   * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
635
   *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
636
   *                                 <strong>null</strong> => Convert the array into null, every time.
637
   *
638
   * @return mixed
639
   */
640
  public function escape($var = '', $stripe_non_utf8 = true, $html_entity_decode = false, $convert_array = false)
641 33
  {
642
    if ($var === '') {
643 33
      return '';
644 2
    }
645
646
    if ($var === null) {
647 33
      return null;
648 2
    }
649
650
    // save the current value as int (for later usage)
651
    if (!is_object($var)) {
652 33
      $varInt = (int)$var;
653 33
    }
654 33
655
    /** @noinspection TypeUnsafeComparisonInspection */
656
    if (
657
        is_int($var)
658 33
        ||
659
        is_bool($var)
660 33
        ||
661 33
        (
662
            isset($varInt, $var[0])
663 33
            &&
664 33
            $var[0] != '0'
665 33
            &&
666 33
            "$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...
667
        )
668 33
    ) {
669 33
670
      // "int" || int || bool
671
672
      return (int)$var;
673 24
    }
674
675
    if (is_float($var)) {
676 33
677
      // float
678
679
      return $var;
680 5
    }
681
682
    if (is_array($var)) {
683 33
684
      // array
685
686
      if ($convert_array === null) {
687 3
        return null;
688 3
      }
689
690
      $varCleaned = array();
691 1
      foreach ((array)$var as $key => $value) {
692 1
693
        $key = $this->escape($key, $stripe_non_utf8, $html_entity_decode);
694 1
        $value = $this->escape($value, $stripe_non_utf8, $html_entity_decode);
695 1
696
        /** @noinspection OffsetOperationsInspection */
697
        $varCleaned[$key] = $value;
698 1
      }
699 1
700
      if ($convert_array === true) {
701 1
        $varCleaned = implode(',', $varCleaned);
702 1
703
        return $varCleaned;
704 1
      }
705
706
      return (array)$varCleaned;
707 1
    }
708
709
    if (
710
        is_string($var)
711 33
        ||
712
        (
713
            is_object($var)
714 3
            &&
715 3
            method_exists($var, '__toString')
716 3
        )
717 3
    ) {
718 33
719
      // "string"
720
721
      $var = (string)$var;
722 33
723
      if ($stripe_non_utf8 === true) {
724 33
        $var = UTF8::cleanup($var);
725 9
      }
726 9
727
      if ($html_entity_decode === true) {
728 33
        // use no-html-entity for db
729
        $var = UTF8::html_entity_decode($var);
730 1
      }
731 1
732
      $var = get_magic_quotes_gpc() ? stripslashes($var) : $var;
733 33
734
      $var = \mysqli_real_escape_string($this->getLink(), $var);
735 33
736
      return (string)$var;
737 33
738
    }
739
740
    if ($var instanceof \DateTime) {
741 3
742
      // "DateTime"-object
743
744
      try {
745
        return $this->escape($var->format('Y-m-d H:i:s'), false);
746 3
      } catch (\Exception $e) {
747
        return null;
748
      }
749
750
    } else {
751
      return false;
752 2
    }
753
  }
754
755
  /**
756
   * Execute select/insert/update/delete sql-queries.
757
   *
758
   * @param string $query    sql-query
759
   * @param bool   $useCache use cache?
760
   * @param int    $cacheTTL cache-ttl in seconds
761 35
   *
762
   * @return mixed "array" by "<b>SELECT</b>"-queries<br />
763 35
   *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
764
   *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
765
   *               "true" by e.g. "DROP"-queries<br />
766
   *               "false" on error
767
   *
768
   * @throws QueryException
769
   */
770
  public static function execSQL($query, $useCache = false, $cacheTTL = 3600)
771 22
  {
772
    // init
773 22
    $cacheKey = null;
774
    $db = self::getInstance();
775
776 View Code Duplication
    if ($useCache === true) {
777
      $cache = new Cache(null, null, false, $useCache);
778
      $cacheKey = 'sql-' . md5($query);
779
780
      if (
781 8
          $cache->getCacheIsReady() === true
782
          &&
783 8
          $cache->existsItem($cacheKey)
784
      ) {
785
        return $cache->getItem($cacheKey);
786
      }
787
788
    } else {
789
      $cache = false;
790
    }
791
792
    $result = $db->query($query);
793
794
    if ($result instanceof Result) {
795
796
      $return = $result->fetchAllArray();
797
798 9
      // save into the cache
799 View Code Duplication
      if (
800 9
          $cacheKey !== null
801 1
          &&
802
          $useCache === true
803
          &&
804 1
          $cache instanceof Cache
805
          &&
806
          $cache->getCacheIsReady() === true
807
      ) {
808
        $cache->setItem($cacheKey, $return, $cacheTTL);
809 1
      }
810
811
    } else {
812 1
      $return = $result;
813 1
    }
814
815
    return $return;
816 1
  }
817
818
  /**
819 8
   * Get all table-names via "SHOW TABLES".
820
   *
821
   * @return array
822 8
   */
823
  public function getAllTables()
824 8
  {
825
    $query = 'SHOW TABLES';
826
    $result = $this->query($query);
827
828
    return $result->fetchAllArray();
829
  }
830
831
  /**
832
   * @return Debug
833
   */
834 3
  public function getDebugger()
835
  {
836 3
    return $this->_debug;
837
  }
838 3
839 2
  /**
840 2
   * Get errors from "$this->_errors".
841
   *
842 3
   * @return array
843 3
   */
844 3
  public function getErrors()
845 3
  {
846
    return $this->_debug->getErrors();
847 3
  }
848
849
  /**
850
   * getInstance()
851
   *
852
   * @param string      $hostname
853
   * @param string      $username
854
   * @param string      $password
855
   * @param string      $database
856 3
   * @param string      $port          default is (int)3306
857
   * @param string      $charset       default is 'utf8' or 'utf8mb4' (if supported)
858
   * @param bool|string $exit_on_error use a empty string "" or false to disable it
859 3
   * @param bool|string $echo_on_error use a empty string "" or false to disable it
860 3
   * @param string      $logger_class_name
861 3
   * @param string      $logger_level
862 3
   * @param bool|string $session_to_db use a empty string "" or false to disable it
863
   *
864
   * @return \voku\db\DB
865 3
   */
866
  public static function getInstance($hostname = '', $username = '', $password = '', $database = '', $port = '', $charset = '', $exit_on_error = '', $echo_on_error = '', $logger_class_name = '', $logger_level = '', $session_to_db = '')
867
  {
868
    /**
869
     * @var $instance DB[]
870
     */
871
    static $instance = array();
872
873
    /**
874
     * @var $firstInstance DB
875
     */
876
    static $firstInstance = null;
877
878
    if (
879
        $hostname . $username . $password . $database . $port . $charset == ''
880
        &&
881
        null !== $firstInstance
882
    ) {
883
      return $firstInstance;
884
    }
885
886 3
    $connection = md5(
887
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . (int)$session_to_db
888
    );
889 3
890 3
    if (!isset($instance[$connection])) {
891
      $instance[$connection] = new self(
892 3
          $hostname,
893 1
          $username,
894 1
          $password,
895
          $database,
896
          $port,
897 1
          $charset,
898 1
          $exit_on_error,
899 1
          $echo_on_error,
900 1
          $logger_class_name,
901 1
          $logger_level,
902
          $session_to_db
903
      );
904 1
905 3
      if (null === $firstInstance) {
906
        $firstInstance = $instance[$connection];
907
      }
908 3
    }
909
910 3
    return $instance[$connection];
911
  }
912 1
913
  /**
914
   * Get the mysqli-link (link identifier returned by mysqli-connect).
915
   *
916
   * @return \mysqli
917 1
   */
918
  public function getLink()
919 1
  {
920
    return $this->link;
921 1
  }
922 1
923 1
  /**
924 1
   * Get the current charset.
925 1
   *
926
   * @return string
927 1
   */
928 2
  public function get_charset()
929
  {
930
    return $this->charset;
931 3
  }
932
933
  /**
934
   * Check if we are in a transaction.
935
   *
936
   * @return bool
937
   */
938
  public function inTransaction()
939 1
  {
940
    return $this->_in_transaction;
941 1
  }
942
943
  /**
944
   * Execute a "insert"-query.
945
   *
946
   * @param string      $table
947
   * @param array       $data
948
   * @param string|null $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
949
   *
950
   * @return false|int <p>false on error</p>
951
   *
952
   * @throws QueryException
953
   */
954
  public function insert($table, array $data = array(), $databaseName = null)
955
  {
956
    // init
957
    $table = trim($table);
958
959
    if ($table === '') {
960
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
961
962
      return false;
963
    }
964
965
    if (count($data) === 0) {
966
      $this->_debug->displayError('Invalid data for INSERT, data is empty.', false);
967
968
      return false;
969
    }
970
971
    $SET = $this->_parseArrayPair($data);
972
973
    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...
974
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
975
    }
976
977
    $sql = 'INSERT INTO ' . $databaseName . $this->quote_string($table) . " SET $SET;";
978
979
    return $this->query($sql);
980
  }
981
982
  /**
983
   * Returns the auto generated id used in the last query.
984
   *
985
   * @return int|string
986
   */
987
  public function insert_id()
988
  {
989
    return \mysqli_insert_id($this->link);
990
  }
991
992
  /**
993
   * Check if db-connection is ready.
994
   *
995
   * @return boolean
996 7
   */
997
  public function isReady()
998 7
  {
999 7
    return $this->connected ? true : false;
1000 5
  }
1001 5
1002 7
  /**
1003 5
   * Get the last sql-error.
1004 5
   *
1005
   * @return string|false <p>false === there was no error</p>
1006 7
   */
1007
  public function lastError()
1008 7
  {
1009
    $errors = $this->_debug->getErrors();
1010
1011 7
    return count($errors) > 0 ? end($errors) : false;
1012
  }
1013
1014 7
  /**
1015
   * Execute a sql-multi-query.
1016 7
   *
1017
   * @param string $sql
1018
   *
1019
   * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
1020
   *                        "boolean" by only "<b>INSERT</b>"-queries<br />
1021
   *                        "boolean" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1022
   *                        "boolean" by only by e.g. "DROP"-queries<br />
1023
   *
1024
   * @throws QueryException
1025
   */
1026 1
  public function multi_query($sql)
1027
  {
1028 1
    if (!$this->isReady()) {
1029 1
      return false;
1030
    }
1031
1032 View Code Duplication
    if (!$sql || $sql === '') {
1033
      $this->_debug->displayError('Can not execute an empty query.', false);
1034
1035
      return false;
1036 1
    }
1037
1038 1
    $query_start_time = microtime(true);
1039 1
    $resultTmp = \mysqli_multi_query($this->link, $sql);
1040
    $query_duration = microtime(true) - $query_start_time;
1041 1
1042
    $this->_debug->logQuery($sql, $query_duration, 0);
1043
1044
    $returnTheResult = false;
1045
    $result = array();
1046
    if ($resultTmp) {
1047
      do {
1048
        $resultTmpInner = \mysqli_store_result($this->link);
1049
1050
        if ($resultTmpInner instanceof \mysqli_result) {
1051
          $returnTheResult = true;
1052
          $result[] = new Result($sql, $resultTmpInner);
1053
        } else {
1054
          $errorMsg = \mysqli_error($this->link);
1055
1056 1
          // is the query successful
1057
          if ($resultTmpInner === true || !$errorMsg) {
1058 1
            $result[] = true;
1059
          } else {
1060
            $result[] = $this->queryErrorHandling($errorMsg, $sql);
1061
          }
1062 1
        }
1063 1
      } while (\mysqli_more_results($this->link) === true ? \mysqli_next_result($this->link) : false);
1064
1065 1
    } else {
1066
1067
      $errorMsg = \mysqli_error($this->link);
1068 1
1069 1
      if ($this->_debug->checkForDev() === true) {
1070 1
        echo "Info: maybe you have to increase your 'max_allowed_packet = 30M' in the config: 'my.conf' \n<br />";
1071
        echo 'Error:' . $errorMsg;
1072 1
      }
1073
1074 1
      $this->_debug->mailToAdmin('SQL-Error in mysqli_multi_query', $errorMsg . ":\n<br />" . $sql);
1075 1
    }
1076 1
1077
    // return the result only if there was a "SELECT"-query
1078 1
    if ($returnTheResult === true) {
1079
      return $result;
1080 1
    }
1081 1
1082 1
    if (in_array(false, $result, true) === false) {
1083 1
      return true;
1084 1
    }
1085
1086
    return false;
1087 1
  }
1088 1
1089 1
  /**
1090
   * Pings a server connection, or tries to reconnect
1091
   * if the connection has gone down.
1092
   *
1093 1
   * @return boolean
1094
   */
1095 1
  public function ping()
1096
  {
1097
    if (
1098
        $this->link
1099
        &&
1100
        $this->link instanceof \mysqli
1101
    ) {
1102
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
1103
      /** @noinspection UsageOfSilenceOperatorInspection */
1104
      return (bool)@\mysqli_ping($this->link);
1105
    }
1106
1107
    return false;
1108 1
  }
1109 1
1110
  /**
1111
   * Get a new "Prepare"-Object for your sql-query.
1112 1
   *
1113 1
   * @param string $query
1114
   *
1115
   * @return Prepare
1116
   */
1117
  public function prepare($query)
1118
  {
1119
    return new Prepare($this, $query);
1120
  }
1121
1122 1
  /**
1123
   * Execute a sql-query and return the result-array for select-statements.
1124 1
   *
1125 1
   * @param $query
1126
   *
1127
   * @return mixed
1128
   * @deprecated
1129
   * @throws \Exception
1130
   */
1131
  public static function qry($query)
1132 4
  {
1133
    $db = self::getInstance();
1134 4
1135
    $args = func_get_args();
1136 4
    /** @noinspection SuspiciousAssignmentsInspection */
1137 1
    $query = array_shift($args);
1138
    $query = str_replace('?', '%s', $query);
1139 1
    $args = array_map(
1140
        array(
1141
            $db,
1142 4
            'escape',
1143
        ),
1144
        $args
1145
    );
1146
    array_unshift($args, $query);
1147
    $query = call_user_func_array('sprintf', $args);
1148 4
    $result = $db->query($query);
1149 4
1150
    if ($result instanceof Result) {
1151 4
      return $result->fetchAllArray();
1152
    }
1153
1154
    return $result;
1155
  }
1156
1157
  /**
1158
   * Execute a sql-query.
1159 4
   *
1160
   * @param string        $sql            <p>The sql query-string.</p>
1161 4
   *
1162
   * @param array|boolean $params         <p>
1163
   *                                      "array" of sql-query-parameters<br/>
1164
   *                                      "false" if you don't need any parameter (default)<br/>
1165
   *                                      </p>
1166
   *
1167
   * @return bool|int|Result              <p>
1168
   *                                      "Result" by "<b>SELECT</b>"-queries<br />
1169 4
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
1170
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1171 4
   *                                      "true" by e.g. "DROP"-queries<br />
1172
   *                                      "false" on error
1173
   *                                      </p>
1174
   *
1175
   * @throws QueryException
1176
   */
1177
  public function query($sql = '', $params = false)
1178
  {
1179 2
    if (!$this->isReady()) {
1180
      return false;
1181
    }
1182 2
1183 1 View Code Duplication
    if (!$sql || $sql === '') {
1184 1
      $this->_debug->displayError('Can not execute an empty query.', false);
1185 1
1186 1
      return false;
1187 1
    }
1188
1189
    if (
1190 2
        $params !== false
1191 2
        &&
1192
        is_array($params)
1193 2
        &&
1194
        count($params) > 0
1195
    ) {
1196
      $sql = $this->_parseQueryParams($sql, $params);
1197
    }
1198
1199
    $query_start_time = microtime(true);
1200
    $query_result = \mysqli_real_query($this->link, $sql);
1201 2
    $query_duration = microtime(true) - $query_start_time;
1202
1203 2
    $this->query_count++;
1204
1205 2
    $mysqli_field_count = \mysqli_field_count($this->link);
1206
    if ($mysqli_field_count) {
1207
      $result = \mysqli_store_result($this->link);
1208
    } else {
1209
      $result = $query_result;
1210
    }
1211 2
1212
    if ($result instanceof \mysqli_result) {
1213
1214 2
      // log the select query
1215
      $this->_debug->logQuery($sql, $query_duration, $mysqli_field_count);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 1196 can also be of type array<integer,string>; however, voku\db\Debug::logQuery() 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...
1216 2
1217 2
      // return query result object
1218 2
      return new Result($sql, $result);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 1196 can also be of type array<integer,string>; however, voku\db\Result::__construct() 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...
1219 2
1220 2
    }
1221
1222 2
    if ($query_result === true) {
1223
1224
      // "INSERT" || "REPLACE"
1225 View Code Duplication
      if (preg_match('/^\s*"?(INSERT|REPLACE)\s+/i', $sql)) {
1226
        $insert_id = (int)$this->insert_id();
1227
        $this->_debug->logQuery($sql, $query_duration, $insert_id);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 1196 can also be of type array<integer,string>; however, voku\db\Debug::logQuery() 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...
1228
1229
        return $insert_id;
1230
      }
1231
1232
      // "UPDATE" || "DELETE"
1233 View Code Duplication
      if (preg_match('/^\s*"?(UPDATE|DELETE)\s+/i', $sql)) {
1234
        $affected_rows = (int)$this->affected_rows();
1235
        $this->_debug->logQuery($sql, $query_duration, $affected_rows);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 1196 can also be of type array<integer,string>; however, voku\db\Debug::logQuery() 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...
1236 21
1237
        return $affected_rows;
1238
      }
1239 21
1240
      // log the ? query
1241 21
      $this->_debug->logQuery($sql, $query_duration, 0);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 1196 can also be of type array<integer,string>; however, voku\db\Debug::logQuery() 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...
1242 2
1243
      return true;
1244 2
    }
1245
1246
    // log the error query
1247 20
    $this->_debug->logQuery($sql, $query_duration, 0, true);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 1196 can also be of type array<integer,string>; however, voku\db\Debug::logQuery() 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...
1248 3
1249
    return $this->queryErrorHandling(\mysqli_error($this->link), $sql, $params);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 1196 can also be of type array<integer,string>; however, voku\db\DB::queryErrorHandling() 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...
1250 3
  }
1251
1252
  /**
1253 18
   * Error-handling for the sql-query.
1254
   *
1255 18
   * @param string     $errorMsg
1256
   * @param string     $sql
1257
   * @param array|bool $sqlParams false if there wasn't any parameter
1258
   *
1259 18
   * @throws QueryException
1260
   * @throws DBGoneAwayException
1261 18
   *
1262
   * @return bool
1263
   */
1264 View Code Duplication
  protected function queryErrorHandling($errorMsg, $sql, $sqlParams = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1265
  {
1266
    if ($errorMsg === 'DB server has gone away' || $errorMsg === 'MySQL server has gone away') {
1267
      static $RECONNECT_COUNTER;
1268
1269
      // exit if we have more then 3 "DB server has gone away"-errors
1270
      if ($RECONNECT_COUNTER > 3) {
1271
        $this->_debug->mailToAdmin('DB-Fatal-Error', $errorMsg . ":\n<br />" . $sql, 5);
1272
        throw new DBGoneAwayException($errorMsg);
1273
      }
1274 23
1275
      $this->_debug->mailToAdmin('DB-Error', $errorMsg . ":\n<br />" . $sql);
1276
1277 23
      // reconnect
1278
      $RECONNECT_COUNTER++;
1279
      $this->reconnect(true);
1280 23
1281
      // re-run the current query
1282
      return $this->query($sql, $sqlParams);
1283
    }
1284 23
1285 23
    $this->_debug->mailToAdmin('SQL-Error', $errorMsg . ":\n<br />" . $sql);
1286 23
1287 23
    // this query returned an error, we must display it (only for dev) !!!
1288 23
    $this->_debug->displayError($errorMsg . ' | ' . $sql);
1289
1290 23
    return false;
1291 2
  }
1292 2
1293
  /**
1294 23
   * Quote && Escape e.g. a table name string.
1295 1
   *
1296 1
   * @param string $str
1297
   *
1298 23
   * @return string
1299 1
   */
1300 1
  public function quote_string($str)
1301
  {
1302 23
    $str = str_replace(
1303 1
        '`',
1304 1
        '``',
1305
        trim(
1306 23
            $this->escape($str, false),
1307 1
            '`'
1308 1
        )
1309
    );
1310 23
1311 1
    return '`' . $str . '`';
1312 1
  }
1313
1314 23
  /**
1315 1
   * Reconnect to the MySQL-Server.
1316 1
   *
1317
   * @param bool $checkViaPing
1318 23
   *
1319 2
   * @return bool
1320 2
   */
1321
  public function reconnect($checkViaPing = false)
1322 23
  {
1323 2
    $ping = false;
1324 2
1325
    if ($checkViaPing === true) {
1326 23
      $ping = $this->ping();
1327 4
    }
1328 4
1329
    if ($ping !== true) {
1330 23
      $this->connected = false;
1331 1
      $this->connect();
1332 1
    }
1333
1334 23
    return $this->isReady();
1335 4
  }
1336 4
1337
  /**
1338 23
   * Execute a "replace"-query.
1339 1
   *
1340 1
   * @param string      $table
1341
   * @param array       $data
1342 23
   * @param null|string $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1343 1
   *
1344 1
   * @return false|int <p>false on error</p>
1345
   *
1346 23
   * @throws QueryException
1347 2
   */
1348 2
  public function replace($table, array $data = array(), $databaseName = null)
1349
  {
1350 23
    // init
1351 1
    $table = trim($table);
1352 1
1353
    if ($table === '') {
1354 23
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1355 2
1356 2
      return false;
1357 2
    }
1358
1359 2
    if (count($data) === 0) {
1360 1
      $this->_debug->displayError('Invalid data for REPLACE, data is empty.', false);
1361 2
1362 1
      return false;
1363 1
    }
1364
1365 2
    // extracting column names
1366 23
    $columns = array_keys($data);
1367
    foreach ($columns as $k => $_key) {
1368
      /** @noinspection AlterInForeachInspection */
1369 23
      $columns[$k] = $this->quote_string($_key);
1370 23
    }
1371 23
1372
    $columns = implode(',', $columns);
1373 23
1374 23
    // extracting values
1375 23
    foreach ($data as $k => $_value) {
1376 23
      /** @noinspection AlterInForeachInspection */
1377
      $data[$k] = $this->secure($_value);
1378 23
    }
1379 23
    $values = implode(',', $data);
1380 23
1381
    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...
1382 23
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1383 23
    }
1384 23
1385
    $sql = 'REPLACE INTO ' . $databaseName . $this->quote_string($table) . " ($columns) VALUES ($values);";
1386 23
1387 23
    return $this->query($sql);
1388 23
  }
1389
1390 23
  /**
1391 23
   * Rollback in a transaction.
1392
   */
1393 23
  public function rollback()
1394
  {
1395 23
    // init
1396
    $return = false;
1397 23
1398 1
    if ($this->_in_transaction === true) {
1399 23
      $return = \mysqli_rollback($this->link);
1400 23
      \mysqli_autocommit($this->link, true);
1401 23
      $this->_in_transaction = false;
1402
    }
1403 23
1404 1
    return $return;
1405 1
  }
1406
1407 23
  /**
1408 23
   * Try to secure a variable, so can you use it in sql-queries.
1409 23
   *
1410
   * <p>
1411 23
   * <strong>int:</strong> (also strings that contains only an int-value)<br />
1412 2
   * 1. parse into "int"
1413 2
   * </p><br />
1414
   *
1415 23
   * <p>
1416 23
   * <strong>float:</strong><br />
1417
   * 1. return "float"
1418 23
   * </p><br />
1419
   *
1420
   * <p>
1421
   * <strong>string:</strong><br />
1422
   * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'<br />
1423
   * 2. trim whitespace<br />
1424
   * 3. trim '<br />
1425
   * 4. escape the string (and remove non utf-8 chars)<br />
1426
   * 5. trim ' again (because we maybe removed some chars)<br />
1427
   * 6. add ' around the new string<br />
1428 26
   * </p><br />
1429
   *
1430 26
   * <p>
1431 26
   * <strong>array:</strong><br />
1432 26
   * 1. return null
1433 26
   * </p><br />
1434 26
   *
1435
   * <p>
1436 26
   * <strong>object:</strong><br />
1437 26
   * 1. return false
1438
   * </p><br />
1439 26
   *
1440
   * <p>
1441
   * <strong>null:</strong><br />
1442
   * 1. return null
1443
   * </p>
1444
   *
1445
   * @param mixed $var
1446
   *
1447 1
   * @return mixed
1448
   */
1449 1
  public function secure($var)
1450
  {
1451
    if (
1452
        $var === ''
1453
        ||
1454
        ($this->_convert_null_to_empty_string === true && $var === null)
1455
    ) {
1456
      return "''";
1457
    }
1458
1459
    if (in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
1460
      return $var;
1461
    }
1462
1463 1
    if (is_string($var)) {
1464
      $var = trim(trim($var), "'");
1465
    }
1466 1
1467
    $var = $this->escape($var, false, false, null);
1468 1
1469 1
    if (is_string($var)) {
1470
      $var = "'" . trim($var, "'") . "'";
1471 1
    }
1472
1473
    return $var;
1474 1
  }
1475 1
1476
  /**
1477 1
   * Execute a "select"-query.
1478
   *
1479
   * @param string       $table
1480
   * @param string|array $where
1481 1
   * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1482 1
   *
1483
   * @return false|Result <p>false on error</p>
1484 1
   *
1485 1
   * @throws QueryException
1486
   */
1487 1 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...
1488
  {
1489
    // init
1490 1
    $table = trim($table);
1491
1492 1
    if ($table === '') {
1493 1
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1494 1
1495
      return false;
1496 1
    }
1497
1498
    if (is_string($where)) {
1499
      $WHERE = $this->escape($where, false);
1500 1
    } elseif (is_array($where)) {
1501
      $WHERE = $this->_parseArrayPair($where, 'AND');
1502 1
    } else {
1503
      $WHERE = '';
1504
    }
1505
1506
    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...
1507
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1508
    }
1509
1510
    $sql = 'SELECT * FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE);";
1511
1512
    return $this->query($sql);
1513
  }
1514
1515
  /**
1516
   * Set the current charset.
1517 6
   *
1518
   * @param string $charset
1519
   *
1520 6
   * @return bool
1521
   */
1522 6
  public function set_charset($charset)
1523 1
  {
1524
    $charsetLower = strtolower($charset);
1525 1
    if ($charsetLower === 'utf8' || $charsetLower === 'utf-8') {
1526
      $charset = 'utf8';
1527
    }
1528 6
    if ($charset === 'utf8' && Helper::isUtf8mb4Supported($this) === true) {
1529 2
      $charset = 'utf8mb4';
1530
    }
1531 2
1532
    $this->charset = (string)$charset;
1533
1534 6
    $return = mysqli_set_charset($this->link, $charset);
1535
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
1536 6
    /** @noinspection UsageOfSilenceOperatorInspection */
1537 2
    @\mysqli_query($this->link, 'SET CHARACTER SET ' . $charset);
1538 6
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
1539 4
    /** @noinspection UsageOfSilenceOperatorInspection */
1540 4
    @\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...
1541 1
1542
    return $return;
1543
  }
1544 6
1545
  /**
1546
   * Set the option to convert null to "''" (empty string).
1547
   *
1548 6
   * Used in secure() => select(), insert(), update(), delete()
1549
   *
1550 6
   * @param $bool
1551
   */
1552
  public function set_convert_null_to_empty_string($bool)
1553
  {
1554
    $this->_convert_null_to_empty_string = (bool)$bool;
1555
  }
1556
1557
  /**
1558
   * Enables or disables internal report functions
1559
   *
1560
   * @link http://php.net/manual/en/function.mysqli-report.php
1561
   *
1562
   * @param int $flags <p>
1563
   *                   <table>
1564 2
   *                   Supported flags
1565
   *                   <tr valign="top">
1566
   *                   <td>Name</td>
1567 2
   *                   <td>Description</td>
1568
   *                   </tr>
1569 2
   *                   <tr valign="top">
1570 1
   *                   <td><b>MYSQLI_REPORT_OFF</b></td>
1571
   *                   <td>Turns reporting off</td>
1572 1
   *                   </tr>
1573
   *                   <tr valign="top">
1574
   *                   <td><b>MYSQLI_REPORT_ERROR</b></td>
1575 2
   *                   <td>Report errors from mysqli function calls</td>
1576 1
   *                   </tr>
1577 2
   *                   <tr valign="top">
1578 2
   *                   <td><b>MYSQLI_REPORT_STRICT</b></td>
1579 2
   *                   <td>
1580 1
   *                   Throw <b>mysqli_sql_exception</b> for errors
1581
   *                   instead of warnings
1582
   *                   </td>
1583 2
   *                   </tr>
1584
   *                   <tr valign="top">
1585
   *                   <td><b>MYSQLI_REPORT_INDEX</b></td>
1586
   *                   <td>Report if no index or bad index was used in a query</td>
1587 2
   *                   </tr>
1588
   *                   <tr valign="top">
1589 2
   *                   <td><b>MYSQLI_REPORT_ALL</b></td>
1590
   *                   <td>Set all options (report all)</td>
1591
   *                   </tr>
1592
   *                   </table>
1593
   *                   </p>
1594
   *
1595
   * @return bool
1596
   */
1597
  public function set_mysqli_report($flags)
1598
  {
1599
    return \mysqli_report($flags);
1600
  }
1601
1602
  /**
1603 20
   * Show config errors by throw exceptions.
1604
   *
1605
   * @return bool
1606 20
   *
1607
   * @throws \InvalidArgumentException
1608 20
   */
1609 1
  public function showConfigError()
1610
  {
1611 1
1612
    if (
1613
        !$this->hostname
1614 20
        ||
1615 5
        !$this->username
1616 20
        ||
1617 16
        !$this->database
1618 16
    ) {
1619 1
1620
      if (!$this->hostname) {
1621
        throw new \InvalidArgumentException('no-sql-hostname');
1622 20
      }
1623
1624
      if (!$this->username) {
1625
        throw new \InvalidArgumentException('no-sql-username');
1626 20
      }
1627
1628 20
      if (!$this->database) {
1629
        throw new \InvalidArgumentException('no-sql-database');
1630
      }
1631
1632
      return false;
1633
    }
1634
1635
    return true;
1636 1
  }
1637
1638 1
  /**
1639
   * alias: "beginTransaction()"
1640 1
   */
1641
  public function startTransaction()
1642
  {
1643
    $this->beginTransaction();
1644
  }
1645
1646 9
  /**
1647
   * Execute a "update"-query.
1648 9
   *
1649
   * @param string       $table
1650
   * @param array        $data
1651
   * @param array|string $where
1652
   * @param null|string  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1653
   *
1654
   * @return false|int <p>false on error</p>
1655
   *
1656
   * @throws QueryException
1657
   */
1658
  public function update($table, array $data = array(), $where = '1=1', $databaseName = null)
1659
  {
1660
    // init
1661
    $table = trim($table);
1662
1663
    if ($table === '') {
1664
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1665
1666 2
      return false;
1667
    }
1668 2
1669
    if (count($data) === 0) {
1670 2
      $this->_debug->displayError('Invalid data for UPDATE, data is empty.', false);
1671 2
1672 2
      return false;
1673 2
    }
1674
1675
    $SET = $this->_parseArrayPair($data);
1676
1677
    if (is_string($where)) {
1678
      $WHERE = $this->escape($where, false);
1679
    } elseif (is_array($where)) {
1680
      $WHERE = $this->_parseArrayPair($where, 'AND');
1681
    } else {
1682
      $WHERE = '';
1683
    }
1684
1685
    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...
1686
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1687
    }
1688
1689 2
    $sql = 'UPDATE ' . $databaseName . $this->quote_string($table) . " SET $SET WHERE ($WHERE);";
1690
1691 2
    return $this->query($sql);
1692 2
  }
1693
1694
}
1695