Completed
Pull Request — master (#14)
by Lars
03:00
created

DB::showConfigError()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 7.0145

Importance

Changes 9
Bugs 0 Features 4
Metric Value
c 9
b 0
f 4
dl 0
loc 28
ccs 14
cts 15
cp 0.9333
rs 6.7272
cc 7
eloc 15
nc 5
nop 0
crap 7.0145
1
<?php
2
3
namespace voku\db;
4
5
use voku\cache\Cache;
6
use voku\helper\UTF8;
7
8
/**
9
 * DB: this handles DB queries via MySQLi
10
 *
11
 * @package   voku\db
12
 */
13
final class DB
14
{
15
16
  /**
17
   * @var int
18
   */
19
  public $query_count = 0;
20
21
  /**
22
   * @var \mysqli
23
   */
24
  private $link = false;
25
26
  /**
27
   * @var bool
28
   */
29
  private $connected = false;
30
31
  /**
32
   * @var array
33
   */
34
  private $mysqlDefaultTimeFunctions;
35
36
  /**
37
   * @var string
38
   */
39
  private $hostname = '';
40
41
  /**
42
   * @var string
43
   */
44
  private $username = '';
45
46
  /**
47
   * @var string
48
   */
49
  private $password = '';
50
51
  /**
52
   * @var string
53
   */
54
  private $database = '';
55
56
  /**
57
   * @var int
58
   */
59
  private $port = 3306;
60
61
  /**
62
   * @var string
63
   */
64
  private $charset = 'utf8';
65
66
  /**
67
   * @var string
68
   */
69
  private $socket = '';
70
71
  /**
72
   * @var bool
73
   */
74
  private $session_to_db = false;
75
76
  /**
77
   * @var bool
78
   */
79
  private $_in_transaction = false;
80
81
  /**
82
   * @var Debug
83
   */
84
  private $_debug;
85
86
  /**
87
   * __construct()
88
   *
89
   * @param string         $hostname
90
   * @param string         $username
91
   * @param string         $password
92
   * @param string         $database
93
   * @param int            $port
94
   * @param string         $charset
95
   * @param boolean|string $exit_on_error use a empty string "" or false to disable it
96
   * @param boolean|string $echo_on_error use a empty string "" or false to disable it
97
   * @param string         $logger_class_name
98
   * @param string         $logger_level  'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'
99
   * @param boolean|string $session_to_db use a empty string "" or false to disable it
100
   */
101 10
  protected function __construct($hostname, $username, $password, $database, $port, $charset, $exit_on_error, $echo_on_error, $logger_class_name, $logger_level, $session_to_db)
102
  {
103 10
    $this->connected = false;
104
105 10
    $this->_debug = new Debug($this);
106
107 10
    $this->_loadConfig(
108 10
        $hostname,
109 10
        $username,
110 10
        $password,
111 10
        $database,
112 10
        $port,
113 10
        $charset,
114 10
        $exit_on_error,
115 10
        $echo_on_error,
116 10
        $logger_class_name,
117 10
        $logger_level,
118
        $session_to_db
119 10
    );
120
121 7
    $this->connect();
122
123 4
    $this->mysqlDefaultTimeFunctions = array(
124
      // Returns the current date.
125 4
      'CURDATE()',
126
      // CURRENT_DATE	| Synonyms for CURDATE()
127 4
      'CURRENT_DATE()',
128
      // CURRENT_TIME	| Synonyms for CURTIME()
129 4
      'CURRENT_TIME()',
130
      // CURRENT_TIMESTAMP | Synonyms for NOW()
131 4
      'CURRENT_TIMESTAMP()',
132
      // Returns the current time.
133 4
      'CURTIME()',
134
      // Synonym for NOW()
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
135 4
      'LOCALTIME()',
136
      // Synonym for NOW()
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
137 4
      'LOCALTIMESTAMP()',
138
      // Returns the current date and time.
139 4
      'NOW()',
140
      // Returns the time at which the function executes.
141 4
      'SYSDATE()',
142
      // Returns a UNIX timestamp.
143 4
      'UNIX_TIMESTAMP()',
144
      // Returns the current UTC date.
145 4
      'UTC_DATE()',
146
      // Returns the current UTC time.
147 4
      'UTC_TIME()',
148
      // Returns the current UTC date and time.
149 4
      'UTC_TIMESTAMP()',
150
    );
151 4
  }
152
153
  /**
154
   * Load the config from the constructor.
155
   *
156
   * @param string         $hostname
157
   * @param string         $username
158
   * @param string         $password
159
   * @param string         $database
160
   * @param int            $port
161
   * @param string         $charset
162
   * @param boolean|string $exit_on_error use a empty string "" or false to disable it
163
   * @param boolean|string $echo_on_error use a empty string "" or false to disable it
164
   * @param string         $logger_class_name
165
   * @param string         $logger_level
166
   * @param boolean|string $session_to_db use a empty string "" or false to disable it
167
   *
168
   * @return bool
169
   */
170 10
  private function _loadConfig($hostname, $username, $password, $database, $port, $charset, $exit_on_error, $echo_on_error, $logger_class_name, $logger_level, $session_to_db)
171
  {
172 10
    $this->hostname = (string)$hostname;
173 10
    $this->username = (string)$username;
174 10
    $this->password = (string)$password;
175 10
    $this->database = (string)$database;
176
177 10
    if ($charset) {
178 4
      $this->charset = (string)$charset;
179 4
    }
180
181 10
    if ($port) {
182 4
      $this->port = (int)$port;
183 4
    } else {
184
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
185 7
      $this->port = @ini_get('mysqli.default_port');
186
    }
187
188 10
    if (!$this->socket) {
189
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
190 10
      $this->socket = @ini_get('mysqli.default_socket');
191 10
    }
192
193 10
    if ($exit_on_error === true || $exit_on_error === false) {
194 10
      $this->_debug->setExitOnError($exit_on_error);
195 10
    }
196
197 10
    if ($echo_on_error === true || $echo_on_error === false) {
198 10
      $this->_debug->setEchoOnError($echo_on_error);
199 10
    }
200
201 10
    $this->_debug->setLoggerClassName($logger_class_name);
202 10
    $this->_debug->setLoggerLevel($logger_level);
203
204 10
    $this->session_to_db = (boolean)$session_to_db;
205
206 10
    return $this->showConfigError();
207
  }
208
209
  /**
210
   * Show config errors by throw exceptions.
211
   *
212
   * @return bool
213
   *
214
   * @throws \Exception
215
   */
216 10
  public function showConfigError()
217
  {
218
219
    if (
220 10
        !$this->hostname
221 10
        ||
222 9
        !$this->username
223 9
        ||
224 8
        !$this->database
225 10
    ) {
226
227 3
      if (!$this->hostname) {
228 1
        throw new \Exception('no-sql-hostname');
229
      }
230
231 2
      if (!$this->username) {
232 1
        throw new \Exception('no-sql-username');
233
      }
234
235 1
      if (!$this->database) {
236 1
        throw new \Exception('no-sql-database');
237
      }
238
239
      return false;
240
    }
241
242 7
    return true;
243
  }
244
245
  /**
246
   * Open a new connection to the MySQL server.
247
   *
248
   * @return boolean
249
   */
250 8
  public function connect()
251
  {
252 8
    if ($this->isReady()) {
253 1
      return true;
254
    }
255
256 8
    mysqli_report(MYSQLI_REPORT_STRICT);
257
    try {
258 8
      $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...
259
260 8
      $this->connected = @mysqli_real_connect(
261 8
          $this->link,
262 8
          $this->hostname,
263 8
          $this->username,
264 8
          $this->password,
265 8
          $this->database,
266 8
          $this->port,
267 8
          $this->socket
268 8
      );
269 8
    } catch (\Exception $e) {
270 3
      $this->_debug->displayError('Error connecting to mysql server: ' . $e->getMessage(), true);
271
    }
272 5
    mysqli_report(MYSQLI_REPORT_OFF);
273
274 5
    if (!$this->connected) {
275
      $this->_debug->displayError('Error connecting to mysql server: ' . mysqli_connect_error(), true);
276
    } else {
277 5
      $this->set_charset($this->charset);
278
    }
279
280 5
    return $this->isReady();
281
  }
282
283
  /**
284
   * Check if db-connection is ready.
285
   *
286
   * @return boolean
287
   */
288 33
  public function isReady()
289
  {
290 33
    return $this->connected ? true : false;
291
  }
292
293
  /**
294
   * Get a new "Prepare"-Object for your sql-query.
295
   *
296
   * @param string $query
297
   *
298
   * @return Prepare
299
   */
300
  public function prepare($query)
301
  {
302
    return new Prepare($this, $query);
303
  }
304
305
  /**
306
   * Execute a sql-query and return the result-array for select-statements.
307
   *
308
   * @param $query
309
   *
310
   * @return mixed
311
   * @deprecated
312
   * @throws \Exception
313
   */
314
  public static function qry($query)
315
  {
316
    $db = self::getInstance();
317
318
    $args = func_get_args();
319
    $query = array_shift($args);
320
    $query = str_replace('?', '%s', $query);
321
    $args = array_map(
322
        array(
323
            $db,
324
            'escape',
325
        ),
326
        $args
327
    );
328
    array_unshift($args, $query);
329
    $query = call_user_func_array('sprintf', $args);
330
    $result = $db->query($query);
331
332
    if ($result instanceof Result) {
333
      $return = $result->fetchAllArray();
334
    } else {
335
      $return = $result;
336
    }
337
338
    if ($return || is_array($return)) {
339
      return $return;
340
    } else {
341
      return false;
342
    }
343
  }
344
345
  /**
346
   * getInstance()
347
   *
348
   * @param string      $hostname
349
   * @param string      $username
350
   * @param string      $password
351
   * @param string      $database
352
   * @param string      $port          default is (int)3306
353
   * @param string      $charset       default is 'utf8', but if you need 4-byte chars, then your tables need
354
   *                                   the 'utf8mb4'-charset
355
   * @param bool|string $exit_on_error use a empty string "" or false to disable it
356
   * @param bool|string $echo_on_error use a empty string "" or false to disable it
357
   * @param string      $logger_class_name
358
   * @param string      $logger_level
359
   * @param bool|string $session_to_db use a empty string "" or false to disable it
360
   *
361
   * @return \voku\db\DB
362
   */
363 43
  public static function getInstance($hostname = '', $username = '', $password = '', $database = '', $port = '', $charset = '', $exit_on_error = '', $echo_on_error = '', $logger_class_name = '', $logger_level = '', $session_to_db = '')
364
  {
365
    /**
366
     * @var $instance DB[]
367
     */
368 43
    static $instance = array();
369
370
    /**
371
     * @var $firstInstance DB
372
     */
373 43
    static $firstInstance = null;
374
375
    if (
376 43
        $hostname . $username . $password . $database . $port . $charset == ''
377 43
        &&
378 9
        null !== $firstInstance
379 43
    ) {
380 9
      return $firstInstance;
381
    }
382
383 43
    $connection = md5(
384 43
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . (int)$session_to_db
385 43
    );
386
387 43
    if (!isset($instance[$connection])) {
388 10
      $instance[$connection] = new self(
389 10
          $hostname,
390 10
          $username,
391 10
          $password,
392 10
          $database,
393 10
          $port,
394 10
          $charset,
395 10
          $exit_on_error,
396 10
          $echo_on_error,
397 10
          $logger_class_name,
398 10
          $logger_level,
399
          $session_to_db
400 10
      );
401
402 4
      if (null === $firstInstance) {
403 1
        $firstInstance = $instance[$connection];
404 1
      }
405 4
    }
406
407 43
    return $instance[$connection];
408
  }
409
410
  /**
411
   * Execute a sql-query.
412
   *
413
   * @param string        $sql            sql-query
414
   *
415
   * @param array|boolean $params         "array" of sql-query-parameters
416
   *                                      "false" if you don't need any parameter (default)
417
   *
418
   * @return bool|int|Result              "Result" by "<b>SELECT</b>"-queries<br />
419
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
420
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
421
   *                                      "true" by e.g. "DROP"-queries<br />
422
   *                                      "false" on error
423
   *
424
   * @throws \Exception
425
   */
426 24
  public function query($sql = '', $params = false)
427
  {
428 24
    if (!$this->isReady()) {
429
      return false;
430
    }
431
432 24 View Code Duplication
    if (!$sql || $sql === '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
433 4
      $this->_debug->displayError('Can\'t execute an empty Query', false);
434
435 4
      return false;
436
    }
437
438
    if (
439
        $params !== false
440 22
        &&
441 2
        is_array($params)
442 22
        &&
443 2
        count($params) > 0
444 22
    ) {
445 2
      $sql = $this->_parseQueryParams($sql, $params);
446 2
    }
447
448 22
    $query_start_time = microtime(true);
449 22
    $result = mysqli_query($this->link, $sql);
450 22
    $query_duration = microtime(true) - $query_start_time;
451 22
    $this->query_count++;
452
453 22
    $resultCount = 0;
454 22
    if ($result instanceof \mysqli_result) {
455 19
      $resultCount = (int)$result->num_rows;
456 19
    }
457 22
    $this->_debug->logQuery($sql, $query_duration, $resultCount);
458
459 22
    if ($result instanceof \mysqli_result) {
460
461
      // return query result object
462 19
      return new Result($sql, $result);
463
464
    } else {
465
466
      // is the query successful
467 18
      if ($result === true) {
468
469 16
        if (preg_match('/^\s*"?(INSERT|UPDATE|DELETE|REPLACE)\s+/i', $sql)) {
470
471
          // it is an "INSERT" || "REPLACE"
472 16
          if ($this->insert_id() > 0) {
473 15
            return (int)$this->insert_id();
474
          }
475
476
          // it is an "UPDATE" || "DELETE"
477 7
          if ($this->affected_rows() > 0) {
478 7
            return (int)$this->affected_rows();
479
          }
480
        }
481
482
        return true;
483
      } else {
484 8
        $this->queryErrorHandling(mysqli_error($this->link), $sql, $params);
485
      }
486
    }
487
488 8
    return false;
489
  }
490
491
  /**
492
   * _parseQueryParams
493
   *
494
   * @param string $sql
495
   * @param array  $params
496
   *
497
   * @return string
498
   */
499 2
  private function _parseQueryParams($sql, array $params)
500
  {
501
    // is there anything to parse?
502 2
    if (strpos($sql, '?') === false) {
503
      return $sql;
504
    }
505
506 2
    if (count($params) > 0) {
507 2
      $parseKey = md5(uniqid(mt_rand(), true));
508 2
      $sql = str_replace('?', $parseKey, $sql);
509
510 2
      $k = 0;
511 2
      while (strpos($sql, $parseKey) !== false) {
512 2
        $value = $this->secure($params[$k]);
513 2
        $sql = preg_replace("/$parseKey/", $value, $sql, 1);
514 2
        $k++;
515 2
      }
516 2
    }
517
518 2
    return $sql;
519
  }
520
521
  /**
522
   * Try to secure a variable, so can you use it in sql-queries.
523
   *
524
   * int: (also strings that contains only an int-value)
525
   * 1. parse into (int)
526
   *
527
   * strings:
528
   * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'
529
   * 2. trim whitespace
530
   * 3. trim '
531
   * 4. escape the string (and remove non utf-8 chars)
532
   * 5. trim ' again (because we maybe removed some chars)
533
   * 6. add ' around the new string
534
   *
535
   * @param mixed $var
536
   *
537
   * @return string | null
538
   */
539 15
  public function secure($var)
540
  {
541
    // save the current value as int (for later usage)
542 15
    if (!is_object($var)) {
543 15
      $varInt = (int)$var;
544 15
    }
545
546
    /** @noinspection TypeUnsafeComparisonInspection */
547
    if (
548 15
        is_int($var)
549
        ||
550 15
        is_bool($var)
551 15
        ||
552 15
        (isset($varInt, $var[0]) && $var[0] != '0' && "$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...
553 15
    ) {
554
555
      // "int" || int || bool
556
557 13
      $var = (int)$var;
558
559 15
    } elseif (is_string($var)) {
560
561
      // "string"
562
563 15
      if (!in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
564 15
        $var = "'" . trim($this->escape(trim(trim((string)$var), "'")), "'") . "'";
565 15
      }
566
567 15 View Code Duplication
    } elseif (is_float($var)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
568
569
      // float
570
571 2
      $var = number_format((float)str_replace(',', '.', $var), 8, '.', '');
572
573 2
    } elseif (is_array($var)) {
574
575
      // array
576
577 1
      $var = null;
578
579 1
    } elseif ($var instanceof \DateTime) {
580
581
      // "DateTime"-object
582
583
      try {
584 1
        $var = "'" . $this->escape($var->format('Y-m-d H:i:s'), false, false) . "'";
585 1
      } catch (\Exception $e) {
586
        $var = null;
587
      }
588
589 1
    } else {
590
591
      // fallback ...
592
593
      $var = "'" . trim($this->escape(trim(trim((string)$var), "'")), "'") . "'";
594
595
    }
596
597 15
    return $var;
598
  }
599
600
  /**
601
   * Escape
602
   *
603
   * @param mixed $var boolean: convert into "integer"<br />
604
   *                   int: convert into "integer"<br />
605
   *                   float: convert into "float" and replace "," with "."<br />
606
   *                   array: run escape() for every key => value<br />
607
   *                   string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
608
   * @param bool  $stripe_non_utf8
609
   * @param bool  $html_entity_decode
610
   * @param bool  $array_to_string
611
   *
612
   * @return array|bool|float|int|string
613
   */
614 19
  public function escape($var = '', $stripe_non_utf8 = true, $html_entity_decode = true, $array_to_string = false)
615
  {
616
    // save the current value as int (for later usage)
617 19
    if (!is_object($var)) {
618 19
      $varInt = (int)$var;
619 19
    }
620
621
    /** @noinspection TypeUnsafeComparisonInspection */
622
    if (
623 19
        is_int($var)
624
        ||
625 19
        is_bool($var)
626 19
        ||
627 19
        (isset($varInt, $var[0]) && $var[0] != '0' && "$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...
628 19
    ) {
629
630
      // "int" || int || bool
631
632 4
      return (int)$var;
633
634 19 View Code Duplication
    } elseif (is_float($var)) {
635
636
      // float
637
638 1
      return number_format((float)str_replace(',', '.', $var), 8, '.', '');
639
640 19
    } elseif (is_array($var)) {
641
642
      // array
643
644 1
      $varCleaned = array();
645 1
      foreach ($var as $key => $value) {
646
647 1
        $key = (string)$this->escape($key, $stripe_non_utf8, $html_entity_decode);
648 1
        $value = (string)$this->escape($value, $stripe_non_utf8, $html_entity_decode);
649
650 1
        $varCleaned[$key] = $value;
651 1
      }
652
653 1
      if ($array_to_string === true) {
654 1
        $varCleaned = implode(',', $varCleaned);
655
656 1
        return $varCleaned;
657
      } else {
658 1
        return (array)$varCleaned;
659
      }
660
    }
661
662 19
    if (is_string($var)) {
663
664
      // "string"
665
666 19
      if ($stripe_non_utf8 === true) {
667 19
        $var = UTF8::cleanup($var);
668 19
      }
669
670 19
      if ($html_entity_decode === true) {
671
        // use no-html-entity for db
672 19
        $var = UTF8::html_entity_decode($var);
673 19
      }
674
675 19
      $var = get_magic_quotes_gpc() ? stripslashes($var) : $var;
676
677 19
      $var = mysqli_real_escape_string($this->getLink(), $var);
678
679 19
      return (string)$var;
680
    } else {
681
      return false;
682
    }
683
  }
684
685
  /**
686
   * Get the mysqli-link (link identifier returned by mysqli-connect).
687
   *
688
   * @return \mysqli
689
   */
690 21
  public function getLink()
691
  {
692 21
    return $this->link;
693
  }
694
695
  /**
696
   * Returns the auto generated id used in the last query.
697
   *
698
   * @return int|string
699
   */
700 16
  public function insert_id()
701
  {
702 16
    return mysqli_insert_id($this->link);
703
  }
704
705
  /**
706
   * Gets the number of affected rows in a previous MySQL operation.
707
   *
708
   * @return int
709
   */
710 7
  public function affected_rows()
711
  {
712 7
    return mysqli_affected_rows($this->link);
713
  }
714
715
  /**
716
   * Error-handling for the sql-query.
717
   *
718
   * @param string     $errorMsg
719
   * @param string     $sql
720
   * @param array|bool $sqlParams false if there wasn't any parameter
721
   *
722
   * @throws \Exception
723
   */
724 9 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...
725
  {
726 9
    if ($errorMsg === 'DB server has gone away' || $errorMsg === 'MySQL server has gone away') {
727 1
      static $reconnectCounter;
728
729
      // exit if we have more then 3 "DB server has gone away"-errors
730 1
      if ($reconnectCounter > 3) {
731
        $this->_debug->mailToAdmin('SQL-Fatal-Error', $errorMsg . ":\n<br />" . $sql, 5);
732
        throw new \Exception($errorMsg);
733
      } else {
734 1
        $this->_debug->mailToAdmin('SQL-Error', $errorMsg . ":\n<br />" . $sql);
735
736
        // reconnect
737 1
        $reconnectCounter++;
738 1
        $this->reconnect(true);
739
740
        // re-run the current query
741 1
        $this->query($sql, $sqlParams);
742
      }
743 1
    } else {
744 8
      $this->_debug->mailToAdmin('SQL-Warning', $errorMsg . ":\n<br />" . $sql);
745
746
      // this query returned an error, we must display it (only for dev) !!!
747 8
      $this->_debug->displayError($errorMsg . ' | ' . $sql);
748
    }
749 9
  }
750
751
  /**
752
   * Reconnect to the MySQL-Server.
753
   *
754
   * @param bool $checkViaPing
755
   *
756
   * @return bool
757
   */
758 2
  public function reconnect($checkViaPing = false)
759
  {
760 2
    $ping = false;
761
762 2
    if ($checkViaPing === true) {
763 2
      $ping = $this->ping();
764 2
    }
765
766 2
    if ($ping !== true) {
767 2
      $this->connected = false;
768 2
      $this->connect();
769 2
    }
770
771 2
    return $this->isReady();
772
  }
773
774
  /**
775
   * Pings a server connection, or tries to reconnect
776
   * if the connection has gone down.
777
   *
778
   * @return boolean
779
   */
780 3
  public function ping()
781
  {
782
    if (
783 3
        $this->link
784 3
        &&
785 3
        $this->link instanceof \mysqli
786 3
    ) {
787
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
788 3
      return @mysqli_ping($this->link);
789
    } else {
790
      return false;
791
    }
792
  }
793
794
  /**
795
   * Execute select/insert/update/delete sql-queries.
796
   *
797
   * @param string $query    sql-query
798
   * @param bool   $useCache use cache?
799
   * @param int    $cacheTTL cache-ttl in seconds
800
   *
801
   * @return mixed "array" by "<b>SELECT</b>"-queries<br />
802
   *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
803
   *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
804
   *               "true" by e.g. "DROP"-queries<br />
805
   *               "false" on error
806
   */
807 3
  public static function execSQL($query, $useCache = false, $cacheTTL = 3600)
808
  {
809 3
    $db = self::getInstance();
810
811 3
    if ($useCache === true) {
812 1
      $cache = new Cache(null, null, false, $useCache);
813 1
      $cacheKey = 'sql-' . md5($query);
814
815
      if (
816 1
          $cache->getCacheIsReady() === true
817 1
          &&
818 1
          $cache->existsItem($cacheKey)
819 1
      ) {
820 1
        return $cache->getItem($cacheKey);
821
      }
822
823 1
    } else {
824 3
      $cache = false;
825
    }
826
827 3
    $result = $db->query($query);
828
829 3
    if ($result instanceof Result) {
830
831 1
      $return = $result->fetchAllArray();
832
833
      if (
834 1
          isset($cacheKey)
835 1
          &&
836
          $useCache === true
837 1
          &&
838
          $cache instanceof Cache
839 1
          &&
840 1
          $cache->getCacheIsReady() === true
841 1
      ) {
842 1
        $cache->setItem($cacheKey, $return, $cacheTTL);
843 1
      }
844
845 1
    } else {
846 2
      $return = $result;
847
    }
848
849 3
    return $return;
850
  }
851
852
  /**
853
   * Get the current charset.
854
   *
855
   * @return string
856
   */
857 1
  public function get_charset()
858
  {
859 1
    return $this->charset;
860
  }
861
862
  /**
863
   * Set the current charset.
864
   *
865
   * @param string $charset
866
   *
867
   * @return bool
868
   */
869 6
  public function set_charset($charset)
870
  {
871 6
    $this->charset = (string)$charset;
872
873 6
    $return = mysqli_set_charset($this->link, $charset);
874
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
875 6
    @mysqli_query($this->link, 'SET CHARACTER SET ' . $charset);
876
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
877 6
    @mysqli_query($this->link, "SET NAMES '" . ($charset === 'utf8' ? 'utf8mb4' : $charset) . "'");
878
879 6
    return $return;
880
  }
881
882
  /**
883
   * __wakeup
884
   *
885
   * @return void
886
   */
887 1
  public function __wakeup()
888
  {
889 1
    $this->reconnect();
890 1
  }
891
892
  /**
893
   * Get all table-names via "SHOW TABLES".
894
   *
895
   * @return array
896
   */
897 1
  public function getAllTables()
898
  {
899 1
    $query = 'SHOW TABLES';
900 1
    $result = $this->query($query);
901
902 1
    return $result->fetchAllArray();
903
  }
904
905
  /**
906
   * Execute a sql-multi-query.
907
   *
908
   * @param string $sql
909
   *
910
   * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
911
   *                        "boolean" by only "<b>INSERT</b>"-queries<br />
912
   *                        "boolean" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
913
   *                        "boolean" by only by e.g. "DROP"-queries<br />
914
   *
915
   * @throws \Exception
916
   */
917 1
  public function multi_query($sql)
918
  {
919 1
    if (!$this->isReady()) {
920
      return false;
921
    }
922
923 1 View Code Duplication
    if (!$sql || $sql === '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
924 1
      $this->_debug->displayError('Can\'t execute an empty Query', false);
925
926 1
      return false;
927
    }
928
929 1
    $query_start_time = microtime(true);
930 1
    $resultTmp = mysqli_multi_query($this->link, $sql);
931 1
    $query_duration = microtime(true) - $query_start_time;
932
933 1
    $this->_debug->logQuery($sql, $query_duration, 0);
934
935 1
    $returnTheResult = false;
936 1
    $result = array();
937 1
    if ($resultTmp) {
938
      do {
939 1
        $resultTmpInner = mysqli_store_result($this->link);
940
941 1
        if ($resultTmpInner instanceof \mysqli_result) {
942 1
          $returnTheResult = true;
943 1
          $result[] = new Result($sql, $resultTmpInner);
944 1
        } else {
945 1
          $errorMsg = mysqli_error($this->link);
946
947
          // is the query successful
948 1
          if ($resultTmpInner === true || !$errorMsg) {
949 1
            $result[] = true;
950 1
          } else {
951
            $result[] = false;
952
953
            $this->queryErrorHandling($errorMsg, $sql);
954
          }
955
        }
956 1
      } while (mysqli_more_results($this->link) === true ? mysqli_next_result($this->link) : false);
957
958 1
    } else {
959
960
      $errorMsg = mysqli_error($this->link);
961
962
      if ($this->_debug->checkForDev() === true) {
963
        echo "Info: maybe you have to increase your 'max_allowed_packet = 30M' in the config: 'my.conf' \n<br />";
964
        echo 'Error:' . $errorMsg;
965
      }
966
967
      $this->_debug->mailToAdmin('SQL-Error in mysqli_multi_query', $errorMsg . ":\n<br />" . $sql);
968
    }
969
970
    // return the result only if there was a "SELECT"-query
971 1
    if ($returnTheResult === true) {
972 1
      return $result;
973
    }
974
975 1
    if (!in_array(false, $result, true)) {
976 1
      return true;
977
    } else {
978
      return false;
979
    }
980
  }
981
982
  /**
983
   * alias: "beginTransaction()"
984
   */
985 1
  public function startTransaction()
986
  {
987 1
    $this->beginTransaction();
988 1
  }
989
990
  /**
991
   * Begins a transaction, by turning off auto commit.
992
   *
993
   * @return boolean this will return true or false indicating success of transaction
994
   */
995 4
  public function beginTransaction()
996
  {
997 4
    $this->clearErrors();
998
999 4
    if ($this->inTransaction() === true) {
1000 1
      $this->_debug->displayError('Error mysql server already in transaction!', true);
1001
1002
      return false;
1003 4
    } elseif (mysqli_connect_errno()) {
1004
      $this->_debug->displayError('Error connecting to mysql server: ' . mysqli_connect_error(), true);
1005
1006
      return false;
1007
    } else {
1008 4
      $this->_in_transaction = true;
1009 4
      mysqli_autocommit($this->link, false);
1010
1011 4
      return true;
1012
1013
    }
1014
  }
1015
1016
  /**
1017
   * Clear the errors in "_debug->_errors".
1018
   *
1019
   * @return bool
1020
   */
1021 4
  public function clearErrors()
1022
  {
1023 4
    return $this->_debug->clearErrors();
1024
  }
1025
1026
  /**
1027
   * Check if we are in a transaction.
1028
   *
1029
   * @return boolean
1030
   */
1031 4
  public function inTransaction()
1032
  {
1033 4
    return $this->_in_transaction;
1034
  }
1035
1036
  /**
1037
   * Ends a transaction and commits if no errors, then ends autocommit.
1038
   *
1039
   * @return boolean this will return true or false indicating success of transactions
1040
   */
1041 2
  public function endTransaction()
1042
  {
1043
1044 2
    if (!$this->errors()) {
1045 1
      mysqli_commit($this->link);
1046 1
      $return = true;
1047 1
    } else {
1048 1
      $this->rollback();
1049 1
      $return = false;
1050
    }
1051
1052 2
    mysqli_autocommit($this->link, true);
1053 2
    $this->_in_transaction = false;
1054
1055 2
    return $return;
1056
  }
1057
1058
  /**
1059
   * Get all errors from "$this->_errors".
1060
   *
1061
   * @return array|false false === on errors
1062
   */
1063 2
  public function errors()
1064
  {
1065 2
    $errors = $this->_debug->getErrors();
1066
1067 2
    return count($errors) > 0 ? $errors : false;
1068
  }
1069
1070
  /**
1071
   * Rollback in a transaction.
1072
   */
1073 2
  public function rollback()
1074
  {
1075
    // init
1076 2
    $return = false;
1077
1078 2
    if ($this->_in_transaction === true) {
1079 2
      $return = mysqli_rollback($this->link);
1080 2
      mysqli_autocommit($this->link, true);
1081 2
      $this->_in_transaction = false;
1082 2
    }
1083
1084 2
    return $return;
1085
  }
1086
1087
  /**
1088
   * Execute a "insert"-query.
1089
   *
1090
   * @param string $table
1091
   * @param array  $data
1092
   *
1093
   * @return false|int false on error
1094
   */
1095 15
  public function insert($table, $data = array())
1096
  {
1097 15
    $table = trim($table);
1098
1099 15
    if ($table === '') {
1100 2
      $this->_debug->displayError('invalid table name');
1101
1102 1
      return false;
1103
    }
1104
1105 14
    if (count($data) === 0) {
1106 3
      $this->_debug->displayError('empty data for INSERT');
1107
1108 2
      return false;
1109
    }
1110
1111 12
    $SET = $this->_parseArrayPair($data);
1112
1113 12
    $sql = 'INSERT INTO ' . $this->quote_string($table) . " SET $SET;";
1114
1115 12
    return $this->query($sql);
1116
  }
1117
1118
  /**
1119
   * Parses arrays with value pairs and generates SQL to use in queries.
1120
   *
1121
   * @param array  $arrayPair
1122
   * @param string $glue this is the separator
1123
   *
1124
   * @return string
1125
   */
1126 13
  private function _parseArrayPair($arrayPair, $glue = ',')
1127
  {
1128
    // init
1129 13
    $sql = '';
1130 13
    $pairs = array();
1131
1132
    /** @noinspection IsEmptyFunctionUsageInspection */
1133 13
    if (!empty($arrayPair)) {
1134
1135 13
      foreach ($arrayPair as $_key => $_value) {
1136 13
        $_connector = '=';
1137 13
        $_key_upper = strtoupper($_key);
1138
1139 13
        if (strpos($_key_upper, ' NOT') !== false) {
1140 2
          $_connector = 'NOT';
1141 2
        }
1142
1143 13
        if (strpos($_key_upper, ' IS') !== false) {
1144 1
          $_connector = 'IS';
1145 1
        }
1146
1147 13
        if (strpos($_key_upper, ' IS NOT') !== false) {
1148 1
          $_connector = 'IS NOT';
1149 1
        }
1150
1151 13
        if (strpos($_key_upper, ' IN') !== false) {
1152 1
          $_connector = 'IN';
1153 1
        }
1154
1155 13
        if (strpos($_key_upper, ' NOT IN') !== false) {
1156 1
          $_connector = 'NOT IN';
1157 1
        }
1158
1159 13
        if (strpos($_key_upper, ' BETWEEN') !== false) {
1160 1
          $_connector = 'BETWEEN';
1161 1
        }
1162
1163 13
        if (strpos($_key_upper, ' NOT BETWEEN') !== false) {
1164 1
          $_connector = 'NOT BETWEEN';
1165 1
        }
1166
1167 13
        if (strpos($_key_upper, ' LIKE') !== false) {
1168 2
          $_connector = 'LIKE';
1169 2
        }
1170
1171 13
        if (strpos($_key_upper, ' NOT LIKE') !== false) {
1172 2
          $_connector = 'NOT LIKE';
1173 2
        }
1174
1175 13 View Code Duplication
        if (strpos($_key_upper, ' >') !== false && strpos($_key_upper, ' =') === false) {
1176 2
          $_connector = '>';
1177 2
        }
1178
1179 13 View Code Duplication
        if (strpos($_key_upper, ' <') !== false && strpos($_key_upper, ' =') === false) {
1180 1
          $_connector = '<';
1181 1
        }
1182
1183 13
        if (strpos($_key_upper, ' >=') !== false) {
1184 2
          $_connector = '>=';
1185 2
        }
1186
1187 13
        if (strpos($_key_upper, ' <=') !== false) {
1188 1
          $_connector = '<=';
1189 1
        }
1190
1191 13
        if (strpos($_key_upper, ' <>') !== false) {
1192 1
          $_connector = '<>';
1193 1
        }
1194
1195 13
        if (is_array($_value) === true) {
1196 1
          foreach ($_value as $oldKey => $oldValue) {
1197 1
            $_value[$oldKey] = $this->secure($oldValue);
1198 1
          }
1199
1200 1
          if ($_connector === 'NOT IN' || $_connector === 'IN') {
1201 1
            $_value = '(' . implode(',', $_value) . ')';
1202 1
          } elseif ($_connector === 'NOT BETWEEN' || $_connector === 'BETWEEN') {
1203 1
            $_value = '(' . implode(' AND ', $_value) . ')';
1204 1
          }
1205
1206 1
        } else {
1207 13
          $_value = $this->secure($_value);
1208
        }
1209
1210 13
        $quoteString = $this->quote_string(trim(str_ireplace($_connector, '', $_key)));
1211 13
        $pairs[] = ' ' . $quoteString . ' ' . $_connector . ' ' . $_value . " \n";
1212 13
      }
1213
1214 13
      $sql = implode($glue, $pairs);
1215 13
    }
1216
1217 13
    return $sql;
1218
  }
1219
1220
  /**
1221
   * Quote && Escape e.g. a table name string.
1222
   *
1223
   * @param string $str
1224
   *
1225
   * @return string
1226
   */
1227 15
  public function quote_string($str)
1228
  {
1229 15
    return '`' . $this->escape($str, false, false) . '`';
1230
  }
1231
1232
  /**
1233
   * Get errors from "$this->_errors".
1234
   *
1235
   * @return array
1236
   */
1237 1
  public function getErrors()
1238
  {
1239 1
    return $this->_debug->getErrors();
1240
  }
1241
1242
  /**
1243
   * Execute a "replace"-query.
1244
   *
1245
   * @param string $table
1246
   * @param array  $data
1247
   *
1248
   * @return false|int false on error
1249
   */
1250 1
  public function replace($table, $data = array())
1251
  {
1252
1253 1
    $table = trim($table);
1254
1255 1
    if ($table === '') {
1256 1
      $this->_debug->displayError('invalid table name');
1257
1258 1
      return false;
1259
    }
1260
1261 1
    if (count($data) === 0) {
1262 1
      $this->_debug->displayError('empty data for REPLACE');
1263
1264 1
      return false;
1265
    }
1266
1267
    // extracting column names
1268 1
    $columns = array_keys($data);
1269 1
    foreach ($columns as $k => $_key) {
1270
      /** @noinspection AlterInForeachInspection */
1271 1
      $columns[$k] = $this->quote_string($_key);
1272 1
    }
1273
1274 1
    $columns = implode(',', $columns);
1275
1276
    // extracting values
1277 1
    foreach ($data as $k => $_value) {
1278
      /** @noinspection AlterInForeachInspection */
1279 1
      $data[$k] = $this->secure($_value);
1280 1
    }
1281 1
    $values = implode(',', $data);
1282
1283 1
    $sql = 'REPLACE INTO ' . $this->quote_string($table) . " ($columns) VALUES ($values);";
1284
1285 1
    return $this->query($sql);
1286
  }
1287
1288
  /**
1289
   * Execute a "update"-query.
1290
   *
1291
   * @param string       $table
1292
   * @param array        $data
1293
   * @param array|string $where
1294
   *
1295
   * @return false|int false on error
1296
   */
1297 6
  public function update($table, $data = array(), $where = '1=1')
1298
  {
1299 6
    $table = trim($table);
1300
1301 6
    if ($table === '') {
1302 1
      $this->_debug->displayError('invalid table name');
1303
1304 1
      return false;
1305
    }
1306
1307 6
    if (count($data) === 0) {
1308 2
      $this->_debug->displayError('empty data for UPDATE');
1309
1310 2
      return false;
1311
    }
1312
1313 6
    $SET = $this->_parseArrayPair($data);
1314
1315 6
    if (is_string($where)) {
1316 2
      $WHERE = $this->escape($where, false, false);
1317 6
    } elseif (is_array($where)) {
1318 4
      $WHERE = $this->_parseArrayPair($where, 'AND');
1319 4
    } else {
1320 1
      $WHERE = '';
1321
    }
1322
1323 6
    $sql = 'UPDATE ' . $this->quote_string($table) . " SET $SET WHERE ($WHERE);";
1324
1325 6
    return $this->query($sql);
1326
  }
1327
1328
  /**
1329
   * Execute a "delete"-query.
1330
   *
1331
   * @param string       $table
1332
   * @param string|array $where
1333
   *
1334
   * @return false|int false on error
1335
   */
1336 1 View Code Duplication
  public function delete($table, $where)
1337
  {
1338
1339 1
    $table = trim($table);
1340
1341 1
    if ($table === '') {
1342 1
      $this->_debug->displayError('invalid table name');
1343
1344 1
      return false;
1345
    }
1346
1347 1
    if (is_string($where)) {
1348 1
      $WHERE = $this->escape($where, false, false);
1349 1
    } elseif (is_array($where)) {
1350 1
      $WHERE = $this->_parseArrayPair($where, 'AND');
1351 1
    } else {
1352 1
      $WHERE = '';
1353
    }
1354
1355 1
    $sql = 'DELETE FROM ' . $this->quote_string($table) . " WHERE ($WHERE);";
1356
1357 1
    return $this->query($sql);
1358
  }
1359
1360
  /**
1361
   * Execute a "select"-query.
1362
   *
1363
   * @param string       $table
1364
   * @param string|array $where
1365
   *
1366
   * @return false|Result false on error
1367
   */
1368 14 View Code Duplication
  public function select($table, $where = '1=1')
1369
  {
1370
1371 14
    if ($table === '') {
1372 1
      $this->_debug->displayError('invalid table name');
1373
1374 1
      return false;
1375
    }
1376
1377 14
    if (is_string($where)) {
1378 3
      $WHERE = $this->escape($where, false, false);
1379 14
    } elseif (is_array($where)) {
1380 12
      $WHERE = $this->_parseArrayPair($where, 'AND');
1381 12
    } else {
1382 1
      $WHERE = '';
1383
    }
1384
1385 14
    $sql = 'SELECT * FROM ' . $this->quote_string($table) . " WHERE ($WHERE);";
1386
1387 14
    return $this->query($sql);
1388
  }
1389
1390
  /**
1391
   * Get the last sql-error.
1392
   *
1393
   * @return string false on error
1394
   */
1395 1
  public function lastError()
1396
  {
1397 1
    $errors = $this->_debug->getErrors();
1398
1399 1
    return count($errors) > 0 ? end($errors) : false;
1400
  }
1401
1402
  /**
1403
   * @return Debug
1404
   */
1405 2
  public function getDebugger()
1406
  {
1407 2
    return $this->_debug;
1408
  }
1409
1410
  /**
1411
   * __destruct
1412
   *
1413
   */
1414
  public function __destruct()
1415
  {
1416
    // close the connection only if we don't save PHP-SESSION's in DB
1417
    if ($this->session_to_db === false) {
1418
      $this->close();
1419
    }
1420
  }
1421
1422
  /**
1423
   * Closes a previously opened database connection.
1424
   */
1425 2
  public function close()
1426
  {
1427 2
    $this->connected = false;
1428
1429 2
    if ($this->link) {
1430 2
      mysqli_close($this->link);
1431 2
    }
1432 2
  }
1433
1434
  /**
1435
   * prevent the instance from being cloned
1436
   *
1437
   * @return void
1438
   */
1439
  private function __clone()
1440
  {
1441
  }
1442
1443
}
1444