Completed
Pull Request — master (#12)
by
unknown
02:53
created

DB::endTransaction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2

Importance

Changes 5
Bugs 0 Features 2
Metric Value
c 5
b 0
f 2
dl 0
loc 16
ccs 9
cts 9
cp 1
rs 9.4285
cc 2
eloc 10
nc 2
nop 0
crap 2
1
<?php
2
3
namespace voku\db;
4
5
use voku\cache\Cache;
6
use voku\helper\Bootup;
7
use voku\helper\UTF8;
8
9
/**
10
 * DB: this handles DB queries via MySQLi
11
 *
12
 * @package   voku\db
13
 */
14
class DB
15
{
16
17
  /**
18
   * @var int
19
   */
20
  public $query_count = 0;
21
22
  /**
23
   * @var bool
24
   */
25
  protected $exit_on_error = true;
26
27
  /**
28
   * @var bool
29
   */
30
  protected $echo_on_error = true;
31
32
  /**
33
   * @var string
34
   */
35
  protected $css_mysql_box_border = '3px solid red';
36
37
  /**
38
   * @var string
39
   */
40
  protected $css_mysql_box_bg = '#FFCCCC';
41
42
  /**
43
   * @var \mysqli
44
   */
45
  protected $link = false;
46
47
  /**
48
   * @var bool
49
   */
50
  protected $connected = false;
51
52
  /**
53
   * @var array
54
   */
55
  protected $mysqlDefaultTimeFunctions;
56
57
  /**
58
   * @var string
59
   */
60
  private $logger_class_name;
61
62
  /**
63
   * @var string
64
   *
65
   * 'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'
66
   */
67
  private $logger_level;
68
69
  /**
70
   * @var string
71
   */
72
  private $hostname = '';
73
74
  /**
75
   * @var string
76
   */
77
  private $username = '';
78
79
  /**
80
   * @var string
81
   */
82
  private $password = '';
83
84
  /**
85
   * @var string
86
   */
87
  private $database = '';
88
89
  /**
90
   * @var int
91
   */
92
  private $port = 3306;
93
94
  /**
95
   * @var string
96
   */
97
  private $charset = 'utf8';
98
99
  /**
100
   * @var string
101
   */
102
  private $socket = '';
103
104
  /**
105
   * @var array
106
   */
107
  private $_errors = array();
108
109
  /**
110
   * @var bool
111
   */
112
  private $session_to_db = false;
113
114
  /**
115
   * @var bool
116
   */
117
  private $_in_transaction = false;
118
119
  /**
120
   * __construct()
121
   *
122
   * @param string         $hostname
123
   * @param string         $username
124
   * @param string         $password
125
   * @param string         $database
126
   * @param int            $port
127
   * @param string         $charset
128
   * @param boolean|string $exit_on_error use a empty string "" or false to disable it
129
   * @param boolean|string $echo_on_error use a empty string "" or false to disable it
130
   * @param string         $logger_class_name
131
   * @param string         $logger_level  'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'
132
   * @param boolean|string $session_to_db use a empty string "" or false to disable it
133
   */
134 10
  protected function __construct($hostname, $username, $password, $database, $port, $charset, $exit_on_error, $echo_on_error, $logger_class_name, $logger_level, $session_to_db)
135
  {
136 10
    $this->connected = false;
137
138 10
    $this->_loadConfig(
139
        $hostname,
140
        $username,
141
        $password,
142
        $database,
143
        $port,
144
        $charset,
145
        $exit_on_error,
146
        $echo_on_error,
147
        $logger_class_name,
148
        $logger_level,
149
        $session_to_db
150
    );
151
152 7
    $this->connect();
153
154 4
    $this->mysqlDefaultTimeFunctions = array(
155
      // Returns the current date.
156
      'CURDATE()',
157
      // CURRENT_DATE	| Synonyms for CURDATE()
158
      'CURRENT_DATE()',
159
      // CURRENT_TIME	| Synonyms for CURTIME()
160
      'CURRENT_TIME()',
161
      // CURRENT_TIMESTAMP | Synonyms for NOW()
162
      'CURRENT_TIMESTAMP()',
163
      // Returns the current time.
164
      'CURTIME()',
165
      // 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...
166
      'LOCALTIME()',
167
      // 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...
168
      'LOCALTIMESTAMP()',
169
      // Returns the current date and time.
170
      'NOW()',
171
      // Returns the time at which the function executes.
172
      'SYSDATE()',
173
      // Returns a UNIX timestamp.
174
      'UNIX_TIMESTAMP()',
175
      // Returns the current UTC date.
176
      'UTC_DATE()',
177
      // Returns the current UTC time.
178
      'UTC_TIME()',
179
      // Returns the current UTC date and time.
180
      'UTC_TIMESTAMP()',
181
    );
182 4
  }
183
184
  /**
185
   * Load the config from the constructor.
186
   *
187
   * @param string         $hostname
188
   * @param string         $username
189
   * @param string         $password
190
   * @param string         $database
191
   * @param int            $port
192
   * @param string         $charset
193
   * @param boolean|string $exit_on_error use a empty string "" or false to disable it
194
   * @param boolean|string $echo_on_error use a empty string "" or false to disable it
195
   * @param string         $logger_class_name
196
   * @param string         $logger_level
197
   * @param boolean|string $session_to_db use a empty string "" or false to disable it
198
   *
199
   * @return bool
200
   */
201 10
  private function _loadConfig($hostname, $username, $password, $database, $port, $charset, $exit_on_error, $echo_on_error, $logger_class_name, $logger_level, $session_to_db)
202
  {
203 10
    $this->hostname = (string)$hostname;
204 10
    $this->username = (string)$username;
205 10
    $this->password = (string)$password;
206 10
    $this->database = (string)$database;
207
208 10
    if ($charset) {
209 4
      $this->charset = (string)$charset;
210
    }
211
212 10
    if ($port) {
213 4
      $this->port = (int)$port;
214
    } else {
215
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
216 7
      $this->port = @ini_get('mysqli.default_port');
217
    }
218
219 10
    if (!$this->socket) {
220
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
221 10
      $this->socket = @ini_get('mysqli.default_socket');
222
    }
223
224 10
    if ($exit_on_error === true || $exit_on_error === false) {
225 10
      $this->exit_on_error = (boolean)$exit_on_error;
226
    }
227
228 10
    if ($echo_on_error === true || $echo_on_error === false) {
229 10
      $this->echo_on_error = (boolean)$echo_on_error;
230
    }
231
232 10
    $this->logger_class_name = (string)$logger_class_name;
233 10
    $this->logger_level = (string)$logger_level;
234
235 10
    $this->session_to_db = (boolean)$session_to_db;
236
237 10
    return $this->showConfigError();
238
  }
239
240
  /**
241
   * Show config errors by throw exceptions.
242
   *
243
   * @return bool
244
   *
245
   * @throws \Exception
246
   */
247 10
  public function showConfigError()
248
  {
249
250
    if (
251 10
        !$this->hostname
252
        ||
253 9
        !$this->username
254
        ||
255 10
        !$this->database
256
    ) {
257
258 3
      if (!$this->hostname) {
259 1
        throw new \Exception('no-sql-hostname');
260
      }
261
262 2
      if (!$this->username) {
263 1
        throw new \Exception('no-sql-username');
264
      }
265
266 1
      if (!$this->database) {
267 1
        throw new \Exception('no-sql-database');
268
      }
269
270
      return false;
271
    }
272
273 7
    return true;
274
  }
275
276
  /**
277
   * Open a new connection to the MySQL server.
278
   *
279
   * @return boolean
280
   */
281 8
  public function connect()
282
  {
283 8
    if ($this->isReady()) {
284 1
      return true;
285
    }
286
287 8
    mysqli_report(MYSQLI_REPORT_STRICT);
288
    try {
289
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
290 8
      $this->link = @mysqli_connect(
291 8
          $this->hostname,
292 8
          $this->username,
293 8
          $this->password,
294 8
          $this->database,
295 8
          $this->port,
296 8
          $this->socket
297
      );
298 3
    } catch (\Exception $e) {
299 3
      $this->_displayError('Error connecting to mysql server: ' . $e->getMessage(), true);
300
    }
301 5
    mysqli_report(MYSQLI_REPORT_OFF);
302
303 5
    if (!$this->link) {
304
      $this->_displayError('Error connecting to mysql server: ' . mysqli_connect_error(), true);
305
    } else {
306 5
      $this->set_charset($this->charset);
307 5
      $this->connected = true;
308
    }
309
310 5
    return $this->isReady();
311
  }
312
313
  /**
314
   * Check if db-connection is ready.
315
   *
316
   * @return boolean
317
   */
318 29
  public function isReady()
319
  {
320 29
    return $this->connected ? true : false;
321
  }
322
323
  /**
324
   * Display SQL-Errors or throw Exceptions (for dev).
325
   *
326
   * @param string       $error
327
   * @param null|boolean $force_exception_after_error
328
   *
329
   * @throws \Exception
330
   */
331 18
  private function _displayError($error, $force_exception_after_error = null)
332
  {
333 18
    $fileInfo = $this->getFileAndLineFromSql();
334
335 18
    $this->logger(
336
        array(
337 18
            'error',
338 18
            '<strong>' . date(
339 18
                'd. m. Y G:i:s'
340 18
            ) . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . ') (sql-error):</strong> ' . $error . '<br>',
341
        )
342
    );
343
344 18
    $this->_errors[] = $error;
345
346 18
    if ($this->checkForDev() === true) {
347
348 18
      if ($this->echo_on_error) {
349 4
        $box_border = $this->css_mysql_box_border;
350 4
        $box_bg = $this->css_mysql_box_bg;
351
352
        echo '
353 4
        <div class="OBJ-mysql-box" style="border:' . $box_border . '; background:' . $box_bg . '; padding:10px; margin:10px;">
354
          <b style="font-size:14px;">MYSQL Error:</b>
355
          <code style="display:block;">
356 4
            file / line: ' . $fileInfo['file'] . ' / ' . $fileInfo['line'] . '
357 4
            ' . $error . '
358
          </code>
359
        </div>
360 4
        ';
361
      }
362
363 18
      if ($force_exception_after_error === true) {
364 4
        throw new \Exception($error);
365 14
      } elseif ($force_exception_after_error === false) {
366
        // nothing
367 11
      } elseif ($force_exception_after_error === null) {
368
        // default
369 11
        if ($this->exit_on_error === true) {
370 2
          throw new \Exception($error);
371
        }
372
      }
373
    }
374 12
  }
375
376
  /**
377
   * Try to get the file & line from the current sql-query.
378
   *
379
   * @return array will return array['file'] and array['line']
380
   */
381 19
  private function getFileAndLineFromSql()
382
  {
383
    // init
384 19
    $return = array();
385 19
    $file = '';
386 19
    $line = '';
387
388 19
    if (Bootup::is_php('5.4') === true) {
389 19
      $referrer = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
390
    } else {
391
      $referrer = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
392
    }
393
394 19
    foreach ($referrer as $key => $ref) {
395
396
      if (
397 19
          $ref['function'] == 'execSQL'
398
          ||
399 19
          $ref['function'] == 'query'
400
          ||
401 19
          $ref['function'] == 'qry'
402
          ||
403 19
          $ref['function'] == 'insert'
404
          ||
405 19
          $ref['function'] == 'update'
406
          ||
407 19
          $ref['function'] == 'replace'
408
          ||
409 19
          $ref['function'] == 'delete'
410
      ) {
411 14
        $file = $referrer[$key]['file'];
412 19
        $line = $referrer[$key]['line'];
413
      }
414
415
    }
416
417 19
    $return['file'] = $file;
418 19
    $return['line'] = $line;
419
420 19
    return $return;
421
  }
422
423
  /**
424
   * Wrapper-Function for a "Logger"-Class.
425
   *
426
   * INFO:
427
   * The "Logger"-ClassName is set by "$this->logger_class_name",<br />
428
   * the "Logger"-Method is the [0] element from the "$log"-parameter,<br />
429
   * the text you want to log is the [1] element and<br />
430
   * the type you want to log is the next [2] element.
431
   *
432
   * @param string[] $log [method, text, type]<br />e.g.: array('error', 'this is a error', 'sql')
433
   */
434 20
  private function logger(array $log)
435
  {
436 20
    $logMethod = '';
437 20
    $logText = '';
438 20
    $logType = '';
439 20
    $logClass = $this->logger_class_name;
440
441 20
    if (isset($log[0])) {
442 20
      $logMethod = $log[0];
443
    }
444 20
    if (isset($log[1])) {
445 20
      $logText = $log[1];
446
    }
447 20
    if (isset($log[2])) {
448 1
      $logType = $log[2];
449
    }
450
451
    if (
452 20
        $logClass
453
        &&
454 20
        class_exists($logClass)
455
        &&
456 20
        method_exists($logClass, $logMethod)
457
    ) {
458
      $logClass::$logMethod($logText, $logType);
459
    }
460 20
  }
461
462
  /**
463
   * Check is the current user is a developer.
464
   *
465
   * INFO:
466
   * By default we will return "true" if the remote-ip-address is localhost or
467
   * if the script is called via CLI. But you can also overwrite this method or
468
   * you can implement a global "checkForDev()"-function.
469
   *
470
   * @return bool
471
   */
472 18
  protected function checkForDev()
473
  {
474
    // init
475 18
    $return = false;
476
477 18
    if (function_exists('checkForDev')) {
478
      $return = checkForDev();
479
    } else {
480
481
      // for testing with dev-address
482 18
      $noDev = isset($_GET['noDev']) ? (int)$_GET['noDev'] : 0;
483 18
      $remoteIpAddress = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : false;
484
485
      if (
486 18
          $noDev != 1
487
          &&
488
          (
489 18
              $remoteIpAddress == '127.0.0.1'
490
              ||
491 18
              $remoteIpAddress == '::1'
492
              ||
493 18
              PHP_SAPI == 'cli'
494
          )
495
      ) {
496 18
        $return = true;
497
      }
498
    }
499
500 18
    return $return;
501
  }
502
503
  /**
504
   * Execute a sql-query and return the result-array for select-statements.
505
   *
506
   * @param $query
507
   *
508
   * @return mixed
509
   * @deprecated
510
   * @throws \Exception
511
   */
512
  public static function qry($query)
513
  {
514
    $db = self::getInstance();
515
516
    $args = func_get_args();
517
    $query = array_shift($args);
518
    $query = str_replace('?', '%s', $query);
519
    $args = array_map(
520
        array(
521
            $db,
522
            'escape',
523
        ),
524
        $args
525
    );
526
    array_unshift($args, $query);
527
    $query = call_user_func_array('sprintf', $args);
528
    $result = $db->query($query);
529
530
    if ($result instanceof Result) {
531
      $return = $result->fetchAllArray();
532
    } else {
533
      $return = $result;
534
    }
535
536
    if ($return || is_array($return)) {
537
      return $return;
538
    } else {
539
      return false;
540
    }
541
  }
542
543
  /**
544
   * getInstance()
545
   *
546
   * @param string      $hostname
547
   * @param string      $username
548
   * @param string      $password
549
   * @param string      $database
550
   * @param string      $port          default is (int)3306
551
   * @param string      $charset       default is 'utf8', but if you need 4-byte chars, then your tables need
552
   *                                   the 'utf8mb4'-charset
553
   * @param bool|string $exit_on_error use a empty string "" or false to disable it
554
   * @param bool|string $echo_on_error use a empty string "" or false to disable it
555
   * @param string      $logger_class_name
556
   * @param string      $logger_level
557
   * @param bool|string $session_to_db use a empty string "" or false to disable it
558
   *
559
   * @return \voku\db\DB
560
   */
561 39
  public static function getInstance($hostname = '', $username = '', $password = '', $database = '', $port = '', $charset = '', $exit_on_error = '', $echo_on_error = '', $logger_class_name = '', $logger_level = '', $session_to_db = '')
562
  {
563
    /**
564
     * @var $instance DB[]
565
     */
566 39
    static $instance = array();
567
568
    /**
569
     * @var $firstInstance DB
570
     */
571 39
    static $firstInstance = null;
572
573
    if (
574 39
        $hostname . $username . $password . $database . $port . $charset == ''
575
        &&
576 39
        null !== $firstInstance
577
    ) {
578 7
      return $firstInstance;
579
    }
580
581 39
    $connection = md5(
582 39
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . (int)$session_to_db
583
    );
584
585 39
    if (!isset($instance[$connection])) {
586 10
      $instance[$connection] = new self(
587
          $hostname,
588
          $username,
589
          $password,
590
          $database,
591
          $port,
592
          $charset,
593
          $exit_on_error,
594
          $echo_on_error,
595
          $logger_class_name,
596
          $logger_level,
597
          $session_to_db
598
      );
599
600 4
      if (null === $firstInstance) {
601 1
        $firstInstance = $instance[$connection];
602
      }
603
    }
604
605 39
    return $instance[$connection];
606
  }
607
608
  /**
609
   * Execute a sql-query.
610
   *
611
   * @param string        $sql            sql-query
612
   *
613
   * @param array|boolean $params         "array" of sql-query-parameters
614
   *                                      "false" if you don't need any parameter (default)
615
   *
616
   * @return bool|int|Result              "Result" by "<b>SELECT</b>"-queries<br />
617
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
618
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
619
   *                                      "true" by e.g. "DROP"-queries<br />
620
   *                                      "false" on error
621
   *
622
   * @throws \Exception
623
   */
624 22
  public function query($sql = '', $params = false)
625
  {
626 22
    if (!$this->isReady()) {
627
      return false;
628
    }
629
630 22
    if (!$sql || $sql === '') {
631 4
      $this->_displayError('Can\'t execute an empty Query', false);
632
633 4
      return false;
634
    }
635
636
    if (
637 20
        $params !== false
638
        &&
639 20
        is_array($params)
640
        &&
641 20
        count($params) > 0
642
    ) {
643 1
      $sql = $this->_parseQueryParams($sql, $params);
644
    }
645
646 20
    $query_start_time = microtime(true);
647 20
    $result = mysqli_query($this->link, $sql);
648 20
    $query_duration = microtime(true) - $query_start_time;
649 20
    $this->query_count++;
650
651 20
    $resultCount = 0;
652 20
    if ($result instanceof \mysqli_result) {
653 17
      $resultCount = (int)$result->num_rows;
654
    }
655 20
    $this->_logQuery($sql, $query_duration, $resultCount);
656
657
    if (
658 20
        $result !== null
659
        &&
660 20
        $result instanceof \mysqli_result
661
    ) {
662
663
      // return query result object
664 17
      return new Result($sql, $result);
665
666
    } else {
667
      // is the query successful
668 17
      if ($result === true) {
669
670 15
        if (preg_match('/^\s*"?(INSERT|UPDATE|DELETE|REPLACE)\s+/i', $sql)) {
671
672
          // it is an "INSERT" || "REPLACE"
673 15
          if ($this->insert_id() > 0) {
674 14
            return (int)$this->insert_id();
675
          }
676
677
          // it is an "UPDATE" || "DELETE"
678 6
          if ($this->affected_rows() > 0) {
679 6
            return (int)$this->affected_rows();
680
          }
681
        }
682
683
        return true;
684
      } else {
685 8
        $this->queryErrorHandling(mysqli_error($this->link), $sql, $params);
686
      }
687
    }
688
689 8
    return false;
690
  }
691
692
  /**
693
   * _parseQueryParams
694
   *
695
   * @param string $sql
696
   * @param array  $params
697
   *
698
   * @return string
699
   */
700 1
  private function _parseQueryParams($sql, array $params)
701
  {
702
703
    // is there anything to parse?
704 1
    if (strpos($sql, '?') === false) {
705
      return $sql;
706
    }
707
708 1
    if (count($params) > 0) {
709 1
      $parseKey = md5(uniqid(mt_rand(), true));
710 1
      $sql = str_replace('?', $parseKey, $sql);
711
712 1
      $k = 0;
713 1
      while (strpos($sql, $parseKey) !== false) {
714 1
        $value = $this->secure($params[$k]);
715 1
        $sql = preg_replace("/$parseKey/", $value, $sql, 1);
716 1
        $k++;
717
      }
718
    }
719
720 1
    return $sql;
721
  }
722
723
  /**
724
   * Try to secure a variable, so can you use it in sql-queries.
725
   *
726
   * int: (also strings that contains only an int-value)
727
   * 1. parse into (int)
728
   *
729
   * strings:
730
   * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'
731
   * 2. trim whitespace
732
   * 3. trim '
733
   * 4. escape the string (and remove non utf-8 chars)
734
   * 5. trim ' again (because we maybe removed some chars)
735
   * 6. add ' around the new string
736
   *
737
   * @param mixed $var
738
   *
739
   * @return string | null
740
   */
741 14
  public function secure($var)
742
  {
743
    // save the current value as int (for later usage)
744 14
    if (!is_object($var)) {
745 14
      $varInt = (int)$var;
746
    }
747
748
    if (
749 14
        (isset($varInt, $var[0]) && $var[0] != '0' && "$varInt" == $var)
0 ignored issues
show
Bug introduced by
The variable $varInt does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
750
        ||
751 14
        is_int($var)
752
        ||
753 14
        is_bool($var)
754
    ) {
755
756
      // "int" || int || bool
757
758 12
      $var = (int)$var;
759
760 14
    } elseif (is_string($var)) {
761
762
      // "string"
763
764 14
      if (!in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
765 14
        $var = "'" . trim($this->escape(trim(trim((string)$var), "'")), "'") . "'";
766
      }
767
768 2 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...
769
770
      // float
771
772 2
      $var = number_format((float)str_replace(',', '.', $var), 8, '.', '');
773
774 1
    } elseif (is_array($var)) {
775
776
      // array
777
778 1
      $var = null;
779
780
    } elseif ($var instanceof \DateTime) {
781
782
      // "DateTime"-object
783
784
      try {
785 1
        $var = "'" . $this->escape($var->format('Y-m-d H:i:s'), false, false) . "'";
786
      } catch (\Exception $e) {
787 1
        $var = null;
788
      }
789
790
    } else {
791
792
      // fallback ...
793
794
      $var = "'" . trim($this->escape(trim(trim((string)$var), "'")), "'") . "'";
795
796
    }
797
798 14
    return $var;
799
  }
800
801
  /**
802
   * Escape
803
   *
804
   * @param mixed $var boolean: convert into "integer"<br />
805
   *                   int: convert into "integer"<br />
806
   *                   float: convert into "float" and replace "," with "."<br />
807
   *                   array: run escape() for every key => value<br />
808
   *                   string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
809
   * @param bool  $stripe_non_utf8
810
   * @param bool  $html_entity_decode
811
   * @param bool  $array_to_string
812
   *
813
   * @return array|bool|float|int|string
814
   */
815 17
  public function escape($var = '', $stripe_non_utf8 = true, $html_entity_decode = true, $array_to_string = false)
816
  {
817
    // save the current value as int (for later usage)
818 17
    if (!is_object($var)) {
819 17
      $varInt = (int)$var;
820
    }
821
822
    if (
823 17
        (isset($varInt, $var[0]) && $var[0] != '0' && "$varInt" == $var)
0 ignored issues
show
Bug introduced by
The variable $varInt does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
824
        ||
825 17
        is_int($var)
826
        ||
827 17
        is_bool($var)
828
    ) {
829
830
      // "int" || int || bool
831
832 3
      return (int)$var;
833
834 17 View Code Duplication
    } elseif (is_float($var)) {
835
836
      // float
837
838 1
      return number_format((float)str_replace(',', '.', $var), 8, '.', '');
839
840 17
    } elseif (is_array($var)) {
841
842
      // array
843
844 1
      $varCleaned = array();
845 1
      foreach ($var as $key => $value) {
846
847 1
        $key = (string)$this->escape($key, $stripe_non_utf8, $html_entity_decode);
848 1
        $value = (string)$this->escape($value, $stripe_non_utf8, $html_entity_decode);
849
850 1
        $varCleaned[$key] = $value;
851
      }
852
853 1
      if ($array_to_string === true) {
854 1
        $varCleaned = implode(',', $varCleaned);
855
856 1
        return $varCleaned;
857
      } else {
858 1
        return (array)$varCleaned;
859
      }
860
    }
861
862 17
    if (is_string($var)) {
863
864
      // "string"
865
866 17
      if ($stripe_non_utf8 === true) {
867 17
        $var = UTF8::cleanup($var);
868
      }
869
870 17
      if ($html_entity_decode === true) {
871
        // use no-html-entity for db
872 17
        $var = UTF8::html_entity_decode($var);
873
      }
874
875 17
      $var = get_magic_quotes_gpc() ? stripslashes($var) : $var;
876
877 17
      $var = mysqli_real_escape_string($this->getLink(), $var);
878
879 17
      return (string)$var;
880
    } else {
881
      return false;
882
    }
883
  }
884
885
  /**
886
   * Get the mysqli-link (link identifier returned by mysqli-connect).
887
   *
888
   * @return \mysqli
889
   */
890 17
  public function getLink()
891
  {
892 17
    return $this->link;
893
  }
894
895
  /**
896
   * Log the current query via "$this->logger".
897
   *
898
   * @param string $sql     sql-query
899
   * @param int    $duration
900
   * @param int    $results result counter
901
   *
902
   * @return bool
903
   */
904 21
  private function _logQuery($sql, $duration, $results)
905
  {
906 21
    $logLevelUse = strtolower($this->logger_level);
907
908
    if (
909 21
        $logLevelUse != 'trace'
910
        &&
911 21
        $logLevelUse != 'debug'
912
    ) {
913 20
      return false;
914
    }
915
916 1
    $info = 'time => ' . round($duration, 5) . ' - ' . 'results => ' . $results . ' - ' . 'SQL => ' . UTF8::htmlentities($sql);
917 1
    $fileInfo = $this->getFileAndLineFromSql();
918
919 1
    $this->logger(
920
        array(
921 1
            'debug',
922 1
            '<strong>' . date('d. m. Y G:i:s') . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . '):</strong> ' . $info . '<br>',
923 1
            'sql',
924
        )
925
    );
926
927 1
    return true;
928
  }
929
930
  /**
931
   * Returns the auto generated id used in the last query.
932
   *
933
   * @return int|string
934
   */
935 15
  public function insert_id()
936
  {
937 15
    return mysqli_insert_id($this->link);
938
  }
939
940
  /**
941
   * Gets the number of affected rows in a previous MySQL operation.
942
   *
943
   * @return int
944
   */
945 6
  public function affected_rows()
946
  {
947 6
    return mysqli_affected_rows($this->link);
948
  }
949
950
  /**
951
   * Error-handling for the sql-query.
952
   *
953
   * @param string     $errorMsg
954
   * @param string     $sql
955
   * @param array|bool $sqlParams false if there wasn't any parameter
956
   *
957
   * @throws \Exception
958
   */
959 9
  protected function queryErrorHandling($errorMsg, $sql, $sqlParams = false)
960
  {
961 9
    if ($errorMsg == 'DB server has gone away' || $errorMsg == 'MySQL server has gone away') {
962 1
      static $reconnectCounter;
963
964
      // exit if we have more then 3 "DB server has gone away"-errors
965 1
      if ($reconnectCounter > 3) {
966
        $this->mailToAdmin('SQL-Fatal-Error', $errorMsg . ":\n<br />" . $sql, 5);
967
        throw new \Exception($errorMsg);
968
      } else {
969 1
        $this->mailToAdmin('SQL-Error', $errorMsg . ":\n<br />" . $sql);
970
971
        // reconnect
972 1
        $reconnectCounter++;
973 1
        $this->reconnect(true);
974
975
        // re-run the current query
976 1
        $this->query($sql, $sqlParams);
977
      }
978
    } else {
979 8
      $this->mailToAdmin('SQL-Warning', $errorMsg . ":\n<br />" . $sql);
980
981
      // this query returned an error, we must display it (only for dev) !!!
982 8
      $this->_displayError($errorMsg . ' | ' . $sql);
983
    }
984 9
  }
985
986
  /**
987
   * send a error mail to the admin / dev
988
   *
989
   * @param string $subject
990
   * @param string $htmlBody
991
   * @param int    $priority
992
   */
993 9
  private function mailToAdmin($subject, $htmlBody, $priority = 3)
994
  {
995 9
    if (function_exists('mailToAdmin')) {
996
      mailToAdmin($subject, $htmlBody, $priority);
997
    } else {
998
999 9
      if ($priority == 3) {
1000 9
        $this->logger(
1001
            array(
1002 9
                'debug',
1003 9
                $subject . ' | ' . $htmlBody,
1004
            )
1005
        );
1006
      } elseif ($priority > 3) {
1007
        $this->logger(
1008
            array(
1009
                'error',
1010
                $subject . ' | ' . $htmlBody,
1011
            )
1012
        );
1013
      } elseif ($priority < 3) {
1014
        $this->logger(
1015
            array(
1016
                'info',
1017
                $subject . ' | ' . $htmlBody,
1018
            )
1019
        );
1020
      }
1021
1022
    }
1023 9
  }
1024
1025
  /**
1026
   * Reconnect to the MySQL-Server.
1027
   *
1028
   * @param bool $checkViaPing
1029
   *
1030
   * @return bool
1031
   */
1032 2
  public function reconnect($checkViaPing = false)
1033
  {
1034 2
    $ping = false;
1035
1036 2
    if ($checkViaPing === true) {
1037 2
      $ping = $this->ping();
1038
    }
1039
1040 2
    if ($ping !== true) {
1041 2
      $this->connected = false;
1042 2
      $this->connect();
1043
    }
1044
1045 2
    return $this->isReady();
1046
  }
1047
1048
  /**
1049
   * Pings a server connection, or tries to reconnect
1050
   * if the connection has gone down.
1051
   *
1052
   * @return boolean
1053
   */
1054 3
  public function ping()
1055
  {
1056
    if (
1057 3
        $this->link
1058
        &&
1059 3
        $this->link instanceof \mysqli
1060
    ) {
1061
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
1062 3
      return @mysqli_ping($this->link);
1063
    } else {
1064
      return false;
1065
    }
1066
  }
1067
1068
  /**
1069
   * Execute select/insert/update/delete sql-queries.
1070
   *
1071
   * @param string $query    sql-query
1072
   * @param bool   $useCache use cache?
1073
   * @param int    $cacheTTL cache-ttl in seconds
1074
   *
1075
   * @return mixed "array" by "<b>SELECT</b>"-queries<br />
1076
   *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
1077
   *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1078
   *               "true" by e.g. "DROP"-queries<br />
1079
   *               "false" on error
1080
   *
1081
   */
1082 3
  public static function execSQL($query, $useCache = false, $cacheTTL = 3600)
1083
  {
1084 3
    $db = self::getInstance();
1085
1086 3
    if ($useCache === true) {
1087 1
      $cache = new Cache(null, null, false, $useCache);
1088 1
      $cacheKey = 'sql-' . md5($query);
1089
1090
      if (
1091 1
          $cache->getCacheIsReady() === true
1092
          &&
1093 1
          $cache->existsItem($cacheKey)
1094
      ) {
1095 1
        return $cache->getItem($cacheKey);
1096
      }
1097
1098
    } else {
1099 3
      $cache = false;
1100
    }
1101
1102 3
    $result = $db->query($query);
1103
1104 3
    if ($result instanceof Result) {
1105
1106 1
      $return = $result->fetchAllArray();
1107
1108
      if (
1109 1
          isset($cacheKey)
1110
          &&
1111 1
          $useCache === true
1112
          &&
1113 1
          $cache instanceof Cache
1114
          &&
1115 1
          $cache->getCacheIsReady() === true
1116
      ) {
1117 1
        $cache->setItem($cacheKey, $return, $cacheTTL);
1118
      }
1119
1120
    } else {
1121 2
      $return = $result;
1122
    }
1123
1124 3
    return $return;
1125
  }
1126
1127
  /**
1128
   * Get the current charset.
1129
   *
1130
   * @return string
1131
   */
1132 1
  public function get_charset()
1133
  {
1134 1
    return $this->charset;
1135
  }
1136
1137
  /**
1138
   * Set the current charset.
1139
   *
1140
   * @param string $charset
1141
   *
1142
   * @return bool
1143
   */
1144 6
  public function set_charset($charset)
1145
  {
1146 6
    $this->charset = (string)$charset;
1147
1148 6
    $return = mysqli_set_charset($this->link, $charset);
1149
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
1150 6
    @mysqli_query($this->link, 'SET CHARACTER SET ' . $charset);
1151
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
1152 6
    @mysqli_query($this->link, "SET NAMES '" . ($charset == 'utf8' ? 'utf8mb4' : $charset) . "'");
1153
1154 6
    return $return;
1155
  }
1156
1157
  /**
1158
   * __wakeup
1159
   *
1160
   * @return void
1161
   */
1162 1
  public function __wakeup()
1163
  {
1164 1
    $this->reconnect();
1165 1
  }
1166
1167
  /**
1168
   * Get all table-names via "SHOW TABLES".
1169
   *
1170
   * @return array
1171
   */
1172 1
  public function getAllTables()
1173
  {
1174 1
    $query = 'SHOW TABLES';
1175 1
    $result = $this->query($query);
1176
1177 1
    return $result->fetchAllArray();
1178
  }
1179
1180
  /**
1181
   * Execute a sql-multi-query.
1182
   *
1183
   * @param string $sql
1184
   *
1185
   * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
1186
   *                        "boolean" by only "<b>INSERT</b>"-queries<br />
1187
   *                        "boolean" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1188
   *                        "boolean" by only by e.g. "DROP"-queries<br />
1189
   *
1190
   * @throws \Exception
1191
   */
1192 1
  public function multi_query($sql)
1193
  {
1194 1
    if (!$this->isReady()) {
1195
      return false;
1196
    }
1197
1198 1
    if (!$sql || $sql === '') {
1199 1
      $this->_displayError('Can\'t execute an empty Query', false);
1200
1201 1
      return false;
1202
    }
1203
1204 1
    $query_start_time = microtime(true);
1205 1
    $resultTmp = mysqli_multi_query($this->link, $sql);
1206 1
    $query_duration = microtime(true) - $query_start_time;
1207
1208 1
    $this->_logQuery($sql, $query_duration, 0);
1209
1210 1
    $returnTheResult = false;
1211 1
    $result = array();
1212 1
    if ($resultTmp) {
1213
      do {
1214 1
        $resultTmpInner = mysqli_store_result($this->link);
1215
1216
        if (
1217 1
            null !== $resultTmpInner
1218
            &&
1219 1
            $resultTmpInner instanceof \mysqli_result
1220
        ) {
1221 1
          $returnTheResult = true;
1222 1
          $result[] = new Result($sql, $resultTmpInner);
1223
        } else {
1224 1
          $errorMsg = mysqli_error($this->link);
1225
1226
          // is the query successful
1227 1
          if ($resultTmpInner === true || !$errorMsg) {
1228 1
            $result[] = true;
1229
          } else {
1230
            $result[] = false;
1231
1232
            $this->queryErrorHandling($errorMsg, $sql);
1233
          }
1234
        }
1235 1
      } while (mysqli_more_results($this->link) === true ? mysqli_next_result($this->link) : false);
1236
1237
    } else {
1238
1239
      $errorMsg = mysqli_error($this->link);
1240
1241
      if ($this->checkForDev() === true) {
1242
        echo "Info: maybe you have to increase your 'max_allowed_packet = 30M' in the config: 'my.conf' \n<br />";
1243
        echo 'Error:' . $errorMsg;
1244
      }
1245
1246
      $this->mailToAdmin('SQL-Error in mysqli_multi_query', $errorMsg . ":\n<br />" . $sql);
1247
    }
1248
1249
    // return the result only if there was a "SELECT"-query
1250 1
    if ($returnTheResult === true) {
1251 1
      return $result;
1252
    }
1253
1254 1
    if (!in_array(false, $result, true)) {
1255 1
      return true;
1256
    } else {
1257
      return false;
1258
    }
1259
  }
1260
1261
  /**
1262
   * alias: "beginTransaction()"
1263
   */
1264 1
  public function startTransaction()
1265
  {
1266 1
    $this->beginTransaction();
1267 1
  }
1268
1269
  /**
1270
   * Begins a transaction, by turning off auto commit.
1271
   *
1272
   * @return boolean this will return true or false indicating success of transaction
1273
   */
1274 4
  public function beginTransaction()
1275
  {
1276 4
    $this->clearErrors();
1277
1278 4
    if ($this->inTransaction() === true) {
1279 1
      $this->_displayError('Error mysql server already in transaction!', true);
1280
1281
      return false;
1282 4
    } elseif (mysqli_connect_errno()) {
1283
      $this->_displayError('Error connecting to mysql server: ' . mysqli_connect_error(), true);
1284
1285
      return false;
1286
    } else {
1287 4
      $this->_in_transaction = true;
1288 4
      mysqli_autocommit($this->link, false);
1289
1290 4
      return true;
1291
1292
    }
1293
  }
1294
1295
  /**
1296
   * Clear the errors in "$this->_errors".
1297
   *
1298
   * @return bool
1299
   */
1300 4
  public function clearErrors()
1301
  {
1302 4
    $this->_errors = array();
1303
1304 4
    return true;
1305
  }
1306
1307
  /**
1308
   * Check if we are in a transaction.
1309
   *
1310
   * @return boolean
1311
   */
1312 4
  public function inTransaction()
1313
  {
1314 4
    return $this->_in_transaction;
1315
  }
1316
1317
  /**
1318
   * Ends a transaction and commits if no errors, then ends autocommit.
1319
   *
1320
   * @return boolean this will return true or false indicating success of transactions
1321
   */
1322 2
  public function endTransaction()
1323
  {
1324
1325 2
    if (!$this->errors()) {
1326 1
      mysqli_commit($this->link);
1327 1
      $return = true;
1328
    } else {
1329 1
      $this->rollback();
1330 1
      $return = false;
1331
    }
1332
1333 2
    mysqli_autocommit($this->link, true);
1334 2
    $this->_in_transaction = false;
1335
1336 2
    return $return;
1337
  }
1338
1339
  /**
1340
   * Get all errors from "$this->_errors".
1341
   *
1342
   * @return array|false false === on errors
1343
   */
1344 2
  public function errors()
1345
  {
1346 2
    return count($this->_errors) > 0 ? $this->_errors : false;
1347
  }
1348
1349
  /**
1350
   * Rollback in a transaction.
1351
   */
1352 2
  public function rollback()
1353
  {
1354
    // init
1355 2
    $return = false;
1356
1357 2
    if ($this->_in_transaction === true) {
1358 2
      $return = mysqli_rollback($this->link);
1359 2
      mysqli_autocommit($this->link, true);
1360 2
      $this->_in_transaction = false;
1361
    }
1362
1363 2
    return $return;
1364
  }
1365
1366
  /**
1367
   * Execute a "insert"-query.
1368
   *
1369
   * @param string $table
1370
   * @param array  $data
1371
   * @param boolean|string $ignore
1372
   * 
1373
   * @return false|int false on error
1374
   */
1375 14
  public function insert($table, $data = array(), $ignore = false)
1376
  {
1377 14
    $table = trim($table);
1378
1379 14
    if ($table === '') {
1380 2
      $this->_displayError('invalid-table-name');
1381
1382 1
      return false;
1383
    }
1384
1385 13
    if (count($data) == 0) {
1386 3
      $this->_displayError('empty-data-for-INSERT');
1387
1388 2
      return false;
1389
    }
1390
1391 11
    $SET = $this->_parseArrayPair($data);
1392
1393 11
    $sql = 'INSERT '.($ignore?'IGNORE':''). 'INTO ' . $this->quote_string($table) . " SET $SET;";
1394
1395 11
    return $this->query($sql);
1396
  }
1397
1398
  /**
1399
   * Parses arrays with value pairs and generates SQL to use in queries.
1400
   *
1401
   * @param array  $arrayPair
1402
   * @param string $glue this is the separator
1403
   *
1404
   * @return string
1405
   */
1406 12
  private function _parseArrayPair($arrayPair, $glue = ',')
1407
  {
1408
    // init
1409 12
    $sql = '';
1410 12
    $pairs = array();
1411
1412 12
    if (!empty($arrayPair)) {
1413
1414 12
      foreach ($arrayPair as $_key => $_value) {
1415 12
        $_connector = '=';
1416 12
        $_key_upper = strtoupper($_key);
1417
1418 12
        if (strpos($_key_upper, ' NOT') !== false) {
1419 2
          $_connector = 'NOT';
1420
        }
1421
1422 12
        if (strpos($_key_upper, ' IS') !== false) {
1423 1
          $_connector = 'IS';
1424
        }
1425
1426 12
        if (strpos($_key_upper, ' IS NOT') !== false) {
1427 1
          $_connector = 'IS NOT';
1428
        }
1429
1430 12
        if (strpos($_key_upper, ' IN') !== false) {
1431 1
          $_connector = 'IN';
1432
        }
1433
1434 12
        if (strpos($_key_upper, ' NOT IN') !== false) {
1435 1
          $_connector = 'NOT IN';
1436
        }
1437
1438 12
        if (strpos($_key_upper, ' BETWEEN') !== false) {
1439 1
          $_connector = 'BETWEEN';
1440
        }
1441
1442 12
        if (strpos($_key_upper, ' NOT BETWEEN') !== false) {
1443 1
          $_connector = 'NOT BETWEEN';
1444
        }
1445
1446 12
        if (strpos($_key_upper, ' LIKE') !== false) {
1447 2
          $_connector = 'LIKE';
1448
        }
1449
1450 12
        if (strpos($_key_upper, ' NOT LIKE') !== false) {
1451 2
          $_connector = 'NOT LIKE';
1452
        }
1453
1454 12 View Code Duplication
        if (strpos($_key_upper, ' >') !== false && strpos($_key_upper, ' =') === false) {
1455 2
          $_connector = '>';
1456
        }
1457
1458 12 View Code Duplication
        if (strpos($_key_upper, ' <') !== false && strpos($_key_upper, ' =') === false) {
1459 1
          $_connector = '<';
1460
        }
1461
1462 12
        if (strpos($_key_upper, ' >=') !== false) {
1463 2
          $_connector = '>=';
1464
        }
1465
1466 12
        if (strpos($_key_upper, ' <=') !== false) {
1467 1
          $_connector = '<=';
1468
        }
1469
1470 12
        if (strpos($_key_upper, ' <>') !== false) {
1471 1
          $_connector = '<>';
1472
        }
1473
1474
        if (
1475 12
            is_array($_value)
1476
            &&
1477
            (
1478 1
                $_connector == 'NOT IN'
1479
                ||
1480 12
                $_connector == 'IN'
1481
            )
1482
        ) {
1483 1
          foreach ($_value as $oldKey => $oldValue) {
1484
            /** @noinspection AlterInForeachInspection */
1485 1
            $_value[$oldKey] = $this->secure($oldValue);
1486
          }
1487 1
          $_value = '(' . implode(',', $_value) . ')';
1488
        } elseif (
1489 12
            is_array($_value)
1490
            &&
1491
            (
1492 1
                $_connector == 'NOT BETWEEN'
1493
                ||
1494 12
                $_connector == 'BETWEEN'
1495
            )
1496
        ) {
1497 1
          foreach ($_value as $oldKey => $oldValue) {
1498
            /** @noinspection AlterInForeachInspection */
1499 1
            $_value[$oldKey] = $this->secure($oldValue);
1500
          }
1501 1
          $_value = '(' . implode(' AND ', $_value) . ')';
1502
        } else {
1503 12
          $_value = $this->secure($_value);
1504
        }
1505
1506 12
        $quoteString = $this->quote_string(trim(str_ireplace($_connector, '', $_key)));
1507 12
        $pairs[] = ' ' . $quoteString . ' ' . $_connector . ' ' . $_value . " \n";
1508
      }
1509
1510 12
      $sql = implode($glue, $pairs);
1511
    }
1512
1513 12
    return $sql;
1514
  }
1515
1516
  /**
1517
   * Quote && Escape e.g. a table name string.
1518
   *
1519
   * @param string $str
1520
   *
1521
   * @return string
1522
   */
1523 14
  public function quote_string($str)
1524
  {
1525 14
    return '`' . $this->escape($str, false, false) . '`';
1526
  }
1527
1528
  /**
1529
   * Get errors from "$this->_errors".
1530
   *
1531
   * @return array
1532
   */
1533 1
  public function getErrors()
1534
  {
1535 1
    return $this->_errors;
1536
  }
1537
1538
  /**
1539
   * Execute a "replace"-query.
1540
   *
1541
   * @param string $table
1542
   * @param array  $data
1543
   *
1544
   * @return false|int false on error
1545
   */
1546 1
  public function replace($table, $data = array())
1547
  {
1548
1549 1
    $table = trim($table);
1550
1551 1
    if ($table === '') {
1552 1
      $this->_displayError('invalid table name');
1553
1554 1
      return false;
1555
    }
1556
1557 1
    if (count($data) == 0) {
1558 1
      $this->_displayError('empty data for REPLACE');
1559
1560 1
      return false;
1561
    }
1562
1563
    // extracting column names
1564 1
    $columns = array_keys($data);
1565 1
    foreach ($columns as $k => $_key) {
1566
      /** @noinspection AlterInForeachInspection */
1567 1
      $columns[$k] = $this->quote_string($_key);
1568
    }
1569
1570 1
    $columns = implode(',', $columns);
1571
1572
    // extracting values
1573 1
    foreach ($data as $k => $_value) {
1574
      /** @noinspection AlterInForeachInspection */
1575 1
      $data[$k] = $this->secure($_value);
1576
    }
1577 1
    $values = implode(',', $data);
1578
1579 1
    $sql = 'REPLACE INTO ' . $this->quote_string($table) . " ($columns) VALUES ($values);";
1580
1581 1
    return $this->query($sql);
1582
  }
1583
1584
  /**
1585
   * Execute a "update"-query.
1586
   *
1587
   * @param string       $table
1588
   * @param array        $data
1589
   * @param array|string $where
1590
   *
1591
   * @return false|int false on error
1592
   */
1593 5
  public function update($table, $data = array(), $where = '1=1')
1594
  {
1595 5
    $table = trim($table);
1596
1597 5
    if ($table === '') {
1598 1
      $this->_displayError('invalid table name');
1599
1600 1
      return false;
1601
    }
1602
1603 5
    if (count($data) == 0) {
1604 1
      $this->_displayError('empty data for UPDATE');
1605
1606 1
      return false;
1607
    }
1608
1609 5
    $SET = $this->_parseArrayPair($data);
1610
1611 5
    if (is_string($where)) {
1612 1
      $WHERE = $this->escape($where, false, false);
1613 5
    } elseif (is_array($where)) {
1614 4
      $WHERE = $this->_parseArrayPair($where, 'AND');
1615
    } else {
1616 1
      $WHERE = '';
1617
    }
1618
1619 5
    $sql = 'UPDATE ' . $this->quote_string($table) . " SET $SET WHERE ($WHERE);";
1620
1621 5
    return $this->query($sql);
1622
  }
1623
1624
  /**
1625
   * Execute a "delete"-query.
1626
   *
1627
   * @param string       $table
1628
   * @param string|array $where
1629
   *
1630
   * @return false|int false on error
1631
   */
1632 1 View Code Duplication
  public function delete($table, $where)
1633
  {
1634
1635 1
    $table = trim($table);
1636
1637 1
    if ($table === '') {
1638 1
      $this->_displayError('invalid table name');
1639
1640 1
      return false;
1641
    }
1642
1643 1
    if (is_string($where)) {
1644 1
      $WHERE = $this->escape($where, false, false);
1645 1
    } elseif (is_array($where)) {
1646 1
      $WHERE = $this->_parseArrayPair($where, 'AND');
1647
    } else {
1648 1
      $WHERE = '';
1649
    }
1650
1651 1
    $sql = 'DELETE FROM ' . $this->quote_string($table) . " WHERE ($WHERE);";
1652
1653 1
    return $this->query($sql);
1654
  }
1655
1656
  /**
1657
   * Execute a "select"-query.
1658
   *
1659
   * @param string       $table
1660
   * @param string|array $where
1661
   *
1662
   * @return false|Result false on error
1663
   */
1664 13 View Code Duplication
  public function select($table, $where = '1=1')
1665
  {
1666
1667 13
    if ($table === '') {
1668 1
      $this->_displayError('invalid table name');
1669
1670 1
      return false;
1671
    }
1672
1673 13
    if (is_string($where)) {
1674 3
      $WHERE = $this->escape($where, false, false);
1675 11
    } elseif (is_array($where)) {
1676 11
      $WHERE = $this->_parseArrayPair($where, 'AND');
1677
    } else {
1678 1
      $WHERE = '';
1679
    }
1680
1681 13
    $sql = 'SELECT * FROM ' . $this->quote_string($table) . " WHERE ($WHERE);";
1682
1683 13
    return $this->query($sql);
1684
  }
1685
1686
  /**
1687
   * Get the last sql-error.
1688
   *
1689
   * @return string false on error
1690
   */
1691 1
  public function lastError()
1692
  {
1693 1
    return count($this->_errors) > 0 ? end($this->_errors) : false;
1694
  }
1695
1696
  /**
1697
   * __destruct
1698
   *
1699
   */
1700 1
  public function __destruct()
1701
  {
1702
    // close the connection only if we don't save PHP-SESSION's in DB
1703 1
    if ($this->session_to_db === false) {
1704 1
      $this->close();
1705
    }
1706 1
  }
1707
1708
  /**
1709
   * Closes a previously opened database connection.
1710
   */
1711 3
  public function close()
1712
  {
1713 3
    $this->connected = false;
1714
1715 3
    if ($this->link) {
1716 3
      mysqli_close($this->link);
1717
    }
1718 3
  }
1719
1720
  /**
1721
   * prevent the instance from being cloned
1722
   *
1723
   * @return void
1724
   */
1725
  private function __clone()
1726
  {
1727
  }
1728
1729
}
1730