Completed
Push — master ( 3633b6...8becb2 )
by Lars
10:19 queued 01:27
created

DB::getLink()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
namespace voku\db;
4
5
use voku\cache\Cache;
6
use voku\helper\UTF8;
7
8
/**
9
 * DB: this handles DB queries via MySQLi
10
 *
11
 * @package   voku\db
12
 */
13
final class DB
14
{
15
16
  /**
17
   * @var int
18
   */
19
  public $query_count = 0;
20
21
  /**
22
   * @var \mysqli
23
   */
24
  private $link = false;
25
26
  /**
27
   * @var bool
28
   */
29
  private $connected = false;
30
31
  /**
32
   * @var array
33
   */
34
  private $mysqlDefaultTimeFunctions;
35
36
  /**
37
   * @var string
38
   */
39
  private $hostname = '';
40
41
  /**
42
   * @var string
43
   */
44
  private $username = '';
45
46
  /**
47
   * @var string
48
   */
49
  private $password = '';
50
51
  /**
52
   * @var string
53
   */
54
  private $database = '';
55
56
  /**
57
   * @var int
58
   */
59
  private $port = 3306;
60
61
  /**
62
   * @var string
63
   */
64
  private $charset = 'utf8';
65
66
  /**
67
   * @var string
68
   */
69
  private $socket = '';
70
71
  /**
72
   * @var bool
73
   */
74
  private $session_to_db = false;
75
76
  /**
77
   * @var bool
78
   */
79
  private $_in_transaction = false;
80
81
  /**
82
   * @var bool
83
   */
84
  private $_convert_null_to_empty_string = false;
85
86
  /**
87
   * @var Debug
88
   */
89
  private $_debug;
90
91
  /**
92
   * __construct()
93
   *
94
   * @param string         $hostname
95
   * @param string         $username
96
   * @param string         $password
97
   * @param string         $database
98
   * @param int            $port
99
   * @param string         $charset
100
   * @param boolean|string $exit_on_error use a empty string "" or false to disable it
101
   * @param boolean|string $echo_on_error use a empty string "" or false to disable it
102
   * @param string         $logger_class_name
103
   * @param string         $logger_level  'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'
104
   * @param boolean|string $session_to_db use a empty string "" or false to disable it
105
   */
106 10
  protected function __construct($hostname, $username, $password, $database, $port, $charset, $exit_on_error, $echo_on_error, $logger_class_name, $logger_level, $session_to_db)
107
  {
108 10
    $this->connected = false;
109
110 10
    $this->_debug = new Debug($this);
111
112 10
    $this->_loadConfig(
113 10
        $hostname,
114 10
        $username,
115 10
        $password,
116 10
        $database,
117 10
        $port,
118 10
        $charset,
119 10
        $exit_on_error,
120 10
        $echo_on_error,
121 10
        $logger_class_name,
122 10
        $logger_level,
123
        $session_to_db
124 10
    );
125
126 7
    $this->connect();
127
128 4
    $this->mysqlDefaultTimeFunctions = array(
129
      // Returns the current date.
130 4
      'CURDATE()',
131
      // CURRENT_DATE	| Synonyms for CURDATE()
132 4
      'CURRENT_DATE()',
133
      // CURRENT_TIME	| Synonyms for CURTIME()
134 4
      'CURRENT_TIME()',
135
      // CURRENT_TIMESTAMP | Synonyms for NOW()
136 4
      'CURRENT_TIMESTAMP()',
137
      // Returns the current time.
138 4
      'CURTIME()',
139
      // 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...
140 4
      'LOCALTIME()',
141
      // 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...
142 4
      'LOCALTIMESTAMP()',
143
      // Returns the current date and time.
144 4
      'NOW()',
145
      // Returns the time at which the function executes.
146 4
      'SYSDATE()',
147
      // Returns a UNIX timestamp.
148 4
      'UNIX_TIMESTAMP()',
149
      // Returns the current UTC date.
150 4
      'UTC_DATE()',
151
      // Returns the current UTC time.
152 4
      'UTC_TIME()',
153
      // Returns the current UTC date and time.
154 4
      'UTC_TIMESTAMP()',
155
    );
156 4
  }
157
158
  /**
159
   * Load the config from the constructor.
160
   *
161
   * @param string         $hostname
162
   * @param string         $username
163
   * @param string         $password
164
   * @param string         $database
165
   * @param int            $port
166
   * @param string         $charset
167
   * @param boolean|string $exit_on_error use a empty string "" or false to disable it
168
   * @param boolean|string $echo_on_error use a empty string "" or false to disable it
169
   * @param string         $logger_class_name
170
   * @param string         $logger_level
171
   * @param boolean|string $session_to_db use a empty string "" or false to disable it
172
   *
173
   * @return bool
174
   */
175 10
  private function _loadConfig($hostname, $username, $password, $database, $port, $charset, $exit_on_error, $echo_on_error, $logger_class_name, $logger_level, $session_to_db)
176
  {
177 10
    $this->hostname = (string)$hostname;
178 10
    $this->username = (string)$username;
179 10
    $this->password = (string)$password;
180 10
    $this->database = (string)$database;
181
182 10
    if ($charset) {
183 4
      $this->charset = (string)$charset;
184 4
    }
185
186 10
    if ($port) {
187 4
      $this->port = (int)$port;
188 4
    } else {
189
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
190 7
      $this->port = @ini_get('mysqli.default_port');
191
    }
192
193 10
    if (!$this->socket) {
194
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
195 10
      $this->socket = @ini_get('mysqli.default_socket');
196 10
    }
197
198 10
    if ($exit_on_error === true || $exit_on_error === false) {
199 10
      $this->_debug->setExitOnError($exit_on_error);
200 10
    }
201
202 10
    if ($echo_on_error === true || $echo_on_error === false) {
203 10
      $this->_debug->setEchoOnError($echo_on_error);
204 10
    }
205
206 10
    $this->_debug->setLoggerClassName($logger_class_name);
207 10
    $this->_debug->setLoggerLevel($logger_level);
208
209 10
    $this->session_to_db = (boolean)$session_to_db;
210
211 10
    return $this->showConfigError();
212
  }
213
214
  /**
215
   * Show config errors by throw exceptions.
216
   *
217
   * @return bool
218
   *
219
   * @throws \Exception
220
   */
221 10
  public function showConfigError()
222
  {
223
224
    if (
225 10
        !$this->hostname
226 10
        ||
227 9
        !$this->username
228 9
        ||
229 8
        !$this->database
230 10
    ) {
231
232 3
      if (!$this->hostname) {
233 1
        throw new \Exception('no-sql-hostname');
234
      }
235
236 2
      if (!$this->username) {
237 1
        throw new \Exception('no-sql-username');
238
      }
239
240 1
      if (!$this->database) {
241 1
        throw new \Exception('no-sql-database');
242
      }
243
244
      return false;
245
    }
246
247 7
    return true;
248
  }
249
250
  /**
251
   * Open a new connection to the MySQL server.
252
   *
253
   * @return boolean
254
   */
255 9
  public function connect()
256
  {
257 9
    if ($this->isReady()) {
258 1
      return true;
259
    }
260
261 9
    \mysqli_report(MYSQLI_REPORT_STRICT);
262
    try {
263 9
      $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...
264
265 9
      if (Helper::isMysqlndIsUsed() === true) {
266 9
        \mysqli_options($this->link, MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
267 9
      }
268
269
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
270 9
      $this->connected = @\mysqli_real_connect(
271 9
          $this->link,
272 9
          $this->hostname,
273 9
          $this->username,
274 9
          $this->password,
275 9
          $this->database,
276 9
          $this->port,
277 9
          $this->socket
278 9
      );
279 9
    } catch (\Exception $e) {
280 3
      $this->_debug->displayError('Error connecting to mysql server: ' . $e->getMessage(), true);
281
    }
282 6
    \mysqli_report(MYSQLI_REPORT_OFF);
283
284 6
    if (!$this->connected) {
285
      $this->_debug->displayError('Error connecting to mysql server: ' . \mysqli_connect_error(), true);
286
    } else {
287 6
      $this->set_charset($this->charset);
288
    }
289
290 6
    return $this->isReady();
291
  }
292
293
  /**
294
   * Check if db-connection is ready.
295
   *
296
   * @return boolean
297
   */
298 43
  public function isReady()
299
  {
300 43
    return $this->connected ? true : false;
301
  }
302
303
  /**
304
   * Get a new "Prepare"-Object for your sql-query.
305
   *
306
   * @param string $query
307
   *
308
   * @return Prepare
309
   */
310 2
  public function prepare($query)
311
  {
312 2
    return new Prepare($this, $query);
313
  }
314
315
  /**
316
   * Execute a sql-query and return the result-array for select-statements.
317
   *
318
   * @param $query
319
   *
320
   * @return mixed
321
   * @deprecated
322
   * @throws \Exception
323
   */
324
  public static function qry($query)
325
  {
326
    $db = self::getInstance();
327
328
    $args = func_get_args();
329
    $query = array_shift($args);
330
    $query = str_replace('?', '%s', $query);
331
    $args = array_map(
332
        array(
333
            $db,
334
            'escape',
335
        ),
336
        $args
337
    );
338
    array_unshift($args, $query);
339
    $query = call_user_func_array('sprintf', $args);
340
    $result = $db->query($query);
341
342
    if ($result instanceof Result) {
343
      return $result->fetchAllArray();
344
    } else {
345
      return $result;
346
    }
347
  }
348
349
  /**
350
   * getInstance()
351
   *
352
   * @param string      $hostname
353
   * @param string      $username
354
   * @param string      $password
355
   * @param string      $database
356
   * @param string      $port          default is (int)3306
357
   * @param string      $charset       default is 'utf8' or 'utf8mb4' (if supported)
358
   * @param bool|string $exit_on_error use a empty string "" or false to disable it
359
   * @param bool|string $echo_on_error use a empty string "" or false to disable it
360
   * @param string      $logger_class_name
361
   * @param string      $logger_level
362
   * @param bool|string $session_to_db use a empty string "" or false to disable it
363
   *
364
   * @return \voku\db\DB
365
   */
366 54
  public static function getInstance($hostname = '', $username = '', $password = '', $database = '', $port = '', $charset = '', $exit_on_error = '', $echo_on_error = '', $logger_class_name = '', $logger_level = '', $session_to_db = '')
367
  {
368
    /**
369
     * @var $instance DB[]
370
     */
371 54
    static $instance = array();
372
373
    /**
374
     * @var $firstInstance DB
375
     */
376 54
    static $firstInstance = null;
377
378
    if (
379 54
        $hostname . $username . $password . $database . $port . $charset == ''
380 54
        &&
381 9
        null !== $firstInstance
382 54
    ) {
383 9
      return $firstInstance;
384
    }
385
386 54
    $connection = md5(
387 54
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . (int)$session_to_db
388 54
    );
389
390 54
    if (!isset($instance[$connection])) {
391 10
      $instance[$connection] = new self(
392 10
          $hostname,
393 10
          $username,
394 10
          $password,
395 10
          $database,
396 10
          $port,
397 10
          $charset,
398 10
          $exit_on_error,
399 10
          $echo_on_error,
400 10
          $logger_class_name,
401 10
          $logger_level,
402
          $session_to_db
403 10
      );
404
405 4
      if (null === $firstInstance) {
406 1
        $firstInstance = $instance[$connection];
407 1
      }
408 4
    }
409
410 54
    return $instance[$connection];
411
  }
412
413
  /**
414
   * Execute a sql-query.
415
   *
416
   * @param string        $sql            sql-query
417
   *
418
   * @param array|boolean $params         "array" of sql-query-parameters
419
   *                                      "false" if you don't need any parameter (default)
420
   *
421
   * @return bool|int|Result              "Result" by "<b>SELECT</b>"-queries<br />
422
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
423
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
424
   *                                      "true" by e.g. "DROP"-queries<br />
425
   *                                      "false" on error
426
   *
427
   * @throws \Exception
428
   */
429 33
  public function query($sql = '', $params = false)
430
  {
431 33
    if (!$this->isReady()) {
432
      return false;
433
    }
434
435 33 View Code Duplication
    if (!$sql || $sql === '') {
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...
436 4
      $this->_debug->displayError('Can\'t execute an empty Query', false);
437
438 4
      return false;
439
    }
440
441
    if (
442
        $params !== false
443 31
        &&
444 3
        is_array($params)
445 31
        &&
446 3
        count($params) > 0
447 31
    ) {
448 3
      $sql = $this->_parseQueryParams($sql, $params);
449 3
    }
450
451 31
    $query_start_time = microtime(true);
452 31
    $query_result = \mysqli_real_query($this->link, $sql);
453 31
    $query_duration = microtime(true) - $query_start_time;
454
455 31
    $this->query_count++;
456
457 31
    $mysqli_field_count = \mysqli_field_count($this->link);
458 31
    if ($mysqli_field_count) {
459 26
      $result = \mysqli_store_result($this->link);
460 26
    } else {
461 22
      $result = $query_result;
462
    }
463
464 31
    if ($result instanceof \mysqli_result) {
465
466
      // log the select query
467 25
      $this->_debug->logQuery($sql, $query_duration, $mysqli_field_count);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 448 can also be of type array; however, voku\db\Debug::logQuery() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
468
469
      // return query result object
470 25
      return new Result($sql, $result);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 448 can also be of type array; however, voku\db\Result::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
471
472 23
    } elseif ($query_result === true) {
473
474
      // "INSERT" || "REPLACE"
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
475 21 View Code Duplication
      if (preg_match('/^\s*"?(INSERT|REPLACE)\s+/i', $sql)) {
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...
476 20
        $insert_id = (int)$this->insert_id();
477 20
        $this->_debug->logQuery($sql, $query_duration, $insert_id);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 448 can also be of type array; however, voku\db\Debug::logQuery() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
478
479 20
        return $insert_id;
480
      }
481
482
      // "UPDATE" || "DELETE"
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
483 7 View Code Duplication
      if (preg_match('/^\s*"?(UPDATE|DELETE)\s+/i', $sql)) {
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...
484 7
        $affected_rows = (int)$this->affected_rows();
485 7
        $this->_debug->logQuery($sql, $query_duration, $affected_rows);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 448 can also be of type array; however, voku\db\Debug::logQuery() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
486
487 7
        return $affected_rows;
488
      }
489
490
      // log the ? query
491
      $this->_debug->logQuery($sql, $query_duration, 0);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 448 can also be of type array; however, voku\db\Debug::logQuery() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
492
493
      return true;
494
    }
495
496
    // log the error query
497 8
    $this->_debug->logQuery($sql, $query_duration, 0, true);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 448 can also be of type array; however, voku\db\Debug::logQuery() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
498
499 8
    return $this->queryErrorHandling(\mysqli_error($this->link), $sql, $params);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->_parseQueryParams($sql, $params) on line 448 can also be of type array; however, voku\db\DB::queryErrorHandling() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
500
  }
501
502
  /**
503
   * _parseQueryParams
504
   *
505
   * @param string $sql
506
   * @param array  $params
507
   *
508
   * @return string
509
   */
510 3
  private function _parseQueryParams($sql, array $params)
511
  {
512
    // is there anything to parse?
513 3
    if (strpos($sql, '?') === false) {
514
      return $sql;
515
    }
516
517 3
    if (count($params) > 0) {
518 3
      $parseKey = md5(uniqid(mt_rand(), true));
519 3
      $sql = str_replace('?', $parseKey, $sql);
520
521 3
      $k = 0;
522 3
      while (strpos($sql, $parseKey) !== false) {
523 3
        $value = $this->secure($params[$k]);
524 3
        $sql = UTF8::str_replace_first($parseKey, $value, $sql);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by \voku\helper\UTF8::str_r...parseKey, $value, $sql) on line 524 can also be of type array; however, voku\helper\UTF8::str_replace_first() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
525 3
        $k++;
526 3
      }
527 3
    }
528
529 3
    return $sql;
530
  }
531
532
  /**
533
   * Try to secure a variable, so can you use it in sql-queries.
534
   *
535
   * <p>
536
   * <strong>int:</strong> (also strings that contains only an int-value)<br />
537
   * 1. parse into "int"
538
   * </p><br />
539
   *
540
   * <p>
541
   * <strong>float:</strong><br />
542
   * 1. return "float"
543
   * </p><br />
544
   *
545
   * <p>
546
   * <strong>string:</strong><br />
547
   * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'<br />
548
   * 2. trim whitespace<br />
549
   * 3. trim '<br />
550
   * 4. escape the string (and remove non utf-8 chars)<br />
551
   * 5. trim ' again (because we maybe removed some chars)<br />
552
   * 6. add ' around the new string<br />
553
   * </p><br />
554
   *
555
   * <p>
556
   * <strong>array:</strong><br />
557
   * 1. return null
558
   * </p><br />
559
   *
560
   * <p>
561
   * <strong>object:</strong><br />
562
   * 1. return false
563
   * </p><br />
564
   *
565
   * <p>
566
   * <strong>null:</strong><br />
567
   * 1. return null
568
   * </p>
569
   *
570
   * @param mixed $var
571
   *
572
   * @return mixed
573
   */
574 24
  public function secure($var)
575
  {
576
    if (
577
        $var === ''
578 24
        ||
579 24
        ($this->_convert_null_to_empty_string === true && $var === null)
580 24
    ) {
581 1
      return "''";
582
    }
583
584 24
    if (in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
585 1
      return $var;
586
    }
587
588 24
    if (is_string($var)) {
589 20
      $var = trim(trim($var), "'");
590 20
    }
591
592 24
    $var = $this->escape($var, false, false, null);
593
594 24
    if (is_string($var)) {
595 20
      $var = "'" . trim($var, "'") . "'";
596 20
    }
597
598 24
    return $var;
599
  }
600
601
  /**
602
   * Escape: Use "mysqli_real_escape_string" and clean non UTF-8 chars + some extra optional stuff.
603
   *
604
   * @param mixed     $var           boolean: convert into "integer"<br />
605
   *                                 int: int (don't change it)<br />
606
   *                                 float: float (don't change it)<br />
607
   *                                 null: null (don't change it)<br />
608
   *                                 array: run escape() for every key => value<br />
609
   *                                 string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
610
   * @param bool      $stripe_non_utf8
611
   * @param bool      $html_entity_decode
612
   * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
613
   *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
614
   *                                 <strong>null</strong> => Convert the array into null, every time.
615
   *
616
   * @return mixed
617
   */
618 30
  public function escape($var = '', $stripe_non_utf8 = true, $html_entity_decode = false, $convert_array = false)
619
  {
620 30
    if ($var === null) {
621 2
      return null;
622
    }
623
624
    // save the current value as int (for later usage)
625 30
    if (!is_object($var)) {
626 30
      $varInt = (int)$var;
627 30
    }
628
629
    /** @noinspection TypeUnsafeComparisonInspection */
630
    if (
631 30
        is_int($var)
632
        ||
633 30
        is_bool($var)
634 30
        ||
635 30
        (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...
636 30
    ) {
637
638
      // "int" || int || bool
639
640 21
      return (int)$var;
641
642 30
    } elseif (is_float($var)) {
643
644
      // float
645
646 5
      return $var;
647
648 30
    } elseif (is_array($var)) {
649
650
      // array
651
652 3
      if ($convert_array === null) {
653 3
        return null;
654
      }
655
656 1
      $varCleaned = array();
657 1
      foreach ((array)$var as $key => $value) {
658
659 1
        $key = $this->escape($key, $stripe_non_utf8, $html_entity_decode);
660 1
        $value = $this->escape($value, $stripe_non_utf8, $html_entity_decode);
661
662
        /** @noinspection OffsetOperationsInspection */
663 1
        $varCleaned[$key] = $value;
664 1
      }
665
666 1
      if ($convert_array === true) {
667 1
        $varCleaned = implode(',', $varCleaned);
668
669 1
        return $varCleaned;
670
      } else {
671 1
        return (array)$varCleaned;
672
      }
673
    }
674
675
    if (
676 30
        is_string($var)
677
        ||
678 3
        (is_object($var) && method_exists($var, '__toString'))
679 30
    ) {
680
681
      // "string"
682
683 30
      $var = (string)$var;
684
685 30
      if ($stripe_non_utf8 === true) {
686 9
        $var = UTF8::cleanup($var);
687 9
      }
688
689 30
      if ($html_entity_decode === true) {
690
        // use no-html-entity for db
691 1
        $var = UTF8::html_entity_decode($var);
692 1
      }
693
694 30
      $var = get_magic_quotes_gpc() ? stripslashes($var) : $var;
695
696 30
      $var = \mysqli_real_escape_string($this->getLink(), $var);
697
698 30
      return (string)$var;
699
700 3
    } elseif ($var instanceof \DateTime) {
701
702
      // "DateTime"-object
703
704
      try {
705 3
        return $this->escape($var->format('Y-m-d H:i:s'), false);
706
      } catch (\Exception $e) {
707
        return null;
708
      }
709
710
    } else {
711 2
      return false;
712
    }
713
  }
714
715
  /**
716
   * Get the mysqli-link (link identifier returned by mysqli-connect).
717
   *
718
   * @return \mysqli
719
   */
720 32
  public function getLink()
721
  {
722 32
    return $this->link;
723
  }
724
725
  /**
726
   * Returns the auto generated id used in the last query.
727
   *
728
   * @return int|string
729
   */
730 20
  public function insert_id()
731
  {
732 20
    return \mysqli_insert_id($this->link);
733
  }
734
735
  /**
736
   * Gets the number of affected rows in a previous MySQL operation.
737
   *
738
   * @return int
739
   */
740 7
  public function affected_rows()
741
  {
742 7
    return \mysqli_affected_rows($this->link);
743
  }
744
745
  /**
746
   * Error-handling for the sql-query.
747
   *
748
   * @param string     $errorMsg
749
   * @param string     $sql
750
   * @param array|bool $sqlParams false if there wasn't any parameter
751
   *
752
   * @throws \Exception
753
   *
754
   * @return bool
755
   */
756 9 View Code Duplication
  protected function queryErrorHandling($errorMsg, $sql, $sqlParams = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
757
  {
758 9
    if ($errorMsg === 'DB server has gone away' || $errorMsg === 'MySQL server has gone away') {
759 1
      static $reconnectCounter;
760
761
      // exit if we have more then 3 "DB server has gone away"-errors
762 1
      if ($reconnectCounter > 3) {
763
        $this->_debug->mailToAdmin('SQL-Fatal-Error', $errorMsg . ":\n<br />" . $sql, 5);
764
        throw new \Exception($errorMsg);
765
      } else {
766 1
        $this->_debug->mailToAdmin('SQL-Error', $errorMsg . ":\n<br />" . $sql);
767
768
        // reconnect
769 1
        $reconnectCounter++;
770 1
        $this->reconnect(true);
771
772
        // re-run the current query
773 1
        return $this->query($sql, $sqlParams);
774
      }
775
    } else {
776 8
      $this->_debug->mailToAdmin('SQL-Warning', $errorMsg . ":\n<br />" . $sql);
777
778
      // this query returned an error, we must display it (only for dev) !!!
779 8
      $this->_debug->displayError($errorMsg . ' | ' . $sql);
780
    }
781
782 8
    return false;
783
  }
784
785
  /**
786
   * Reconnect to the MySQL-Server.
787
   *
788
   * @param bool $checkViaPing
789
   *
790
   * @return bool
791
   */
792 3
  public function reconnect($checkViaPing = false)
793
  {
794 3
    $ping = false;
795
796 3
    if ($checkViaPing === true) {
797 2
      $ping = $this->ping();
798 2
    }
799
800 3
    if ($ping !== true) {
801 3
      $this->connected = false;
802 3
      $this->connect();
803 3
    }
804
805 3
    return $this->isReady();
806
  }
807
808
  /**
809
   * Pings a server connection, or tries to reconnect
810
   * if the connection has gone down.
811
   *
812
   * @return boolean
813
   */
814 3
  public function ping()
815
  {
816
    if (
817 3
        $this->link
818 3
        &&
819 3
        $this->link instanceof \mysqli
820 3
    ) {
821
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
822 3
      return @\mysqli_ping($this->link);
823
    } else {
824
      return false;
825
    }
826
  }
827
828
  /**
829
   * Execute select/insert/update/delete sql-queries.
830
   *
831
   * @param string $query    sql-query
832
   * @param bool   $useCache use cache?
833
   * @param int    $cacheTTL cache-ttl in seconds
834
   *
835
   * @return mixed "array" by "<b>SELECT</b>"-queries<br />
836
   *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
837
   *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
838
   *               "true" by e.g. "DROP"-queries<br />
839
   *               "false" on error
840
   */
841 3
  public static function execSQL($query, $useCache = false, $cacheTTL = 3600)
842
  {
843 3
    $db = self::getInstance();
844
845 3
    if ($useCache === true) {
846 1
      $cache = new Cache(null, null, false, $useCache);
847 1
      $cacheKey = 'sql-' . md5($query);
848
849
      if (
850 1
          $cache->getCacheIsReady() === true
851 1
          &&
852 1
          $cache->existsItem($cacheKey)
853 1
      ) {
854 1
        return $cache->getItem($cacheKey);
855
      }
856
857 1
    } else {
858 3
      $cache = false;
859
    }
860
861 3
    $result = $db->query($query);
862
863 3
    if ($result instanceof Result) {
864
865 1
      $return = $result->fetchAllArray();
866
867
      if (
868 1
          isset($cacheKey)
869 1
          &&
870
          $useCache === true
871 1
          &&
872
          $cache instanceof Cache
873 1
          &&
874 1
          $cache->getCacheIsReady() === true
875 1
      ) {
876 1
        $cache->setItem($cacheKey, $return, $cacheTTL);
877 1
      }
878
879 1
    } else {
880 2
      $return = $result;
881
    }
882
883 3
    return $return;
884
  }
885
886
  /**
887
   * Get the current charset.
888
   *
889
   * @return string
890
   */
891 1
  public function get_charset()
892
  {
893 1
    return $this->charset;
894
  }
895
896
  /**
897
   * Enables or disables internal report functions
898
   *
899
   * @link http://php.net/manual/en/function.mysqli-report.php
900
   *
901
   * @param int $flags <p>
902
   *                   <table>
903
   *                   Supported flags
904
   *                   <tr valign="top">
905
   *                   <td>Name</td>
906
   *                   <td>Description</td>
907
   *                   </tr>
908
   *                   <tr valign="top">
909
   *                   <td><b>MYSQLI_REPORT_OFF</b></td>
910
   *                   <td>Turns reporting off</td>
911
   *                   </tr>
912
   *                   <tr valign="top">
913
   *                   <td><b>MYSQLI_REPORT_ERROR</b></td>
914
   *                   <td>Report errors from mysqli function calls</td>
915
   *                   </tr>
916
   *                   <tr valign="top">
917
   *                   <td><b>MYSQLI_REPORT_STRICT</b></td>
918
   *                   <td>
919
   *                   Throw <b>mysqli_sql_exception</b> for errors
920
   *                   instead of warnings
921
   *                   </td>
922
   *                   </tr>
923
   *                   <tr valign="top">
924
   *                   <td><b>MYSQLI_REPORT_INDEX</b></td>
925
   *                   <td>Report if no index or bad index was used in a query</td>
926
   *                   </tr>
927
   *                   <tr valign="top">
928
   *                   <td><b>MYSQLI_REPORT_ALL</b></td>
929
   *                   <td>Set all options (report all)</td>
930
   *                   </tr>
931
   *                   </table>
932
   *                   </p>
933
   *
934
   * @return bool
935
   */
936
  public function set_mysqli_report($flags)
937
  {
938
    return \mysqli_report($flags);
939
  }
940
941
  /**
942
   * Set the current charset.
943
   *
944
   * @param string $charset
945
   *
946
   * @return bool
947
   */
948 7
  public function set_charset($charset)
949
  {
950 7
    $charsetLower = strtolower($charset);
951 7
    if ($charsetLower === 'utf8' || $charsetLower === 'utf-8') {
952 7
      $charset = 'utf8';
953 7
    }
954 7
    if ($charset === 'utf8' && Helper::isUtf8mb4Supported($this) === true) {
955
      $charset = 'utf8mb4';
956
    }
957
958 7
    $this->charset = (string)$charset;
959
960 7
    $return = mysqli_set_charset($this->link, $charset);
961
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
962 7
    @\mysqli_query($this->link, 'SET CHARACTER SET ' . $charset);
963
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
964 7
    @\mysqli_query($this->link, "SET NAMES '" . $charset . "'");
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
965
966 7
    return $return;
967
  }
968
969
  /**
970
   * Set the option to convert null to "''" (empty string).
971
   *
972
   * Used in secure() => select(), insert(), update(), delete()
973
   *
974
   * @param $bool
975
   */
976 1
  public function set_convert_null_to_empty_string($bool)
977
  {
978 1
    $this->_convert_null_to_empty_string = (bool)$bool;
979 1
  }
980
981
  /**
982
   * Get all table-names via "SHOW TABLES".
983
   *
984
   * @return array
985
   */
986 1
  public function getAllTables()
987
  {
988 1
    $query = 'SHOW TABLES';
989 1
    $result = $this->query($query);
990
991 1
    return $result->fetchAllArray();
992
  }
993
994
  /**
995
   * Execute a sql-multi-query.
996
   *
997
   * @param string $sql
998
   *
999
   * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
1000
   *                        "boolean" by only "<b>INSERT</b>"-queries<br />
1001
   *                        "boolean" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1002
   *                        "boolean" by only by e.g. "DROP"-queries<br />
1003
   *
1004
   * @throws \Exception
1005
   */
1006 1
  public function multi_query($sql)
1007
  {
1008 1
    if (!$this->isReady()) {
1009
      return false;
1010
    }
1011
1012 1 View Code Duplication
    if (!$sql || $sql === '') {
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...
1013 1
      $this->_debug->displayError('Can\'t execute an empty Query', false);
1014
1015 1
      return false;
1016
    }
1017
1018 1
    $query_start_time = microtime(true);
1019 1
    $resultTmp = \mysqli_multi_query($this->link, $sql);
1020 1
    $query_duration = microtime(true) - $query_start_time;
1021
1022 1
    $this->_debug->logQuery($sql, $query_duration, 0);
1023
1024 1
    $returnTheResult = false;
1025 1
    $result = array();
1026 1
    if ($resultTmp) {
1027
      do {
1028 1
        $resultTmpInner = \mysqli_store_result($this->link);
1029
1030 1
        if ($resultTmpInner instanceof \mysqli_result) {
1031 1
          $returnTheResult = true;
1032 1
          $result[] = new Result($sql, $resultTmpInner);
1033 1
        } else {
1034 1
          $errorMsg = \mysqli_error($this->link);
1035
1036
          // is the query successful
1037 1
          if ($resultTmpInner === true || !$errorMsg) {
1038 1
            $result[] = true;
1039 1
          } else {
1040
            $result[] = $this->queryErrorHandling($errorMsg, $sql);
1041
          }
1042
        }
1043 1
      } while (\mysqli_more_results($this->link) === true ? \mysqli_next_result($this->link) : false);
1044
1045 1
    } else {
1046
1047
      $errorMsg = \mysqli_error($this->link);
1048
1049
      if ($this->_debug->checkForDev() === true) {
1050
        echo "Info: maybe you have to increase your 'max_allowed_packet = 30M' in the config: 'my.conf' \n<br />";
1051
        echo 'Error:' . $errorMsg;
1052
      }
1053
1054
      $this->_debug->mailToAdmin('SQL-Error in mysqli_multi_query', $errorMsg . ":\n<br />" . $sql);
1055
    }
1056
1057
    // return the result only if there was a "SELECT"-query
1058 1
    if ($returnTheResult === true) {
1059 1
      return $result;
1060
    }
1061
1062 1
    if (!in_array(false, $result, true)) {
1063 1
      return true;
1064
    } else {
1065
      return false;
1066
    }
1067
  }
1068
1069
  /**
1070
   * alias: "beginTransaction()"
1071
   */
1072 1
  public function startTransaction()
1073
  {
1074 1
    $this->beginTransaction();
1075 1
  }
1076
1077
  /**
1078
   * Begins a transaction, by turning off auto commit.
1079
   *
1080
   * @return boolean this will return true or false indicating success of transaction
1081
   */
1082 4
  public function beginTransaction()
1083
  {
1084 4
    $this->clearErrors();
1085
1086 4
    if ($this->inTransaction() === true) {
1087 1
      $this->_debug->displayError('Error mysql server already in transaction!', true);
1088
1089
      return false;
1090 4
    } elseif (\mysqli_connect_errno()) {
1091
      $this->_debug->displayError('Error connecting to mysql server: ' . \mysqli_connect_error(), true);
1092
1093
      return false;
1094
    } else {
1095 4
      $this->_in_transaction = true;
1096 4
      \mysqli_autocommit($this->link, false);
1097
1098 4
      return true;
1099
1100
    }
1101
  }
1102
1103
  /**
1104
   * Clear the errors in "_debug->_errors".
1105
   *
1106
   * @return bool
1107
   */
1108 4
  public function clearErrors()
1109
  {
1110 4
    return $this->_debug->clearErrors();
1111
  }
1112
1113
  /**
1114
   * Check if we are in a transaction.
1115
   *
1116
   * @return boolean
1117
   */
1118 4
  public function inTransaction()
1119
  {
1120 4
    return $this->_in_transaction;
1121
  }
1122
1123
  /**
1124
   * Ends a transaction and commits if no errors, then ends autocommit.
1125
   *
1126
   * @return boolean this will return true or false indicating success of transactions
1127
   */
1128 2
  public function endTransaction()
1129
  {
1130
1131 2
    if (!$this->errors()) {
1132 1
      \mysqli_commit($this->link);
1133 1
      $return = true;
1134 1
    } else {
1135 1
      $this->rollback();
1136 1
      $return = false;
1137
    }
1138
1139 2
    \mysqli_autocommit($this->link, true);
1140 2
    $this->_in_transaction = false;
1141
1142 2
    return $return;
1143
  }
1144
1145
  /**
1146
   * Get all errors from "$this->_errors".
1147
   *
1148
   * @return array|false false === on errors
1149
   */
1150 2
  public function errors()
1151
  {
1152 2
    $errors = $this->_debug->getErrors();
1153
1154 2
    return count($errors) > 0 ? $errors : false;
1155
  }
1156
1157
  /**
1158
   * Rollback in a transaction.
1159
   */
1160 2
  public function rollback()
1161
  {
1162
    // init
1163 2
    $return = false;
1164
1165 2
    if ($this->_in_transaction === true) {
1166 2
      $return = \mysqli_rollback($this->link);
1167 2
      \mysqli_autocommit($this->link, true);
1168 2
      $this->_in_transaction = false;
1169 2
    }
1170
1171 2
    return $return;
1172
  }
1173
1174
  /**
1175
   * Execute a "insert"-query.
1176
   *
1177
   * @param string $table
1178
   * @param array  $data
1179
   *
1180
   * @return false|int false on error
1181
   */
1182 19
  public function insert($table, array $data = array())
1183
  {
1184 19
    $table = trim($table);
1185
1186 19
    if ($table === '') {
1187 2
      $this->_debug->displayError('invalid table name');
1188
1189 1
      return false;
1190
    }
1191
1192 18
    if (count($data) === 0) {
1193 3
      $this->_debug->displayError('empty data for INSERT');
1194
1195 2
      return false;
1196
    }
1197
1198 16
    $SET = $this->_parseArrayPair($data);
1199
1200 16
    $sql = 'INSERT INTO ' . $this->quote_string($table) . " SET $SET;";
1201
1202 16
    return $this->query($sql);
1203
  }
1204
1205
  /**
1206
   * Parses arrays with value pairs and generates SQL to use in queries.
1207
   *
1208
   * @param array  $arrayPair
1209
   * @param string $glue this is the separator
1210
   *
1211
   * @return string
1212
   */
1213 21
  private function _parseArrayPair($arrayPair, $glue = ',')
1214
  {
1215
    // init
1216 21
    $sql = '';
1217 21
    $pairs = array();
1218
1219
    /** @noinspection IsEmptyFunctionUsageInspection */
1220 21
    if (!empty($arrayPair)) {
1221
1222 21
      foreach ($arrayPair as $_key => $_value) {
1223 21
        $_connector = '=';
1224 21
        $_key_upper = strtoupper($_key);
1225
1226 21
        if (strpos($_key_upper, ' NOT') !== false) {
1227 2
          $_connector = 'NOT';
1228 2
        }
1229
1230 21
        if (strpos($_key_upper, ' IS') !== false) {
1231 1
          $_connector = 'IS';
1232 1
        }
1233
1234 21
        if (strpos($_key_upper, ' IS NOT') !== false) {
1235 1
          $_connector = 'IS NOT';
1236 1
        }
1237
1238 21
        if (strpos($_key_upper, ' IN') !== false) {
1239 1
          $_connector = 'IN';
1240 1
        }
1241
1242 21
        if (strpos($_key_upper, ' NOT IN') !== false) {
1243 1
          $_connector = 'NOT IN';
1244 1
        }
1245
1246 21
        if (strpos($_key_upper, ' BETWEEN') !== false) {
1247 1
          $_connector = 'BETWEEN';
1248 1
        }
1249
1250 21
        if (strpos($_key_upper, ' NOT BETWEEN') !== false) {
1251 1
          $_connector = 'NOT BETWEEN';
1252 1
        }
1253
1254 21
        if (strpos($_key_upper, ' LIKE') !== false) {
1255 2
          $_connector = 'LIKE';
1256 2
        }
1257
1258 21
        if (strpos($_key_upper, ' NOT LIKE') !== false) {
1259 2
          $_connector = 'NOT LIKE';
1260 2
        }
1261
1262 21 View Code Duplication
        if (strpos($_key_upper, ' >') !== false && strpos($_key_upper, ' =') === false) {
1263 2
          $_connector = '>';
1264 2
        }
1265
1266 21 View Code Duplication
        if (strpos($_key_upper, ' <') !== false && strpos($_key_upper, ' =') === false) {
1267 1
          $_connector = '<';
1268 1
        }
1269
1270 21
        if (strpos($_key_upper, ' >=') !== false) {
1271 2
          $_connector = '>=';
1272 2
        }
1273
1274 21
        if (strpos($_key_upper, ' <=') !== false) {
1275 1
          $_connector = '<=';
1276 1
        }
1277
1278 21
        if (strpos($_key_upper, ' <>') !== false) {
1279 1
          $_connector = '<>';
1280 1
        }
1281
1282 21
        if (is_array($_value) === true) {
1283 1
          foreach ($_value as $oldKey => $oldValue) {
1284 1
            $_value[$oldKey] = $this->secure($oldValue);
1285 1
          }
1286
1287 1
          if ($_connector === 'NOT IN' || $_connector === 'IN') {
1288 1
            $_value = '(' . implode(',', $_value) . ')';
1289 1
          } elseif ($_connector === 'NOT BETWEEN' || $_connector === 'BETWEEN') {
1290 1
            $_value = '(' . implode(' AND ', $_value) . ')';
1291 1
          }
1292
1293 1
        } else {
1294 21
          $_value = $this->secure($_value);
1295
        }
1296
1297 21
        $quoteString = $this->quote_string(trim(str_ireplace($_connector, '', $_key)));
1298 21
        $pairs[] = ' ' . $quoteString . ' ' . $_connector . ' ' . $_value . " \n";
1299 21
      }
1300
1301 21
      $sql = implode($glue, $pairs);
1302 21
    }
1303
1304 21
    return $sql;
1305
  }
1306
1307
  /**
1308
   * Quote && Escape e.g. a table name string.
1309
   *
1310
   * @param string $str
1311
   *
1312
   * @return string
1313
   */
1314 24
  public function quote_string($str)
1315
  {
1316 24
    return '`' . $this->escape($str, false) . '`';
1317
  }
1318
1319
  /**
1320
   * Get errors from "$this->_errors".
1321
   *
1322
   * @return array
1323
   */
1324 1
  public function getErrors()
1325
  {
1326 1
    return $this->_debug->getErrors();
1327
  }
1328
1329
  /**
1330
   * Execute a "replace"-query.
1331
   *
1332
   * @param string $table
1333
   * @param array  $data
1334
   *
1335
   * @return false|int false on error
1336
   */
1337 1
  public function replace($table, array $data = array())
1338
  {
1339
1340 1
    $table = trim($table);
1341
1342 1
    if ($table === '') {
1343 1
      $this->_debug->displayError('invalid table name');
1344
1345 1
      return false;
1346
    }
1347
1348 1
    if (count($data) === 0) {
1349 1
      $this->_debug->displayError('empty data for REPLACE');
1350
1351 1
      return false;
1352
    }
1353
1354
    // extracting column names
1355 1
    $columns = array_keys($data);
1356 1
    foreach ($columns as $k => $_key) {
1357
      /** @noinspection AlterInForeachInspection */
1358 1
      $columns[$k] = $this->quote_string($_key);
1359 1
    }
1360
1361 1
    $columns = implode(',', $columns);
1362
1363
    // extracting values
1364 1
    foreach ($data as $k => $_value) {
1365
      /** @noinspection AlterInForeachInspection */
1366 1
      $data[$k] = $this->secure($_value);
1367 1
    }
1368 1
    $values = implode(',', $data);
1369
1370 1
    $sql = 'REPLACE INTO ' . $this->quote_string($table) . " ($columns) VALUES ($values);";
1371
1372 1
    return $this->query($sql);
1373
  }
1374
1375
  /**
1376
   * Execute a "update"-query.
1377
   *
1378
   * @param string       $table
1379
   * @param array        $data
1380
   * @param array|string $where
1381
   *
1382
   * @return false|int false on error
1383
   */
1384 6
  public function update($table, array $data = array(), $where = '1=1')
1385
  {
1386 6
    $table = trim($table);
1387
1388 6
    if ($table === '') {
1389 1
      $this->_debug->displayError('invalid table name');
1390
1391 1
      return false;
1392
    }
1393
1394 6
    if (count($data) === 0) {
1395 2
      $this->_debug->displayError('empty data for UPDATE');
1396
1397 2
      return false;
1398
    }
1399
1400 6
    $SET = $this->_parseArrayPair($data);
1401
1402 6
    if (is_string($where)) {
1403 2
      $WHERE = $this->escape($where, false);
1404 6
    } elseif (is_array($where)) {
1405 4
      $WHERE = $this->_parseArrayPair($where, 'AND');
1406 4
    } else {
1407 1
      $WHERE = '';
1408
    }
1409
1410 6
    $sql = 'UPDATE ' . $this->quote_string($table) . " SET $SET WHERE ($WHERE);";
1411
1412 6
    return $this->query($sql);
1413
  }
1414
1415
  /**
1416
   * Execute a "delete"-query.
1417
   *
1418
   * @param string       $table
1419
   * @param string|array $where
1420
   *
1421
   * @return false|int false on error
1422
   */
1423 1 View Code Duplication
  public function delete($table, $where)
1424
  {
1425
1426 1
    $table = trim($table);
1427
1428 1
    if ($table === '') {
1429 1
      $this->_debug->displayError('invalid table name');
1430
1431 1
      return false;
1432
    }
1433
1434 1
    if (is_string($where)) {
1435 1
      $WHERE = $this->escape($where, false);
1436 1
    } elseif (is_array($where)) {
1437 1
      $WHERE = $this->_parseArrayPair($where, 'AND');
1438 1
    } else {
1439 1
      $WHERE = '';
1440
    }
1441
1442 1
    $sql = 'DELETE FROM ' . $this->quote_string($table) . " WHERE ($WHERE);";
1443
1444 1
    return $this->query($sql);
1445
  }
1446
1447
  /**
1448
   * Execute a "select"-query.
1449
   *
1450
   * @param string       $table
1451
   * @param string|array $where
1452
   *
1453
   * @return false|Result false on error
1454
   */
1455 20 View Code Duplication
  public function select($table, $where = '1=1')
1456
  {
1457
1458 20
    if ($table === '') {
1459 1
      $this->_debug->displayError('invalid table name');
1460
1461 1
      return false;
1462
    }
1463
1464 20
    if (is_string($where)) {
1465 5
      $WHERE = $this->escape($where, false);
1466 20
    } elseif (is_array($where)) {
1467 16
      $WHERE = $this->_parseArrayPair($where, 'AND');
1468 16
    } else {
1469 1
      $WHERE = '';
1470
    }
1471
1472 20
    $sql = 'SELECT * FROM ' . $this->quote_string($table) . " WHERE ($WHERE);";
1473
1474 20
    return $this->query($sql);
1475
  }
1476
1477
  /**
1478
   * Get the last sql-error.
1479
   *
1480
   * @return string false on error
1481
   */
1482 1
  public function lastError()
1483
  {
1484 1
    $errors = $this->_debug->getErrors();
1485
1486 1
    return count($errors) > 0 ? end($errors) : false;
1487
  }
1488
1489
  /**
1490
   * @return Debug
1491
   */
1492 9
  public function getDebugger()
1493
  {
1494 9
    return $this->_debug;
1495
  }
1496
1497
  /**
1498
   * __destruct
1499
   *
1500
   */
1501
  public function __destruct()
1502
  {
1503
    // close the connection only if we don't save PHP-SESSION's in DB
1504
    if ($this->session_to_db === false) {
1505
      $this->close();
1506
    }
1507
  }
1508
1509
  /**
1510
   * Closes a previously opened database connection.
1511
   */
1512 2
  public function close()
1513
  {
1514 2
    $this->connected = false;
1515
1516 2
    if ($this->link) {
1517 2
      \mysqli_close($this->link);
1518 2
    }
1519 2
  }
1520
1521
  /**
1522
   * Prevent the instance from being cloned.
1523
   *
1524
   * @return void
1525
   */
1526
  private function __clone()
1527
  {
1528
  }
1529
1530
  /**
1531
   * __wakeup
1532
   *
1533
   * @return void
1534
   */
1535 2
  public function __wakeup()
1536
  {
1537 2
    $this->reconnect();
1538 2
  }
1539
1540
}
1541