Completed
Push — master ( 7c2b24...9c7626 )
by Lars
03:07
created

DB::ping()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

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