Completed
Push — master ( fc8940...ca3183 )
by Lars
03:25
created

DB::__destruct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 3
Bugs 0 Features 3
Metric Value
c 3
b 0
f 3
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.4285
cc 2
eloc 3
nc 2
nop 0
crap 2
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
class DB
14
{
15
16
  /**
17
   * @var int
18
   */
19
  public $query_count = 0;
20
21
  /**
22
   * @var bool
23
   */
24
  protected $exit_on_error = true;
25
26
  /**
27
   * @var bool
28
   */
29
  protected $echo_on_error = true;
30
31
  /**
32
   * @var string
33
   */
34
  protected $css_mysql_box_border = '3px solid red';
35
36
  /**
37
   * @var string
38
   */
39
  protected $css_mysql_box_bg = '#FFCCCC';
40
41
  /**
42
   * @var \mysqli
43
   */
44
  protected $link = false;
45
46
  /**
47
   * @var bool
48
   */
49
  protected $connected = false;
50
51
  /**
52
   * @var array
53
   */
54
  protected $mysqlDefaultTimeFunctions;
55
56
  /**
57
   * @var string
58
   */
59
  private $logger_class_name;
60
61
  /**
62
   * @var string
63
   *
64
   * 'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'
65
   */
66
  private $logger_level;
67
68
  /**
69
   * @var string
70
   */
71
  private $hostname = '';
72
73
  /**
74
   * @var string
75
   */
76
  private $username = '';
77
78
  /**
79
   * @var string
80
   */
81
  private $password = '';
82
83
  /**
84
   * @var string
85
   */
86
  private $database = '';
87
88
  /**
89
   * @var int
90
   */
91
  private $port = 3306;
92
93
  /**
94
   * @var string
95
   */
96
  private $charset = 'utf8';
97
98
  /**
99
   * @var string
100
   */
101
  private $socket = '';
102
103
  /**
104
   * @var array
105
   */
106
  private $_errors = array();
107
108
  /**
109
   * @var bool
110
   */
111
  private $session_to_db = false;
112
113
  /**
114
   * @var bool
115
   */
116
  private $_in_transaction = false;
117
118
  /**
119
   * __construct()
120
   *
121
   * @param string         $hostname
122
   * @param string         $username
123
   * @param string         $password
124
   * @param string         $database
125
   * @param int            $port
126
   * @param string         $charset
127
   * @param boolean|string $exit_on_error use a empty string "" or false to disable it
128
   * @param boolean|string $echo_on_error use a empty string "" or false to disable it
129
   * @param string         $logger_class_name
130
   * @param string         $logger_level  'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'
131
   * @param boolean|string $session_to_db use a empty string "" or false to disable it
132
   */
133 10
  protected function __construct($hostname, $username, $password, $database, $port, $charset, $exit_on_error, $echo_on_error, $logger_class_name, $logger_level, $session_to_db)
134
  {
135 10
    $this->connected = false;
136
137 10
    $this->_loadConfig(
138 10
        $hostname,
139 10
        $username,
140 10
        $password,
141 10
        $database,
142 10
        $port,
143 10
        $charset,
144 10
        $exit_on_error,
145 10
        $echo_on_error,
146 10
        $logger_class_name,
147 10
        $logger_level,
148
        $session_to_db
149 10
    );
150
151 7
    $this->connect();
152
153 4
    $this->mysqlDefaultTimeFunctions = array(
154
      // Returns the current date.
155 4
      'CURDATE()',
156
      // CURRENT_DATE	| Synonyms for CURDATE()
157 4
      'CURRENT_DATE()',
158
      // CURRENT_TIME	| Synonyms for CURTIME()
159 4
      'CURRENT_TIME()',
160
      // CURRENT_TIMESTAMP | Synonyms for NOW()
161 4
      'CURRENT_TIMESTAMP()',
162
      // Returns the current time.
163 4
      'CURTIME()',
164
      // 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...
165 4
      'LOCALTIME()',
166
      // 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...
167 4
      'LOCALTIMESTAMP()',
168
      // Returns the current date and time.
169 4
      'NOW()',
170
      // Returns the time at which the function executes.
171 4
      'SYSDATE()',
172
      // Returns a UNIX timestamp.
173 4
      'UNIX_TIMESTAMP()',
174
      // Returns the current UTC date.
175 4
      'UTC_DATE()',
176
      // Returns the current UTC time.
177 4
      'UTC_TIME()',
178
      // Returns the current UTC date and time.
179 4
      'UTC_TIMESTAMP()',
180
    );
181 4
  }
182
183
  /**
184
   * Load the config from the constructor.
185
   *
186
   * @param string         $hostname
187
   * @param string         $username
188
   * @param string         $password
189
   * @param string         $database
190
   * @param int            $port
191
   * @param string         $charset
192
   * @param boolean|string $exit_on_error use a empty string "" or false to disable it
193
   * @param boolean|string $echo_on_error use a empty string "" or false to disable it
194
   * @param string         $logger_class_name
195
   * @param string         $logger_level
196
   * @param boolean|string $session_to_db use a empty string "" or false to disable it
197
   *
198
   * @return bool
199
   */
200 10
  private function _loadConfig($hostname, $username, $password, $database, $port, $charset, $exit_on_error, $echo_on_error, $logger_class_name, $logger_level, $session_to_db)
201
  {
202 10
    $this->hostname = (string)$hostname;
203 10
    $this->username = (string)$username;
204 10
    $this->password = (string)$password;
205 10
    $this->database = (string)$database;
206
207 10
    if ($charset) {
208 4
      $this->charset = (string)$charset;
209 4
    }
210
211 10
    if ($port) {
212 4
      $this->port = (int)$port;
213 4
    } else {
214
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
215 7
      $this->port = @ini_get('mysqli.default_port');
216
    }
217
218 10
    if (!$this->socket) {
219
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
220 10
      $this->socket = @ini_get('mysqli.default_socket');
221 10
    }
222
223 10
    if ($exit_on_error === true || $exit_on_error === false) {
224 10
      $this->exit_on_error = (boolean)$exit_on_error;
225 10
    }
226
227 10
    if ($echo_on_error === true || $echo_on_error === false) {
228 10
      $this->echo_on_error = (boolean)$echo_on_error;
229 10
    }
230
231 10
    $this->logger_class_name = (string)$logger_class_name;
232 10
    $this->logger_level = (string)$logger_level;
233
234 10
    $this->session_to_db = (boolean)$session_to_db;
235
236 10
    return $this->showConfigError();
237
  }
238
239
  /**
240
   * Show config errors by throw exceptions.
241
   *
242
   * @return bool
243
   *
244
   * @throws \Exception
245
   */
246 10
  public function showConfigError()
247
  {
248
249
    if (
250 10
        !$this->hostname
251 10
        ||
252 9
        !$this->username
253 9
        ||
254 8
        !$this->database
255 10
    ) {
256
257 3
      if (!$this->hostname) {
258 1
        throw new \Exception('no-sql-hostname');
259
      }
260
261 2
      if (!$this->username) {
262 1
        throw new \Exception('no-sql-username');
263
      }
264
265 1
      if (!$this->database) {
266 1
        throw new \Exception('no-sql-database');
267
      }
268
269
      return false;
270
    }
271
272 7
    return true;
273
  }
274
275
  /**
276
   * Open a new connection to the MySQL server.
277
   *
278
   * @return boolean
279
   */
280 8
  public function connect()
281
  {
282 8
    if ($this->isReady()) {
283 1
      return true;
284
    }
285
286 8
    mysqli_report(MYSQLI_REPORT_STRICT);
287
    try {
288
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
289 8
      $this->link = @mysqli_connect(
290 8
          $this->hostname,
291 8
          $this->username,
292 8
          $this->password,
293 8
          $this->database,
294 8
          $this->port,
295 8
          $this->socket
296 8
      );
297 8
    } catch (\Exception $e) {
298 3
      $this->_displayError('Error connecting to mysql server: ' . $e->getMessage(), true);
299
    }
300 5
    mysqli_report(MYSQLI_REPORT_OFF);
301
302 5
    if (!$this->link) {
303
      $this->_displayError('Error connecting to mysql server: ' . mysqli_connect_error(), true);
304
    } else {
305 5
      $this->set_charset($this->charset);
306 5
      $this->connected = true;
307
    }
308
309 5
    return $this->isReady();
310
  }
311
312
  /**
313
   * Check if db-connection is ready.
314
   *
315
   * @return boolean
316
   */
317 29
  public function isReady()
318
  {
319 29
    return $this->connected ? true : false;
320
  }
321
322
  /**
323
   * Display SQL-Errors or throw Exceptions (for dev).
324
   *
325
   * @param string       $error
326
   * @param null|boolean $force_exception_after_error
327
   *
328
   * @throws \Exception
329
   */
330 18
  private function _displayError($error, $force_exception_after_error = null)
331
  {
332 18
    $fileInfo = $this->getFileAndLineFromSql();
333
334 18
    $this->logger(
335
        array(
336 18
            'error',
337 18
            '<strong>' . date(
338
                'd. m. Y G:i:s'
339 18
            ) . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . ') (sql-error):</strong> ' . $error . '<br>',
340
        )
341 18
    );
342
343 18
    $this->_errors[] = $error;
344
345 18
    if ($this->checkForDev() === true) {
346
347 18
      if ($this->echo_on_error) {
348 4
        $box_border = $this->css_mysql_box_border;
349 4
        $box_bg = $this->css_mysql_box_bg;
350
351
        echo '
352 4
        <div class="OBJ-mysql-box" style="border:' . $box_border . '; background:' . $box_bg . '; padding:10px; margin:10px;">
353
          <b style="font-size:14px;">MYSQL Error:</b>
354
          <code style="display:block;">
355 4
            file / line: ' . $fileInfo['file'] . ' / ' . $fileInfo['line'] . '
356 4
            ' . $error . '
357
          </code>
358
        </div>
359 4
        ';
360 4
      }
361
362 18
      if ($force_exception_after_error === true) {
363 4
        throw new \Exception($error);
364 14
      } elseif ($force_exception_after_error === false) {
365
        // nothing
366 14
      } elseif ($force_exception_after_error === null) {
367
        // default
368 11
        if ($this->exit_on_error === true) {
369 2
          throw new \Exception($error);
370
        }
371 9
      }
372 12
    }
373 12
  }
374
375
  /**
376
   * Try to get the file & line from the current sql-query.
377
   *
378
   * @return array will return array['file'] and array['line']
379
   */
380 19
  private function getFileAndLineFromSql()
381
  {
382
    // init
383 19
    $return = array();
384 19
    $file = '';
385 19
    $line = '';
386
387 19
    $referrer = debug_backtrace();
388
389 19
    foreach ($referrer as $key => $ref) {
390
391 View Code Duplication
      if (
392 19
          $ref['function'] == 'query'
393 19
          ||
394 19
          $ref['function'] == 'qry'
395 19
      ) {
396 10
        $file = $referrer[$key]['file'];
397 10
        $line = $referrer[$key]['line'];
398
399 10
        break;
400
      }
401
402 19 View Code Duplication
      if ($ref['function'] == '_logQuery') {
403 1
        $file = $referrer[$key + 1]['file'];
404 1
        $line = $referrer[$key + 1]['line'];
405
406 1
        break;
407
      }
408
409 19 View Code Duplication
      if ($ref['function'] == 'execSQL') {
410
        $file = $referrer[$key]['file'];
411
        $line = $referrer[$key]['line'];
412
413
        break;
414
      }
415 19
    }
416
417 19
    $return['file'] = $file;
418 19
    $return['line'] = $line;
419
420 19
    return $return;
421
  }
422
423
  /**
424
   * Wrapper-Function for a "Logger"-Class.
425
   *
426
   * INFO:
427
   * The "Logger"-ClassName is set by "$this->logger_class_name",<br />
428
   * the "Logger"-Method is the [0] element from the "$log"-parameter,<br />
429
   * the text you want to log is the [1] element and<br />
430
   * the type you want to log is the next [2] element.
431
   *
432
   * @param string[] $log [method, text, type]<br />e.g.: array('error', 'this is a error', 'sql')
433
   */
434 20
  private function logger(array $log)
435
  {
436 20
    $logMethod = '';
437 20
    $logText = '';
438 20
    $logType = '';
439 20
    $logClass = $this->logger_class_name;
440
441 20
    if (isset($log[0])) {
442 20
      $logMethod = $log[0];
443 20
    }
444 20
    if (isset($log[1])) {
445 20
      $logText = $log[1];
446 20
    }
447 20
    if (isset($log[2])) {
448 1
      $logType = $log[2];
449 1
    }
450
451
    if (
452
        $logClass
453 20
        &&
454
        class_exists($logClass)
455 20
        &&
456
        method_exists($logClass, $logMethod)
457 20
    ) {
458
      $logClass::$logMethod($logText, $logType);
459
    }
460 20
  }
461
462
  /**
463
   * Check is the current user is a developer.
464
   *
465
   * INFO:
466
   * By default we will return "true" if the remote-ip-address is localhost or
467
   * if the script is called via CLI. But you can also overwrite this method or
468
   * you can implement a global "checkForDev()"-function.
469
   *
470
   * @return bool
471
   */
472 18
  protected function checkForDev()
473
  {
474
    // init
475 18
    $return = false;
476
477 18
    if (function_exists('checkForDev')) {
478
      $return = checkForDev();
479
    } else {
480
481
      // for testing with dev-address
482 18
      $noDev = isset($_GET['noDev']) ? (int)$_GET['noDev'] : 0;
483 18
      $remoteIpAddress = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : false;
484
485
      if (
486
          $noDev != 1
487 18
          &&
488
          (
489
              $remoteIpAddress == '127.0.0.1'
490 18
              ||
491
              $remoteIpAddress == '::1'
492 18
              ||
493 18
              PHP_SAPI == 'cli'
494 18
          )
495 18
      ) {
496 18
        $return = true;
497 18
      }
498
    }
499
500 18
    return $return;
501
  }
502
503
  /**
504
   * Execute a sql-query and return the result-array for select-statements.
505
   *
506
   * @param $query
507
   *
508
   * @return mixed
509
   * @deprecated
510
   * @throws \Exception
511
   */
512
  public static function qry($query)
513
  {
514
    $db = self::getInstance();
515
516
    $args = func_get_args();
517
    $query = array_shift($args);
518
    $query = str_replace('?', '%s', $query);
519
    $args = array_map(
520
        array(
521
            $db,
522
            'escape',
523
        ),
524
        $args
525
    );
526
    array_unshift($args, $query);
527
    $query = call_user_func_array('sprintf', $args);
528
    $result = $db->query($query);
529
530
    if ($result instanceof Result) {
531
      $return = $result->fetchAllArray();
532
    } else {
533
      $return = $result;
534
    }
535
536
    if ($return || is_array($return)) {
537
      return $return;
538
    } else {
539
      return false;
540
    }
541
  }
542
543
  /**
544
   * getInstance()
545
   *
546
   * @param string      $hostname
547
   * @param string      $username
548
   * @param string      $password
549
   * @param string      $database
550
   * @param string      $port          default is (int)3306
551
   * @param string      $charset       default is 'utf8', but if you need 4-byte chars, then your tables need
552
   *                                   the 'utf8mb4'-charset
553
   * @param bool|string $exit_on_error use a empty string "" or false to disable it
554
   * @param bool|string $echo_on_error use a empty string "" or false to disable it
555
   * @param string      $logger_class_name
556
   * @param string      $logger_level
557
   * @param bool|string $session_to_db use a empty string "" or false to disable it
558
   *
559
   * @return \voku\db\DB
560
   */
561 39
  public static function getInstance($hostname = '', $username = '', $password = '', $database = '', $port = '', $charset = '', $exit_on_error = '', $echo_on_error = '', $logger_class_name = '', $logger_level = '', $session_to_db = '')
562
  {
563
    /**
564
     * @var $instance DB[]
565
     */
566 39
    static $instance = array();
567
568
    /**
569
     * @var $firstInstance DB
570
     */
571 39
    static $firstInstance = null;
572
573
    if (
574 39
        $hostname . $username . $password . $database . $port . $charset == ''
575 39
        &&
576 7
        null !== $firstInstance
577 39
    ) {
578 7
      return $firstInstance;
579
    }
580
581 39
    $connection = md5(
582 39
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . (int)$session_to_db
583 39
    );
584
585 39
    if (!isset($instance[$connection])) {
586 10
      $instance[$connection] = new self(
587 10
          $hostname,
588 10
          $username,
589 10
          $password,
590 10
          $database,
591 10
          $port,
592 10
          $charset,
593 10
          $exit_on_error,
594 10
          $echo_on_error,
595 10
          $logger_class_name,
596 10
          $logger_level,
597
          $session_to_db
598 10
      );
599
600 4
      if (null === $firstInstance) {
601 1
        $firstInstance = $instance[$connection];
602 1
      }
603 4
    }
604
605 39
    return $instance[$connection];
606
  }
607
608
  /**
609
   * Execute a sql-query.
610
   *
611
   * @param string        $sql            sql-query
612
   *
613
   * @param array|boolean $params         "array" of sql-query-parameters
614
   *                                      "false" if you don't need any parameter (default)
615
   *
616
   * @return bool|int|Result              "Result" by "<b>SELECT</b>"-queries<br />
617
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
618
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
619
   *                                      "true" by e.g. "DROP"-queries<br />
620
   *                                      "false" on error
621
   *
622
   * @throws \Exception
623
   */
624 22
  public function query($sql = '', $params = false)
625
  {
626 22
    if (!$this->isReady()) {
627
      return false;
628
    }
629
630 22
    if (!$sql || $sql === '') {
631 4
      $this->_displayError('Can\'t execute an empty Query', false);
632
633 4
      return false;
634
    }
635
636
    if (
637
        $params !== false
638 20
        &&
639 1
        is_array($params)
640 20
        &&
641 1
        count($params) > 0
642 20
    ) {
643 1
      $sql = $this->_parseQueryParams($sql, $params);
644 1
    }
645
646 20
    $query_start_time = microtime(true);
647 20
    $result = mysqli_query($this->link, $sql);
648 20
    $query_duration = microtime(true) - $query_start_time;
649 20
    $this->query_count++;
650
651 20
    $resultCount = 0;
652 20
    if ($result instanceof \mysqli_result) {
653 17
      $resultCount = (int)$result->num_rows;
654 17
    }
655 20
    $this->_logQuery($sql, $query_duration, $resultCount);
656
657
    if (
658
        $result !== null
659 20
        &&
660
        $result instanceof \mysqli_result
661 20
    ) {
662
663
      // return query result object
664 17
      return new Result($sql, $result);
665
666
    } else {
667
      // is the query successful
668 17
      if ($result === true) {
669
670 15
        if (preg_match('/^\s*"?(INSERT|UPDATE|DELETE|REPLACE)\s+/i', $sql)) {
671
672
          // it is an "INSERT" || "REPLACE"
673 15
          if ($this->insert_id() > 0) {
674 14
            return (int)$this->insert_id();
675
          }
676
677
          // it is an "UPDATE" || "DELETE"
678 6
          if ($this->affected_rows() > 0) {
679 6
            return (int)$this->affected_rows();
680
          }
681
        }
682
683
        return true;
684
      } else {
685 8
        $this->queryErrorHandling(mysqli_error($this->link), $sql, $params);
686
      }
687
    }
688
689 8
    return false;
690
  }
691
692
  /**
693
   * _parseQueryParams
694
   *
695
   * @param string $sql
696
   * @param array  $params
697
   *
698
   * @return string
699
   */
700 1
  private function _parseQueryParams($sql, array $params)
701
  {
702
703
    // is there anything to parse?
704 1
    if (strpos($sql, '?') === false) {
705
      return $sql;
706
    }
707
708 1
    if (count($params) > 0) {
709 1
      $parseKey = md5(uniqid(mt_rand(), true));
710 1
      $sql = str_replace('?', $parseKey, $sql);
711
712 1
      $k = 0;
713 1
      while (strpos($sql, $parseKey) !== false) {
714 1
        $value = $this->secure($params[$k]);
715 1
        $sql = preg_replace("/$parseKey/", $value, $sql, 1);
716 1
        $k++;
717 1
      }
718 1
    }
719
720 1
    return $sql;
721
  }
722
723
  /**
724
   * Try to secure a variable, so can you use it in sql-queries.
725
   *
726
   * int: (also strings that contains only an int-value)
727
   * 1. parse into (int)
728
   *
729
   * strings:
730
   * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'
731
   * 2. trim whitespace
732
   * 3. trim '
733
   * 4. escape the string (and remove non utf-8 chars)
734
   * 5. trim ' again (because we maybe removed some chars)
735
   * 6. add ' around the new string
736
   *
737
   * @param mixed $var
738
   *
739
   * @return string | null
740
   */
741 14
  public function secure($var)
742
  {
743
    // save the current value as int (for later usage)
744 14
    if (!is_object($var)) {
745 14
      $varInt = (int)$var;
746 14
    }
747
748 14
    if ((isset($varInt) && "$varInt" == $var) || is_int($var) || is_bool($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...
749
750
      // "int" || int || bool
751
752 12
      $var = (int)$var;
753
754 14
    } elseif (is_string($var)) {
755
756
      // "string"
757
758 14
      if (!in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
759 14
        $var = "'" . trim($this->escape(trim(trim((string)$var), "'")), "'") . "'";
760 14
      }
761
762 14 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...
763
764
      // float
765
766 2
      $var = number_format((float)str_replace(',', '.', $var), 8, '.', '');
767
768 2
    } elseif (is_array($var)) {
769
770
      // array
771
772 1
      $var = null;
773
774 1
    } elseif ($var instanceof \DateTime) {
775
776
      // "DateTime"-object
777
778
      try {
779 1
        $var = "'" . $this->escape($var->format('Y-m-d H:i:s'), false, false) . "'";
780 1
      } catch (\Exception $e) {
781
        $var = null;
782
      }
783
784 1
    } else {
785
786
      // fallback ...
787
788
      $var = "'" . trim($this->escape(trim(trim((string)$var), "'")), "'") . "'";
789
790
    }
791
792 14
    return $var;
793
  }
794
795
  /**
796
   * Escape
797
   *
798
   * @param mixed $var boolean: convert into "integer"<br />
799
   *                   int: convert into "integer"<br />
800
   *                   float: convert into "float" and replace "," with "."<br />
801
   *                   array: run escape() for every key => value<br />
802
   *                   string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
803
   * @param bool  $stripe_non_utf8
804
   * @param bool  $html_entity_decode
805
   * @param bool  $array_to_string
806
   *
807
   * @return array|bool|float|int|string
808
   */
809 17
  public function escape($var = '', $stripe_non_utf8 = true, $html_entity_decode = true, $array_to_string = false)
810
  {
811
    // save the current value as int (for later usage)
812 17
    if (!is_object($var)) {
813 17
      $varInt = (int)$var;
814 17
    }
815
816 17
    if ((isset($varInt) && "$varInt" == $var) || is_int($var) || is_bool($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...
817
818
      // "int" || int || bool
819
820 3
      return (int)$var;
821
822 17 View Code Duplication
    } elseif (is_float($var)) {
823
824
      // float
825
826 1
      return number_format((float)str_replace(',', '.', $var), 8, '.', '');
827
828 17
    } elseif (is_array($var)) {
829
830
      // array
831
832 1
      $varCleaned = array();
833 1
      foreach ($var as $key => $value) {
834
835 1
        $key = (string)$this->escape($key, $stripe_non_utf8, $html_entity_decode);
836 1
        $value = (string)$this->escape($value, $stripe_non_utf8, $html_entity_decode);
837
838 1
        $varCleaned[$key] = $value;
839 1
      }
840
841 1
      if ($array_to_string === true) {
842 1
        $varCleaned = implode(',', $varCleaned);
843
844 1
        return $varCleaned;
845
      } else {
846 1
        return (array)$varCleaned;
847
      }
848
    }
849
850 17
    if (is_string($var)) {
851
852
      // "string"
853
854 17
      if ($stripe_non_utf8 === true) {
855 17
        $var = UTF8::cleanup($var);
856 17
      }
857
858 17
      if ($html_entity_decode === true) {
859
        // use no-html-entity for db
860 17
        $var = UTF8::html_entity_decode($var);
861 17
      }
862
863 17
      $var = get_magic_quotes_gpc() ? stripslashes($var) : $var;
864
865 17
      $var = mysqli_real_escape_string($this->getLink(), $var);
866
867 17
      return (string)$var;
868
    } else {
869
      return false;
870
    }
871
  }
872
873
  /**
874
   * Get the mysqli-link (link identifier returned by mysqli-connect).
875
   *
876
   * @return \mysqli
877
   */
878 17
  public function getLink()
879
  {
880 17
    return $this->link;
881
  }
882
883
  /**
884
   * Log the current query via "$this->logger".
885
   *
886
   * @param string $sql     sql-query
887
   * @param int    $duration
888
   * @param int    $results result counter
889
   *
890
   * @return bool
891
   */
892 21
  private function _logQuery($sql, $duration, $results)
893
  {
894 21
    $logLevelUse = strtolower($this->logger_level);
895
896
    if (
897
        $logLevelUse != 'trace'
898 21
        &&
899
        $logLevelUse != 'debug'
900 21
    ) {
901 20
      return false;
902
    }
903
904 1
    $info = 'time => ' . round(
905 1
            $duration,
906
            5
907 1
        ) . ' - ' . 'results => ' . $results . ' - ' . 'SQL => ' . UTF8::htmlentities($sql);
908
909 1
    $fileInfo = $this->getFileAndLineFromSql();
910 1
    $this->logger(
911
        array(
912 1
            'debug',
913 1
            '<strong>' . date(
914
                'd. m. Y G:i:s'
915 1
            ) . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . '):</strong> ' . $info . '<br>',
916 1
            'sql',
917
        )
918 1
    );
919
920 1
    return true;
921
  }
922
923
  /**
924
   * Returns the auto generated id used in the last query.
925
   *
926
   * @return int|string
927
   */
928 15
  public function insert_id()
929
  {
930 15
    return mysqli_insert_id($this->link);
931
  }
932
933
  /**
934
   * Gets the number of affected rows in a previous MySQL operation.
935
   *
936
   * @return int
937
   */
938 6
  public function affected_rows()
939
  {
940 6
    return mysqli_affected_rows($this->link);
941
  }
942
943
  /**
944
   * Error-handling for the sql-query.
945
   *
946
   * @param string     $errorMsg
947
   * @param string     $sql
948
   * @param array|bool $sqlParams false if there wasn't any parameter
949
   *
950
   * @throws \Exception
951
   */
952 9
  protected function queryErrorHandling($errorMsg, $sql, $sqlParams = false)
953
  {
954 9
    if ($errorMsg == 'DB server has gone away' || $errorMsg == 'MySQL server has gone away') {
955 1
      static $reconnectCounter;
956
957
      // exit if we have more then 3 "DB server has gone away"-errors
958 1
      if ($reconnectCounter > 3) {
959
        $this->mailToAdmin('SQL-Fatal-Error', $errorMsg . ":\n<br />" . $sql, 5);
960
        throw new \Exception($errorMsg);
961
      } else {
962 1
        $this->mailToAdmin('SQL-Error', $errorMsg . ":\n<br />" . $sql);
963
964
        // reconnect
965 1
        $reconnectCounter++;
966 1
        $this->reconnect(true);
967
968
        // re-run the current query
969 1
        $this->query($sql, $sqlParams);
970
      }
971 1
    } else {
972 8
      $this->mailToAdmin('SQL-Warning', $errorMsg . ":\n<br />" . $sql);
973
974
      // this query returned an error, we must display it (only for dev) !!!
975 8
      $this->_displayError($errorMsg . ' | ' . $sql);
976
    }
977 9
  }
978
979
  /**
980
   * send a error mail to the admin / dev
981
   *
982
   * @param string $subject
983
   * @param string $htmlBody
984
   * @param int    $priority
985
   */
986 9
  private function mailToAdmin($subject, $htmlBody, $priority = 3)
987
  {
988 9
    if (function_exists('mailToAdmin')) {
989
      mailToAdmin($subject, $htmlBody, $priority);
990
    } else {
991
992 9
      if ($priority == 3) {
993 9
        $this->logger(
994
            array(
995 9
                'debug',
996 9
                $subject . ' | ' . $htmlBody,
997
            )
998 9
        );
999 9
      } elseif ($priority > 3) {
1000
        $this->logger(
1001
            array(
1002
                'error',
1003
                $subject . ' | ' . $htmlBody,
1004
            )
1005
        );
1006
      } elseif ($priority < 3) {
1007
        $this->logger(
1008
            array(
1009
                'info',
1010
                $subject . ' | ' . $htmlBody,
1011
            )
1012
        );
1013
      }
1014
1015
    }
1016 9
  }
1017
1018
  /**
1019
   * Reconnect to the MySQL-Server.
1020
   *
1021
   * @param bool $checkViaPing
1022
   *
1023
   * @return bool
1024
   */
1025 2
  public function reconnect($checkViaPing = false)
1026
  {
1027 2
    $ping = false;
1028
1029 2
    if ($checkViaPing === true) {
1030 2
      $ping = $this->ping();
1031 2
    }
1032
1033 2
    if ($ping !== true) {
1034 2
      $this->connected = false;
1035 2
      $this->connect();
1036 2
    }
1037
1038 2
    return $this->isReady();
1039
  }
1040
1041
  /**
1042
   * Pings a server connection, or tries to reconnect
1043
   * if the connection has gone down.
1044
   *
1045
   * @return boolean
1046
   */
1047 3
  public function ping()
1048
  {
1049
    if (
1050 3
        $this->link
1051 3
        &&
1052 3
        $this->link instanceof \mysqli
1053 3
    ) {
1054
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
1055 3
      return @mysqli_ping($this->link);
1056
    } else {
1057
      return false;
1058
    }
1059
  }
1060
1061
  /**
1062
   * Execute select/insert/update/delete sql-queries.
1063
   *
1064
   * @param string $query    sql-query
1065
   * @param bool   $useCache use cache?
1066
   * @param int    $cacheTTL cache-ttl in seconds
1067
   *
1068
   * @return mixed "array" by "<b>SELECT</b>"-queries<br />
1069
   *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
1070
   *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1071
   *               "true" by e.g. "DROP"-queries<br />
1072
   *               "false" on error
1073
   *
1074
   */
1075 3
  public static function execSQL($query, $useCache = false, $cacheTTL = 3600)
1076
  {
1077 3
    $db = self::getInstance();
1078
1079 3
    if ($useCache === true) {
1080 1
      $cache = new Cache(null, null, false, $useCache);
1081 1
      $cacheKey = 'sql-' . md5($query);
1082
1083
      if (
1084 1
          $cache->getCacheIsReady() === true
1085 1
          &&
1086 1
          $cache->existsItem($cacheKey)
1087 1
      ) {
1088 1
        return $cache->getItem($cacheKey);
1089
      }
1090
1091 1
    } else {
1092 3
      $cache = false;
1093
    }
1094
1095 3
    $result = $db->query($query);
1096
1097 3
    if ($result instanceof Result) {
1098
1099 1
      $return = $result->fetchAllArray();
1100
1101
      if (
1102 1
          isset($cacheKey)
1103 1
          &&
1104
          $useCache === true
1105 1
          &&
1106
          $cache instanceof Cache
1107 1
          &&
1108 1
          $cache->getCacheIsReady() === true
1109 1
      ) {
1110 1
        $cache->setItem($cacheKey, $return, $cacheTTL);
1111 1
      }
1112
1113 1
    } else {
1114 2
      $return = $result;
1115
    }
1116
1117 3
    return $return;
1118
  }
1119
1120
  /**
1121
   * Get the current charset.
1122
   *
1123
   * @return string
1124
   */
1125 1
  public function get_charset()
1126
  {
1127 1
    return $this->charset;
1128
  }
1129
1130
  /**
1131
   * Set the current charset.
1132
   *
1133
   * @param string $charset
1134
   *
1135
   * @return bool
1136
   */
1137 6
  public function set_charset($charset)
1138
  {
1139 6
    $this->charset = (string)$charset;
1140
1141 6
    $return = mysqli_set_charset($this->link, $charset);
1142
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
1143 6
    @mysqli_query($this->link, 'SET CHARACTER SET ' . $charset);
1144
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
1145 6
    @mysqli_query($this->link, "SET NAMES '" . ($charset == 'utf8' ? 'utf8mb4' : $charset) . "'");
1146
1147 6
    return $return;
1148
  }
1149
1150
  /**
1151
   * __wakeup
1152
   *
1153
   * @return void
1154
   */
1155 1
  public function __wakeup()
1156
  {
1157 1
    $this->reconnect();
1158 1
  }
1159
1160
  /**
1161
   * Get all table-names via "SHOW TABLES".
1162
   *
1163
   * @return array
1164
   */
1165 1
  public function getAllTables()
1166
  {
1167 1
    $query = 'SHOW TABLES';
1168 1
    $result = $this->query($query);
1169
1170 1
    return $result->fetchAllArray();
1171
  }
1172
1173
  /**
1174
   * Execute a sql-multi-query.
1175
   *
1176
   * @param string $sql
1177
   *
1178
   * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
1179
   *                        "boolean" by only "<b>INSERT</b>"-queries<br />
1180
   *                        "boolean" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1181
   *                        "boolean" by only by e.g. "DROP"-queries<br />
1182
   *
1183
   * @throws \Exception
1184
   */
1185 1
  public function multi_query($sql)
1186
  {
1187 1
    if (!$this->isReady()) {
1188
      return false;
1189
    }
1190
1191 1
    if (!$sql || $sql === '') {
1192 1
      $this->_displayError('Can\'t execute an empty Query', false);
1193
1194 1
      return false;
1195
    }
1196
1197 1
    $query_start_time = microtime(true);
1198 1
    $resultTmp = mysqli_multi_query($this->link, $sql);
1199 1
    $query_duration = microtime(true) - $query_start_time;
1200
1201 1
    $this->_logQuery($sql, $query_duration, 0);
1202
1203 1
    $returnTheResult = false;
1204 1
    $result = array();
1205 1
    if ($resultTmp) {
1206
      do {
1207 1
        $resultTmpInner = mysqli_store_result($this->link);
1208
1209
        if (
1210 1
            null !== $resultTmpInner
1211 1
            &&
1212
            $resultTmpInner instanceof \mysqli_result
1213 1
        ) {
1214 1
          $returnTheResult = true;
1215 1
          $result[] = new Result($sql, $resultTmpInner);
1216 1
        } else {
1217 1
          $errorMsg = mysqli_error($this->link);
1218
1219
          // is the query successful
1220 1
          if ($resultTmpInner === true || !$errorMsg) {
1221 1
            $result[] = true;
1222 1
          } else {
1223
            $result[] = false;
1224
1225
            $this->queryErrorHandling($errorMsg, $sql);
1226
          }
1227
        }
1228 1
      } while (mysqli_more_results($this->link) === true ? mysqli_next_result($this->link) : false);
1229
1230 1
    } else {
1231
1232
      $errorMsg = mysqli_error($this->link);
1233
1234
      if ($this->checkForDev() === true) {
1235
        echo "Info: maybe you have to increase your 'max_allowed_packet = 30M' in the config: 'my.conf' \n<br />";
1236
        echo 'Error:' . $errorMsg;
1237
      }
1238
1239
      $this->mailToAdmin('SQL-Error in mysqli_multi_query', $errorMsg . ":\n<br />" . $sql);
1240
    }
1241
1242
    // return the result only if there was a "SELECT"-query
1243 1
    if ($returnTheResult === true) {
1244 1
      return $result;
1245
    }
1246
1247 1
    if (!in_array(false, $result, true)) {
1248 1
      return true;
1249
    } else {
1250
      return false;
1251
    }
1252
  }
1253
1254
  /**
1255
   * alias: "beginTransaction()"
1256
   */
1257 1
  public function startTransaction()
1258
  {
1259 1
    $this->beginTransaction();
1260 1
  }
1261
1262
  /**
1263
   * Begins a transaction, by turning off auto commit.
1264
   *
1265
   * @return boolean this will return true or false indicating success of transaction
1266
   */
1267 4
  public function beginTransaction()
1268
  {
1269 4
    $this->clearErrors();
1270
1271 4
    if ($this->inTransaction() === true) {
1272 1
      $this->_displayError('Error mysql server already in transaction!', true);
1273
1274
      return false;
1275 4
    } elseif (mysqli_connect_errno()) {
1276
      $this->_displayError('Error connecting to mysql server: ' . mysqli_connect_error(), true);
1277
1278
      return false;
1279
    } else {
1280 4
      $this->_in_transaction = true;
1281 4
      mysqli_autocommit($this->link, false);
1282
1283 4
      return true;
1284
1285
    }
1286
  }
1287
1288
  /**
1289
   * Clear the errors in "$this->_errors".
1290
   *
1291
   * @return bool
1292
   */
1293 4
  public function clearErrors()
1294
  {
1295 4
    $this->_errors = array();
1296
1297 4
    return true;
1298
  }
1299
1300
  /**
1301
   * Check if we are in a transaction.
1302
   *
1303
   * @return boolean
1304
   */
1305 4
  public function inTransaction()
1306
  {
1307 4
    return $this->_in_transaction;
1308
  }
1309
1310
  /**
1311
   * Ends a transaction and commits if no errors, then ends autocommit.
1312
   *
1313
   * @return boolean this will return true or false indicating success of transactions
1314
   */
1315 2
  public function endTransaction()
1316
  {
1317
1318 2
    if (!$this->errors()) {
1319 1
      mysqli_commit($this->link);
1320 1
      $return = true;
1321 1
    } else {
1322 1
      $this->rollback();
1323 1
      $return = false;
1324
    }
1325
1326 2
    mysqli_autocommit($this->link, true);
1327 2
    $this->_in_transaction = false;
1328
1329 2
    return $return;
1330
  }
1331
1332
  /**
1333
   * Get all errors from "$this->_errors".
1334
   *
1335
   * @return array|false false === on errors
1336
   */
1337 2
  public function errors()
1338
  {
1339 2
    return count($this->_errors) > 0 ? $this->_errors : false;
1340
  }
1341
1342
  /**
1343
   * Rollback in a transaction.
1344
   */
1345 2
  public function rollback()
1346
  {
1347
    // init
1348 2
    $return = false;
1349
1350 2
    if ($this->_in_transaction === true) {
1351 2
      $return = mysqli_rollback($this->link);
1352 2
      mysqli_autocommit($this->link, true);
1353 2
      $this->_in_transaction = false;
1354 2
    }
1355
1356 2
    return $return;
1357
  }
1358
1359
  /**
1360
   * Execute a "insert"-query.
1361
   *
1362
   * @param string $table
1363
   * @param array  $data
1364
   *
1365
   * @return false|int false on error
1366
   */
1367 14
  public function insert($table, $data = array())
1368
  {
1369 14
    $table = trim($table);
1370
1371 14
    if ($table === '') {
1372 2
      $this->_displayError('invalid-table-name');
1373
1374 1
      return false;
1375
    }
1376
1377 13
    if (count($data) == 0) {
1378 3
      $this->_displayError('empty-data-for-INSERT');
1379
1380 2
      return false;
1381
    }
1382
1383 11
    $SET = $this->_parseArrayPair($data);
1384
1385 11
    $sql = 'INSERT INTO ' . $this->quote_string($table) . " SET $SET;";
1386
1387 11
    return $this->query($sql);
1388
  }
1389
1390
  /**
1391
   * Parses arrays with value pairs and generates SQL to use in queries.
1392
   *
1393
   * @param array  $arrayPair
1394
   * @param string $glue this is the separator
1395
   *
1396
   * @return string
1397
   */
1398 12
  private function _parseArrayPair($arrayPair, $glue = ',')
1399
  {
1400
    // init
1401 12
    $sql = '';
1402 12
    $pairs = array();
1403
1404 12
    if (!empty($arrayPair)) {
1405
1406 12
      foreach ($arrayPair as $_key => $_value) {
1407 12
        $_connector = '=';
1408 12
        $_key_upper = strtoupper($_key);
1409
1410 12
        if (strpos($_key_upper, ' NOT') !== false) {
1411 2
          $_connector = 'NOT';
1412 2
        }
1413
1414 12
        if (strpos($_key_upper, ' IS') !== false) {
1415 1
          $_connector = 'IS';
1416 1
        }
1417
1418 12
        if (strpos($_key_upper, ' IS NOT') !== false) {
1419 1
          $_connector = 'IS NOT';
1420 1
        }
1421
1422 12
        if (strpos($_key_upper, ' IN') !== false) {
1423 1
          $_connector = 'IN';
1424 1
        }
1425
1426 12
        if (strpos($_key_upper, ' NOT IN') !== false) {
1427 1
          $_connector = 'NOT IN';
1428 1
        }
1429
1430 12
        if (strpos($_key_upper, ' BETWEEN') !== false) {
1431 1
          $_connector = 'BETWEEN';
1432 1
        }
1433
1434 12
        if (strpos($_key_upper, ' NOT BETWEEN') !== false) {
1435 1
          $_connector = 'NOT BETWEEN';
1436 1
        }
1437
1438 12
        if (strpos($_key_upper, ' LIKE') !== false) {
1439 2
          $_connector = 'LIKE';
1440 2
        }
1441
1442 12
        if (strpos($_key_upper, ' NOT LIKE') !== false) {
1443 2
          $_connector = 'NOT LIKE';
1444 2
        }
1445
1446 12 View Code Duplication
        if (strpos($_key_upper, ' >') !== false && strpos($_key_upper, ' =') === false) {
1447 2
          $_connector = '>';
1448 2
        }
1449
1450 12 View Code Duplication
        if (strpos($_key_upper, ' <') !== false && strpos($_key_upper, ' =') === false) {
1451 1
          $_connector = '<';
1452 1
        }
1453
1454 12
        if (strpos($_key_upper, ' >=') !== false) {
1455 2
          $_connector = '>=';
1456 2
        }
1457
1458 12
        if (strpos($_key_upper, ' <=') !== false) {
1459 1
          $_connector = '<=';
1460 1
        }
1461
1462 12
        if (strpos($_key_upper, ' <>') !== false) {
1463 1
          $_connector = '<>';
1464 1
        }
1465
1466
        if (
1467 12
            is_array($_value)
1468 12
            &&
1469
            (
1470
                $_connector == 'NOT IN'
1471 1
                ||
1472
                $_connector == 'IN'
1473 1
            )
1474 12
        ) {
1475 1
          foreach ($_value as $oldKey => $oldValue) {
1476
            /** @noinspection AlterInForeachInspection */
1477 1
            $_value[$oldKey] = $this->secure($oldValue);
1478 1
          }
1479 1
          $_value = '(' . implode(',', $_value) . ')';
1480 1
        } elseif (
1481 12
            is_array($_value)
1482 12
            &&
1483
            (
1484
                $_connector == 'NOT BETWEEN'
1485 1
                ||
1486
                $_connector == 'BETWEEN'
1487
            )
1488 12
        ) {
1489 1
          foreach ($_value as $oldKey => $oldValue) {
1490
            /** @noinspection AlterInForeachInspection */
1491 1
            $_value[$oldKey] = $this->secure($oldValue);
1492 1
          }
1493 1
          $_value = '(' . implode(' AND ', $_value) . ')';
1494 1
        } else {
1495 12
          $_value = $this->secure($_value);
1496
        }
1497
1498 12
        $quoteString = $this->quote_string(trim(str_ireplace($_connector, '', $_key)));
1499 12
        $pairs[] = ' ' . $quoteString . ' ' . $_connector . ' ' . $_value . " \n";
1500 12
      }
1501
1502 12
      $sql = implode($glue, $pairs);
1503 12
    }
1504
1505 12
    return $sql;
1506
  }
1507
1508
  /**
1509
   * Quote && Escape e.g. a table name string.
1510
   *
1511
   * @param string $str
1512
   *
1513
   * @return string
1514
   */
1515 14
  public function quote_string($str)
1516
  {
1517 14
    return '`' . $this->escape($str, false, false) . '`';
1518
  }
1519
1520
  /**
1521
   * Get errors from "$this->_errors".
1522
   *
1523
   * @return array
1524
   */
1525 1
  public function getErrors()
1526
  {
1527 1
    return $this->_errors;
1528
  }
1529
1530
  /**
1531
   * Execute a "replace"-query.
1532
   *
1533
   * @param string $table
1534
   * @param array  $data
1535
   *
1536
   * @return false|int false on error
1537
   */
1538 1
  public function replace($table, $data = array())
1539
  {
1540
1541 1
    $table = trim($table);
1542
1543 1
    if ($table === '') {
1544 1
      $this->_displayError('invalid table name');
1545
1546 1
      return false;
1547
    }
1548
1549 1
    if (count($data) == 0) {
1550 1
      $this->_displayError('empty data for REPLACE');
1551
1552 1
      return false;
1553
    }
1554
1555
    // extracting column names
1556 1
    $columns = array_keys($data);
1557 1
    foreach ($columns as $k => $_key) {
1558
      /** @noinspection AlterInForeachInspection */
1559 1
      $columns[$k] = $this->quote_string($_key);
1560 1
    }
1561
1562 1
    $columns = implode(',', $columns);
1563
1564
    // extracting values
1565 1
    foreach ($data as $k => $_value) {
1566
      /** @noinspection AlterInForeachInspection */
1567 1
      $data[$k] = $this->secure($_value);
1568 1
    }
1569 1
    $values = implode(',', $data);
1570
1571 1
    $sql = 'REPLACE INTO ' . $this->quote_string($table) . " ($columns) VALUES ($values);";
1572
1573 1
    return $this->query($sql);
1574
  }
1575
1576
  /**
1577
   * Execute a "update"-query.
1578
   *
1579
   * @param string       $table
1580
   * @param array        $data
1581
   * @param array|string $where
1582
   *
1583
   * @return false|int false on error
1584
   */
1585 5
  public function update($table, $data = array(), $where = '1=1')
1586
  {
1587 5
    $table = trim($table);
1588
1589 5
    if ($table === '') {
1590 1
      $this->_displayError('invalid table name');
1591
1592 1
      return false;
1593
    }
1594
1595 5
    if (count($data) == 0) {
1596 1
      $this->_displayError('empty data for UPDATE');
1597
1598 1
      return false;
1599
    }
1600
1601 5
    $SET = $this->_parseArrayPair($data);
1602
1603 5
    if (is_string($where)) {
1604 1
      $WHERE = $this->escape($where, false, false);
1605 5
    } elseif (is_array($where)) {
1606 4
      $WHERE = $this->_parseArrayPair($where, 'AND');
1607 4
    } else {
1608 1
      $WHERE = '';
1609
    }
1610
1611 5
    $sql = 'UPDATE ' . $this->quote_string($table) . " SET $SET WHERE ($WHERE);";
1612
1613 5
    return $this->query($sql);
1614
  }
1615
1616
  /**
1617
   * Execute a "delete"-query.
1618
   *
1619
   * @param string       $table
1620
   * @param string|array $where
1621
   *
1622
   * @return false|int false on error
1623
   */
1624 1 View Code Duplication
  public function delete($table, $where)
1625
  {
1626
1627 1
    $table = trim($table);
1628
1629 1
    if ($table === '') {
1630 1
      $this->_displayError('invalid table name');
1631
1632 1
      return false;
1633
    }
1634
1635 1
    if (is_string($where)) {
1636 1
      $WHERE = $this->escape($where, false, false);
1637 1
    } elseif (is_array($where)) {
1638 1
      $WHERE = $this->_parseArrayPair($where, 'AND');
1639 1
    } else {
1640 1
      $WHERE = '';
1641
    }
1642
1643 1
    $sql = 'DELETE FROM ' . $this->quote_string($table) . " WHERE ($WHERE);";
1644
1645 1
    return $this->query($sql);
1646
  }
1647
1648
  /**
1649
   * Execute a "select"-query.
1650
   *
1651
   * @param string       $table
1652
   * @param string|array $where
1653
   *
1654
   * @return false|Result false on error
1655
   */
1656 13 View Code Duplication
  public function select($table, $where = '1=1')
1657
  {
1658
1659 13
    if ($table === '') {
1660 1
      $this->_displayError('invalid table name');
1661
1662 1
      return false;
1663
    }
1664
1665 13
    if (is_string($where)) {
1666 3
      $WHERE = $this->escape($where, false, false);
1667 13
    } elseif (is_array($where)) {
1668 11
      $WHERE = $this->_parseArrayPair($where, 'AND');
1669 11
    } else {
1670 1
      $WHERE = '';
1671
    }
1672
1673 13
    $sql = 'SELECT * FROM ' . $this->quote_string($table) . " WHERE ($WHERE);";
1674
1675 13
    return $this->query($sql);
1676
  }
1677
1678
  /**
1679
   * Get the last sql-error.
1680
   *
1681
   * @return string false on error
1682
   */
1683 1
  public function lastError()
1684
  {
1685 1
    return count($this->_errors) > 0 ? end($this->_errors) : false;
1686
  }
1687
1688
  /**
1689
   * __destruct
1690
   *
1691
   */
1692 1
  public function __destruct()
1693
  {
1694
    // close the connection only if we don't save PHP-SESSION's in DB
1695 1
    if ($this->session_to_db === false) {
1696 1
      $this->close();
1697 1
    }
1698 1
  }
1699
1700
  /**
1701
   * Closes a previously opened database connection.
1702
   */
1703 3
  public function close()
1704
  {
1705 3
    $this->connected = false;
1706
1707 3
    if ($this->link) {
1708 3
      mysqli_close($this->link);
1709 3
    }
1710 3
  }
1711
1712
  /**
1713
   * prevent the instance from being cloned
1714
   *
1715
   * @return void
1716
   */
1717
  private function __clone()
1718
  {
1719
  }
1720
1721
}
1722