Completed
Push — master ( cf6cbe...7c2b24 )
by Lars
04:28
created

DB::mailToAdmin()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 31
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 9.664

Importance

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