Completed
Push — master ( 86cd12...911a79 )
by Lars
02:29
created

DB::_parseQueryParams()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4.0092

Importance

Changes 8
Bugs 2 Features 2
Metric Value
c 8
b 2
f 2
dl 0
loc 21
ccs 11
cts 12
cp 0.9167
rs 9.0534
cc 4
eloc 12
nc 3
nop 2
crap 4.0092
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 8
      $this->link = mysqli_init();
0 ignored issues
show
Documentation Bug introduced by
It seems like mysqli_init() of type object<mysql> is incompatible with the declared type object<mysqli> of property $link.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
290
291 8
      $this->connected = @mysqli_real_connect(
292 8
          $this->link,
293 8
          $this->hostname,
294 8
          $this->username,
295 8
          $this->password,
296 8
          $this->database,
297 8
          $this->port,
298 8
          $this->socket
299
      );
300 3
    } catch (\Exception $e) {
301 3
      $this->_displayError('Error connecting to mysql server: ' . $e->getMessage(), true);
302
    }
303 5
    mysqli_report(MYSQLI_REPORT_OFF);
304
305 5
    if (!$this->connected) {
306
      $this->_displayError('Error connecting to mysql server: ' . mysqli_connect_error(), true);
307
    } else {
308 5
      $this->set_charset($this->charset);
309
    }
310
311 5
    return $this->isReady();
312
  }
313
314
  /**
315
   * Check if db-connection is ready.
316
   *
317
   * @return boolean
318
   */
319 31
  public function isReady()
320
  {
321 31
    return $this->connected ? true : false;
322
  }
323
324
  /**
325
   * Display SQL-Errors or throw Exceptions (for dev).
326
   *
327
   * @param string       $error
328
   * @param null|boolean $force_exception_after_error
329
   *
330
   * @throws \Exception
331
   */
332 19
  private function _displayError($error, $force_exception_after_error = null)
333
  {
334 19
    $fileInfo = $this->getFileAndLineFromSql();
335
336 19
    $this->logger(
337
        array(
338 19
            'error',
339 19
            '<strong>' . date(
340 19
                'd. m. Y G:i:s'
341 19
            ) . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . ') (sql-error):</strong> ' . $error . '<br>',
342
        )
343
    );
344
345 19
    $this->_errors[] = $error;
346
347 19
    if ($this->checkForDev() === true) {
348
349 19
      if ($this->echo_on_error) {
350 4
        $box_border = $this->css_mysql_box_border;
351 4
        $box_bg = $this->css_mysql_box_bg;
352
353
        echo '
354 4
        <div class="OBJ-mysql-box" style="border:' . $box_border . '; background:' . $box_bg . '; padding:10px; margin:10px;">
355
          <b style="font-size:14px;">MYSQL Error:</b>
356
          <code style="display:block;">
357 4
            file / line: ' . $fileInfo['file'] . ' / ' . $fileInfo['line'] . '
358 4
            ' . $error . '
359
          </code>
360
        </div>
361 4
        ';
362
      }
363
364 19
      if ($force_exception_after_error === true) {
365 4
        throw new \Exception($error);
366 15
      } elseif ($force_exception_after_error === false) {
367
        // nothing
368 12
      } elseif ($force_exception_after_error === null) {
369
        // default
370 12
        if ($this->exit_on_error === true) {
371 2
          throw new \Exception($error);
372
        }
373
      }
374
    }
375 13
  }
376
377
  /**
378
   * Try to get the file & line from the current sql-query.
379
   *
380
   * @return array will return array['file'] and array['line']
381
   */
382 32
  private function getFileAndLineFromSql()
383
  {
384
    // init
385 32
    $return = array();
386 32
    $file = '';
387 32
    $line = '';
388
389 32
    if (Bootup::is_php('5.4') === true) {
390 32
      $referrer = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
391
    } else {
392
      $referrer = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
393
    }
394
395 32
    foreach ($referrer as $key => $ref) {
396
397
      if (
398 32
          $ref['function'] === 'execSQL'
399
          ||
400 32
          $ref['function'] === 'query'
401
          ||
402 32
          $ref['function'] === 'qry'
403
          ||
404 32
          $ref['function'] === 'insert'
405
          ||
406 32
          $ref['function'] === 'update'
407
          ||
408 32
          $ref['function'] === 'replace'
409
          ||
410 32
          $ref['function'] === 'delete'
411
      ) {
412 27
        $file = $referrer[$key]['file'];
413 32
        $line = $referrer[$key]['line'];
414
      }
415
416
    }
417
418 32
    $return['file'] = $file;
419 32
    $return['line'] = $line;
420
421 32
    return $return;
422
  }
423
424
  /**
425
   * Wrapper-Function for a "Logger"-Class.
426
   *
427
   * INFO:
428
   * The "Logger"-ClassName is set by "$this->logger_class_name",<br />
429
   * the "Logger"-Method is the [0] element from the "$log"-parameter,<br />
430
   * the text you want to log is the [1] element and<br />
431
   * the type you want to log is the next [2] element.
432
   *
433
   * @param string[] $log [method, text, type]<br />e.g.: array('error', 'this is a error', 'sql')
434
   */
435 32
  private function logger(array $log)
436
  {
437 32
    $logMethod = '';
438 32
    $logText = '';
439 32
    $logType = '';
440 32
    $logClass = $this->logger_class_name;
441
442 32
    if (isset($log[0])) {
443 32
      $logMethod = $log[0];
444
    }
445 32
    if (isset($log[1])) {
446 32
      $logText = $log[1];
447
    }
448 32
    if (isset($log[2])) {
449 23
      $logType = $log[2];
450
    }
451
452
    if (
453 32
        $logClass
454
        &&
455 32
        class_exists($logClass)
456
        &&
457 32
        method_exists($logClass, $logMethod)
458
    ) {
459
      $logClass::$logMethod($logText, $logType);
460
    }
461 32
  }
462
463
  /**
464
   * Check is the current user is a developer.
465
   *
466
   * INFO:
467
   * By default we will return "true" if the remote-ip-address is localhost or
468
   * if the script is called via CLI. But you can also overwrite this method or
469
   * you can implement a global "checkForDev()"-function.
470
   *
471
   * @return bool
472
   */
473 19
  protected function checkForDev()
474
  {
475
    // init
476 19
    $return = false;
477
478 19
    if (function_exists('checkForDev')) {
479
      $return = checkForDev();
480
    } else {
481
482
      // for testing with dev-address
483 19
      $noDev = isset($_GET['noDev']) ? (int)$_GET['noDev'] : 0;
484 19
      $remoteIpAddress = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : false;
485
486
      if (
487 19
          $noDev != 1
488
          &&
489
          (
490 19
              $remoteIpAddress === '127.0.0.1'
491
              ||
492 19
              $remoteIpAddress === '::1'
493
              ||
494 19
              PHP_SAPI === 'cli'
495
          )
496
      ) {
497 19
        $return = true;
498
      }
499
    }
500
501 19
    return $return;
502
  }
503
504
  /**
505
   * Execute a sql-query and return the result-array for select-statements.
506
   *
507
   * @param $query
508
   *
509
   * @return mixed
510
   * @deprecated
511
   * @throws \Exception
512
   */
513
  public static function qry($query)
514
  {
515
    $db = self::getInstance();
516
517
    $args = func_get_args();
518
    $query = array_shift($args);
519
    $query = str_replace('?', '%s', $query);
520
    $args = array_map(
521
        array(
522
            $db,
523
            'escape',
524
        ),
525
        $args
526
    );
527
    array_unshift($args, $query);
528
    $query = call_user_func_array('sprintf', $args);
529
    $result = $db->query($query);
530
531
    if ($result instanceof Result) {
532
      $return = $result->fetchAllArray();
533
    } else {
534
      $return = $result;
535
    }
536
537
    if ($return || is_array($return)) {
538
      return $return;
539
    } else {
540
      return false;
541
    }
542
  }
543
544
  /**
545
   * getInstance()
546
   *
547
   * @param string      $hostname
548
   * @param string      $username
549
   * @param string      $password
550
   * @param string      $database
551
   * @param string      $port          default is (int)3306
552
   * @param string      $charset       default is 'utf8', but if you need 4-byte chars, then your tables need
553
   *                                   the 'utf8mb4'-charset
554
   * @param bool|string $exit_on_error use a empty string "" or false to disable it
555
   * @param bool|string $echo_on_error use a empty string "" or false to disable it
556
   * @param string      $logger_class_name
557
   * @param string      $logger_level
558
   * @param bool|string $session_to_db use a empty string "" or false to disable it
559
   *
560
   * @return \voku\db\DB
561
   */
562 41
  public static function getInstance($hostname = '', $username = '', $password = '', $database = '', $port = '', $charset = '', $exit_on_error = '', $echo_on_error = '', $logger_class_name = '', $logger_level = '', $session_to_db = '')
563
  {
564
    /**
565
     * @var $instance DB[]
566
     */
567 41
    static $instance = array();
568
569
    /**
570
     * @var $firstInstance DB
571
     */
572 41
    static $firstInstance = null;
573
574
    if (
575 41
        $hostname . $username . $password . $database . $port . $charset == ''
576
        &&
577 41
        null !== $firstInstance
578
    ) {
579 9
      return $firstInstance;
580
    }
581
582 41
    $connection = md5(
583 41
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . (int)$session_to_db
584
    );
585
586 41
    if (!isset($instance[$connection])) {
587 10
      $instance[$connection] = new self(
588
          $hostname,
589
          $username,
590
          $password,
591
          $database,
592
          $port,
593
          $charset,
594
          $exit_on_error,
595
          $echo_on_error,
596
          $logger_class_name,
597
          $logger_level,
598
          $session_to_db
599
      );
600
601 4
      if (null === $firstInstance) {
602 1
        $firstInstance = $instance[$connection];
603
      }
604
    }
605
606 41
    return $instance[$connection];
607
  }
608
609
  /**
610
   * Execute a sql-query.
611
   *
612
   * @param string        $sql            sql-query
613
   *
614
   * @param array|boolean $params         "array" of sql-query-parameters
615
   *                                      "false" if you don't need any parameter (default)
616
   *
617
   * @return bool|int|Result              "Result" by "<b>SELECT</b>"-queries<br />
618
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
619
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
620
   *                                      "true" by e.g. "DROP"-queries<br />
621
   *                                      "false" on error
622
   *
623
   * @throws \Exception
624
   */
625 24
  public function query($sql = '', $params = false)
626
  {
627 24
    if (!$this->isReady()) {
628
      return false;
629
    }
630
631 24
    if (!$sql || $sql === '') {
632 4
      $this->_displayError('Can\'t execute an empty Query', false);
633
634 4
      return false;
635
    }
636
637
    if (
638 22
        $params !== false
639
        &&
640 22
        is_array($params)
641
        &&
642 22
        count($params) > 0
643
    ) {
644 2
      $sql = $this->_parseQueryParams($sql, $params);
645
    }
646
647 22
    $query_start_time = microtime(true);
648 22
    $result = mysqli_query($this->link, $sql);
649 22
    $query_duration = microtime(true) - $query_start_time;
650 22
    $this->query_count++;
651
652 22
    $resultCount = 0;
653 22
    if ($result instanceof \mysqli_result) {
654 19
      $resultCount = (int)$result->num_rows;
655
    }
656 22
    $this->_logQuery($sql, $query_duration, $resultCount);
657
658 22
    if ($result instanceof \mysqli_result) {
659
660
      // return query result object
661 19
      return new Result($sql, $result);
662
663
    } else {
664
665
      // is the query successful
666 18
      if ($result === true) {
667
668 16
        if (preg_match('/^\s*"?(INSERT|UPDATE|DELETE|REPLACE)\s+/i', $sql)) {
669
670
          // it is an "INSERT" || "REPLACE"
671 16
          if ($this->insert_id() > 0) {
672 15
            return (int)$this->insert_id();
673
          }
674
675
          // it is an "UPDATE" || "DELETE"
676 7
          if ($this->affected_rows() > 0) {
677 7
            return (int)$this->affected_rows();
678
          }
679
        }
680
681
        return true;
682
      } else {
683 8
        $this->queryErrorHandling(mysqli_error($this->link), $sql, $params);
684
      }
685
    }
686
687 8
    return false;
688
  }
689
690
  /**
691
   * _parseQueryParams
692
   *
693
   * @param string $sql
694
   * @param array  $params
695
   *
696
   * @return string
697
   */
698 2
  private function _parseQueryParams($sql, array $params)
699
  {
700
    // is there anything to parse?
701 2
    if (strpos($sql, '?') === false) {
702
      return $sql;
703
    }
704
705 2
    if (count($params) > 0) {
706 2
      $parseKey = md5(uniqid(mt_rand(), true));
707 2
      $sql = str_replace('?', $parseKey, $sql);
708
709 2
      $k = 0;
710 2
      while (strpos($sql, $parseKey) !== false) {
711 2
        $value = $this->secure($params[$k]);
712 2
        $sql = preg_replace("/$parseKey/", $value, $sql, 1);
713 2
        $k++;
714
      }
715
    }
716
717 2
    return $sql;
718
  }
719
720
  /**
721
   * Try to secure a variable, so can you use it in sql-queries.
722
   *
723
   * int: (also strings that contains only an int-value)
724
   * 1. parse into (int)
725
   *
726
   * strings:
727
   * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'
728
   * 2. trim whitespace
729
   * 3. trim '
730
   * 4. escape the string (and remove non utf-8 chars)
731
   * 5. trim ' again (because we maybe removed some chars)
732
   * 6. add ' around the new string
733
   *
734
   * @param mixed $var
735
   *
736
   * @return string | null
737
   */
738 15
  public function secure($var)
739
  {
740
    // save the current value as int (for later usage)
741 15
    if (!is_object($var)) {
742 15
      $varInt = (int)$var;
743
    }
744
745
    if (
746 15
        (isset($varInt, $var[0]) && $var[0] != '0' && "$varInt" == $var)
0 ignored issues
show
Bug introduced by
The variable $varInt does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

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

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
821
        ||
822 19
        is_int($var)
823
        ||
824 19
        is_bool($var)
825
    ) {
826
827
      // "int" || int || bool
828
829 4
      return (int)$var;
830
831 19 View Code Duplication
    } elseif (is_float($var)) {
832
833
      // float
834
835 1
      return number_format((float)str_replace(',', '.', $var), 8, '.', '');
836
837 19
    } elseif (is_array($var)) {
838
839
      // array
840
841 1
      $varCleaned = array();
842 1
      foreach ($var as $key => $value) {
843
844 1
        $key = (string)$this->escape($key, $stripe_non_utf8, $html_entity_decode);
845 1
        $value = (string)$this->escape($value, $stripe_non_utf8, $html_entity_decode);
846
847 1
        $varCleaned[$key] = $value;
848
      }
849
850 1
      if ($array_to_string === true) {
851 1
        $varCleaned = implode(',', $varCleaned);
852
853 1
        return $varCleaned;
854
      } else {
855 1
        return (array)$varCleaned;
856
      }
857
    }
858
859 19
    if (is_string($var)) {
860
861
      // "string"
862
863 19
      if ($stripe_non_utf8 === true) {
864 19
        $var = UTF8::cleanup($var);
865
      }
866
867 19
      if ($html_entity_decode === true) {
868
        // use no-html-entity for db
869 19
        $var = UTF8::html_entity_decode($var);
870
      }
871
872 19
      $var = get_magic_quotes_gpc() ? stripslashes($var) : $var;
873
874 19
      $var = mysqli_real_escape_string($this->getLink(), $var);
875
876 19
      return (string)$var;
877
    } else {
878
      return false;
879
    }
880
  }
881
882
  /**
883
   * Get the mysqli-link (link identifier returned by mysqli-connect).
884
   *
885
   * @return \mysqli
886
   */
887 19
  public function getLink()
888
  {
889 19
    return $this->link;
890
  }
891
892
  /**
893
   * Log the current query via "$this->logger".
894
   *
895
   * @param string $sql     sql-query
896
   * @param int    $duration
897
   * @param int    $results result counter
898
   *
899
   * @return bool
900
   */
901 23
  private function _logQuery($sql, $duration, $results)
902
  {
903 23
    $logLevelUse = strtolower($this->logger_level);
904
905
    if (
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
906 23
        $logLevelUse !== 'trace'
907
        &&
908 23
        $logLevelUse !== 'debug'
909
    ) {
910
      //return false;
911
    }
912
913 23
    $infoExtra = mysqli_info($this->link);
914 23
    if ($infoExtra) {
915 7
      $infoExtra = ' | info => ' . $infoExtra;
916
    }
917
918 23
    $info = 'time => ' . round($duration, 5) . ' | results => ' . (int)$results . $infoExtra . ' | SQL => ' . UTF8::htmlentities($sql);
919 23
    $fileInfo = $this->getFileAndLineFromSql();
920
921 23
    $this->logger(
922
        array(
923 23
            'debug',
924 23
            '<strong>' . date('d. m. Y G:i:s') . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . '):</strong> ' . $info . '<br>',
925 23
            'sql',
926
        )
927
    );
928
929 23
    return true;
930
  }
931
932
  /**
933
   * Returns the auto generated id used in the last query.
934
   *
935
   * @return int|string
936
   */
937 16
  public function insert_id()
938
  {
939 16
    return mysqli_insert_id($this->link);
940
  }
941
942
  /**
943
   * Gets the number of affected rows in a previous MySQL operation.
944
   *
945
   * @return int
946
   */
947 7
  public function affected_rows()
948
  {
949 7
    return mysqli_affected_rows($this->link);
950
  }
951
952
  /**
953
   * Error-handling for the sql-query.
954
   *
955
   * @param string     $errorMsg
956
   * @param string     $sql
957
   * @param array|bool $sqlParams false if there wasn't any parameter
958
   *
959
   * @throws \Exception
960
   */
961 9
  protected function queryErrorHandling($errorMsg, $sql, $sqlParams = false)
962
  {
963 9
    if ($errorMsg === 'DB server has gone away' || $errorMsg === 'MySQL server has gone away') {
964 1
      static $reconnectCounter;
965
966
      // exit if we have more then 3 "DB server has gone away"-errors
967 1
      if ($reconnectCounter > 3) {
968
        $this->mailToAdmin('SQL-Fatal-Error', $errorMsg . ":\n<br />" . $sql, 5);
969
        throw new \Exception($errorMsg);
970
      } else {
971 1
        $this->mailToAdmin('SQL-Error', $errorMsg . ":\n<br />" . $sql);
972
973
        // reconnect
974 1
        $reconnectCounter++;
975 1
        $this->reconnect(true);
976
977
        // re-run the current query
978 1
        $this->query($sql, $sqlParams);
979
      }
980
    } else {
981 8
      $this->mailToAdmin('SQL-Warning', $errorMsg . ":\n<br />" . $sql);
982
983
      // this query returned an error, we must display it (only for dev) !!!
984 8
      $this->_displayError($errorMsg . ' | ' . $sql);
985
    }
986 9
  }
987
988
  /**
989
   * send a error mail to the admin / dev
990
   *
991
   * @param string $subject
992
   * @param string $htmlBody
993
   * @param int    $priority
994
   */
995 9
  private function mailToAdmin($subject, $htmlBody, $priority = 3)
996
  {
997 9
    if (function_exists('mailToAdmin')) {
998
      mailToAdmin($subject, $htmlBody, $priority);
999
    } else {
1000
1001 9
      if ($priority == 3) {
1002 9
        $this->logger(
1003
            array(
1004 9
                'debug',
1005 9
                $subject . ' | ' . $htmlBody,
1006
            )
1007
        );
1008
      } elseif ($priority > 3) {
1009
        $this->logger(
1010
            array(
1011
                'error',
1012
                $subject . ' | ' . $htmlBody,
1013
            )
1014
        );
1015
      } elseif ($priority < 3) {
1016
        $this->logger(
1017
            array(
1018
                'info',
1019
                $subject . ' | ' . $htmlBody,
1020
            )
1021
        );
1022
      }
1023
1024
    }
1025 9
  }
1026
1027
  /**
1028
   * Reconnect to the MySQL-Server.
1029
   *
1030
   * @param bool $checkViaPing
1031
   *
1032
   * @return bool
1033
   */
1034 2
  public function reconnect($checkViaPing = false)
1035
  {
1036 2
    $ping = false;
1037
1038 2
    if ($checkViaPing === true) {
1039 2
      $ping = $this->ping();
1040
    }
1041
1042 2
    if ($ping !== true) {
1043 2
      $this->connected = false;
1044 2
      $this->connect();
1045
    }
1046
1047 2
    return $this->isReady();
1048
  }
1049
1050
  /**
1051
   * Pings a server connection, or tries to reconnect
1052
   * if the connection has gone down.
1053
   *
1054
   * @return boolean
1055
   */
1056 3
  public function ping()
1057
  {
1058
    if (
1059 3
        $this->link
1060
        &&
1061 3
        $this->link instanceof \mysqli
1062
    ) {
1063
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
1064 3
      return @mysqli_ping($this->link);
1065
    } else {
1066
      return false;
1067
    }
1068
  }
1069
1070
  /**
1071
   * Execute select/insert/update/delete sql-queries.
1072
   *
1073
   * @param string $query    sql-query
1074
   * @param bool   $useCache use cache?
1075
   * @param int    $cacheTTL cache-ttl in seconds
1076
   *
1077
   * @return mixed "array" by "<b>SELECT</b>"-queries<br />
1078
   *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
1079
   *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1080
   *               "true" by e.g. "DROP"-queries<br />
1081
   *               "false" on error
1082
   *
1083
   */
1084 3
  public static function execSQL($query, $useCache = false, $cacheTTL = 3600)
1085
  {
1086 3
    $db = self::getInstance();
1087
1088 3
    if ($useCache === true) {
1089 1
      $cache = new Cache(null, null, false, $useCache);
1090 1
      $cacheKey = 'sql-' . md5($query);
1091
1092
      if (
1093 1
          $cache->getCacheIsReady() === true
1094
          &&
1095 1
          $cache->existsItem($cacheKey)
1096
      ) {
1097 1
        return $cache->getItem($cacheKey);
1098
      }
1099
1100
    } else {
1101 3
      $cache = false;
1102
    }
1103
1104 3
    $result = $db->query($query);
1105
1106 3
    if ($result instanceof Result) {
1107
1108 1
      $return = $result->fetchAllArray();
1109
1110
      if (
1111 1
          isset($cacheKey)
1112
          &&
1113 1
          $useCache === true
1114
          &&
1115 1
          $cache instanceof Cache
1116
          &&
1117 1
          $cache->getCacheIsReady() === true
1118
      ) {
1119 1
        $cache->setItem($cacheKey, $return, $cacheTTL);
1120
      }
1121
1122
    } else {
1123 2
      $return = $result;
1124
    }
1125
1126 3
    return $return;
1127
  }
1128
1129
  /**
1130
   * Get the current charset.
1131
   *
1132
   * @return string
1133
   */
1134 1
  public function get_charset()
1135
  {
1136 1
    return $this->charset;
1137
  }
1138
1139
  /**
1140
   * Set the current charset.
1141
   *
1142
   * @param string $charset
1143
   *
1144
   * @return bool
1145
   */
1146 6
  public function set_charset($charset)
1147
  {
1148 6
    $this->charset = (string)$charset;
1149
1150 6
    $return = mysqli_set_charset($this->link, $charset);
1151
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
1152 6
    @mysqli_query($this->link, 'SET CHARACTER SET ' . $charset);
1153
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
1154 6
    @mysqli_query($this->link, "SET NAMES '" . ($charset === 'utf8' ? 'utf8mb4' : $charset) . "'");
1155
1156 6
    return $return;
1157
  }
1158
1159
  /**
1160
   * __wakeup
1161
   *
1162
   * @return void
1163
   */
1164 1
  public function __wakeup()
1165
  {
1166 1
    $this->reconnect();
1167 1
  }
1168
1169
  /**
1170
   * Get all table-names via "SHOW TABLES".
1171
   *
1172
   * @return array
1173
   */
1174 1
  public function getAllTables()
1175
  {
1176 1
    $query = 'SHOW TABLES';
1177 1
    $result = $this->query($query);
1178
1179 1
    return $result->fetchAllArray();
1180
  }
1181
1182
  /**
1183
   * Execute a sql-multi-query.
1184
   *
1185
   * @param string $sql
1186
   *
1187
   * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
1188
   *                        "boolean" by only "<b>INSERT</b>"-queries<br />
1189
   *                        "boolean" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1190
   *                        "boolean" by only by e.g. "DROP"-queries<br />
1191
   *
1192
   * @throws \Exception
1193
   */
1194 1
  public function multi_query($sql)
1195
  {
1196 1
    if (!$this->isReady()) {
1197
      return false;
1198
    }
1199
1200 1
    if (!$sql || $sql === '') {
1201 1
      $this->_displayError('Can\'t execute an empty Query', false);
1202
1203 1
      return false;
1204
    }
1205
1206 1
    $query_start_time = microtime(true);
1207 1
    $resultTmp = mysqli_multi_query($this->link, $sql);
1208 1
    $query_duration = microtime(true) - $query_start_time;
1209
1210 1
    $this->_logQuery($sql, $query_duration, 0);
1211
1212 1
    $returnTheResult = false;
1213 1
    $result = array();
1214 1
    if ($resultTmp) {
1215
      do {
1216 1
        $resultTmpInner = mysqli_store_result($this->link);
1217
1218 1
        if ($resultTmpInner instanceof \mysqli_result) {
1219 1
          $returnTheResult = true;
1220 1
          $result[] = new Result($sql, $resultTmpInner);
1221
        } else {
1222 1
          $errorMsg = mysqli_error($this->link);
1223
1224
          // is the query successful
1225 1
          if ($resultTmpInner === true || !$errorMsg) {
1226 1
            $result[] = true;
1227
          } else {
1228
            $result[] = false;
1229
1230
            $this->queryErrorHandling($errorMsg, $sql);
1231
          }
1232
        }
1233 1
      } while (mysqli_more_results($this->link) === true ? mysqli_next_result($this->link) : false);
1234
1235
    } else {
1236
1237
      $errorMsg = mysqli_error($this->link);
1238
1239
      if ($this->checkForDev() === true) {
1240
        echo "Info: maybe you have to increase your 'max_allowed_packet = 30M' in the config: 'my.conf' \n<br />";
1241
        echo 'Error:' . $errorMsg;
1242
      }
1243
1244
      $this->mailToAdmin('SQL-Error in mysqli_multi_query', $errorMsg . ":\n<br />" . $sql);
1245
    }
1246
1247
    // return the result only if there was a "SELECT"-query
1248 1
    if ($returnTheResult === true) {
1249 1
      return $result;
1250
    }
1251
1252 1
    if (!in_array(false, $result, true)) {
1253 1
      return true;
1254
    } else {
1255
      return false;
1256
    }
1257
  }
1258
1259
  /**
1260
   * alias: "beginTransaction()"
1261
   */
1262 1
  public function startTransaction()
1263
  {
1264 1
    $this->beginTransaction();
1265 1
  }
1266
1267
  /**
1268
   * Begins a transaction, by turning off auto commit.
1269
   *
1270
   * @return boolean this will return true or false indicating success of transaction
1271
   */
1272 4
  public function beginTransaction()
1273
  {
1274 4
    $this->clearErrors();
1275
1276 4
    if ($this->inTransaction() === true) {
1277 1
      $this->_displayError('Error mysql server already in transaction!', true);
1278
1279
      return false;
1280 4
    } elseif (mysqli_connect_errno()) {
1281
      $this->_displayError('Error connecting to mysql server: ' . mysqli_connect_error(), true);
1282
1283
      return false;
1284
    } else {
1285 4
      $this->_in_transaction = true;
1286 4
      mysqli_autocommit($this->link, false);
1287
1288 4
      return true;
1289
1290
    }
1291
  }
1292
1293
  /**
1294
   * Clear the errors in "$this->_errors".
1295
   *
1296
   * @return bool
1297
   */
1298 4
  public function clearErrors()
1299
  {
1300 4
    $this->_errors = array();
1301
1302 4
    return true;
1303
  }
1304
1305
  /**
1306
   * Check if we are in a transaction.
1307
   *
1308
   * @return boolean
1309
   */
1310 4
  public function inTransaction()
1311
  {
1312 4
    return $this->_in_transaction;
1313
  }
1314
1315
  /**
1316
   * Ends a transaction and commits if no errors, then ends autocommit.
1317
   *
1318
   * @return boolean this will return true or false indicating success of transactions
1319
   */
1320 2
  public function endTransaction()
1321
  {
1322
1323 2
    if (!$this->errors()) {
1324 1
      mysqli_commit($this->link);
1325 1
      $return = true;
1326
    } else {
1327 1
      $this->rollback();
1328 1
      $return = false;
1329
    }
1330
1331 2
    mysqli_autocommit($this->link, true);
1332 2
    $this->_in_transaction = false;
1333
1334 2
    return $return;
1335
  }
1336
1337
  /**
1338
   * Get all errors from "$this->_errors".
1339
   *
1340
   * @return array|false false === on errors
1341
   */
1342 2
  public function errors()
1343
  {
1344 2
    return count($this->_errors) > 0 ? $this->_errors : false;
1345
  }
1346
1347
  /**
1348
   * Rollback in a transaction.
1349
   */
1350 2
  public function rollback()
1351
  {
1352
    // init
1353 2
    $return = false;
1354
1355 2
    if ($this->_in_transaction === true) {
1356 2
      $return = mysqli_rollback($this->link);
1357 2
      mysqli_autocommit($this->link, true);
1358 2
      $this->_in_transaction = false;
1359
    }
1360
1361 2
    return $return;
1362
  }
1363
1364
  /**
1365
   * Execute a "insert"-query.
1366
   *
1367
   * @param string $table
1368
   * @param array  $data
1369
   *
1370
   * @return false|int false on error
1371
   */
1372 15
  public function insert($table, $data = array())
1373
  {
1374 15
    $table = trim($table);
1375
1376 15
    if ($table === '') {
1377 2
      $this->_displayError('invalid-table-name');
1378
1379 1
      return false;
1380
    }
1381
1382 14
    if (count($data) == 0) {
1383 3
      $this->_displayError('empty-data-for-INSERT');
1384
1385 2
      return false;
1386
    }
1387
1388 12
    $SET = $this->_parseArrayPair($data);
1389
1390 12
    $sql = 'INSERT INTO ' . $this->quote_string($table) . " SET $SET;";
1391
1392 12
    return $this->query($sql);
1393
  }
1394
1395
  /**
1396
   * Parses arrays with value pairs and generates SQL to use in queries.
1397
   *
1398
   * @param array  $arrayPair
1399
   * @param string $glue this is the separator
1400
   *
1401
   * @return string
1402
   */
1403 13
  private function _parseArrayPair($arrayPair, $glue = ',')
1404
  {
1405
    // init
1406 13
    $sql = '';
1407 13
    $pairs = array();
1408
1409 13
    if (!empty($arrayPair)) {
1410
1411 13
      foreach ($arrayPair as $_key => $_value) {
1412 13
        $_connector = '=';
1413 13
        $_key_upper = strtoupper($_key);
1414
1415 13
        if (strpos($_key_upper, ' NOT') !== false) {
1416 2
          $_connector = 'NOT';
1417
        }
1418
1419 13
        if (strpos($_key_upper, ' IS') !== false) {
1420 1
          $_connector = 'IS';
1421
        }
1422
1423 13
        if (strpos($_key_upper, ' IS NOT') !== false) {
1424 1
          $_connector = 'IS NOT';
1425
        }
1426
1427 13
        if (strpos($_key_upper, ' IN') !== false) {
1428 1
          $_connector = 'IN';
1429
        }
1430
1431 13
        if (strpos($_key_upper, ' NOT IN') !== false) {
1432 1
          $_connector = 'NOT IN';
1433
        }
1434
1435 13
        if (strpos($_key_upper, ' BETWEEN') !== false) {
1436 1
          $_connector = 'BETWEEN';
1437
        }
1438
1439 13
        if (strpos($_key_upper, ' NOT BETWEEN') !== false) {
1440 1
          $_connector = 'NOT BETWEEN';
1441
        }
1442
1443 13
        if (strpos($_key_upper, ' LIKE') !== false) {
1444 2
          $_connector = 'LIKE';
1445
        }
1446
1447 13
        if (strpos($_key_upper, ' NOT LIKE') !== false) {
1448 2
          $_connector = 'NOT LIKE';
1449
        }
1450
1451 13 View Code Duplication
        if (strpos($_key_upper, ' >') !== false && strpos($_key_upper, ' =') === false) {
1452 2
          $_connector = '>';
1453
        }
1454
1455 13 View Code Duplication
        if (strpos($_key_upper, ' <') !== false && strpos($_key_upper, ' =') === false) {
1456 1
          $_connector = '<';
1457
        }
1458
1459 13
        if (strpos($_key_upper, ' >=') !== false) {
1460 2
          $_connector = '>=';
1461
        }
1462
1463 13
        if (strpos($_key_upper, ' <=') !== false) {
1464 1
          $_connector = '<=';
1465
        }
1466
1467 13
        if (strpos($_key_upper, ' <>') !== false) {
1468 1
          $_connector = '<>';
1469
        }
1470
1471 13
        if (is_array($_value) === true) {
1472 1
          foreach ($_value as $oldKey => $oldValue) {
1473 1
            $_value[$oldKey] = $this->secure($oldValue);
1474
          }
1475
1476 1
          if ($_connector === 'NOT IN' || $_connector === 'IN') {
1477 1
            $_value = '(' . implode(',', $_value) . ')';
1478 1
          } elseif ($_connector === 'NOT BETWEEN' || $_connector === 'BETWEEN') {
1479 1
            $_value = '(' . implode(' AND ', $_value) . ')';
1480
          }
1481
1482
        } else {
1483 13
          $_value = $this->secure($_value);
1484
        }
1485
1486 13
        $quoteString = $this->quote_string(trim(str_ireplace($_connector, '', $_key)));
1487 13
        $pairs[] = ' ' . $quoteString . ' ' . $_connector . ' ' . $_value . " \n";
1488
      }
1489
1490 13
      $sql = implode($glue, $pairs);
1491
    }
1492
1493 13
    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 15
  public function quote_string($str)
1504
  {
1505 15
    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
    }
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
    }
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 6
  public function update($table, $data = array(), $where = '1=1')
1574
  {
1575 6
    $table = trim($table);
1576
1577 6
    if ($table === '') {
1578 1
      $this->_displayError('invalid table name');
1579
1580 1
      return false;
1581
    }
1582
1583 6
    if (count($data) == 0) {
1584 2
      $this->_displayError('empty data for UPDATE');
1585
1586 2
      return false;
1587
    }
1588
1589 6
    $SET = $this->_parseArrayPair($data);
1590
1591 6
    if (is_string($where)) {
1592 2
      $WHERE = $this->escape($where, false, false);
1593 5
    } elseif (is_array($where)) {
1594 4
      $WHERE = $this->_parseArrayPair($where, 'AND');
1595
    } else {
1596 1
      $WHERE = '';
1597
    }
1598
1599 6
    $sql = 'UPDATE ' . $this->quote_string($table) . " SET $SET WHERE ($WHERE);";
1600
1601 6
    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
    } 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 14 View Code Duplication
  public function select($table, $where = '1=1')
1645
  {
1646
1647 14
    if ($table === '') {
1648 1
      $this->_displayError('invalid table name');
1649
1650 1
      return false;
1651
    }
1652
1653 14
    if (is_string($where)) {
1654 3
      $WHERE = $this->escape($where, false, false);
1655 12
    } elseif (is_array($where)) {
1656 12
      $WHERE = $this->_parseArrayPair($where, 'AND');
1657
    } else {
1658 1
      $WHERE = '';
1659
    }
1660
1661 14
    $sql = 'SELECT * FROM ' . $this->quote_string($table) . " WHERE ($WHERE);";
1662
1663 14
    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
    }
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
    }
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