Completed
Push — master ( 8fc2f5...0e91d6 )
by Lars
17:41 queued 02:52
created

DB::clearErrors()   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
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
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 = (int)@ini_get('mysqli.default_port');
191
    }
192
193
    // fallback
194 10
    if (!$this->port) {
195
      $this->port = 3306;
196
    }
197
198 10
    if (!$this->socket) {
199
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
200 10
      $this->socket = @ini_get('mysqli.default_socket');
201 10
    }
202
203 10
    if ($exit_on_error === true || $exit_on_error === false) {
204 10
      $this->_debug->setExitOnError($exit_on_error);
205 10
    }
206
207 10
    if ($echo_on_error === true || $echo_on_error === false) {
208 10
      $this->_debug->setEchoOnError($echo_on_error);
209 10
    }
210
211 10
    $this->_debug->setLoggerClassName($logger_class_name);
212 10
    $this->_debug->setLoggerLevel($logger_level);
213
214 10
    $this->session_to_db = (boolean)$session_to_db;
215
216 10
    return $this->showConfigError();
217
  }
218
219
  /**
220
   * Show config errors by throw exceptions.
221
   *
222
   * @return bool
223
   *
224
   * @throws \Exception
225
   */
226 10
  public function showConfigError()
227
  {
228
229
    if (
230 10
        !$this->hostname
231 10
        ||
232 9
        !$this->username
233 9
        ||
234 8
        !$this->database
235 10
    ) {
236
237 3
      if (!$this->hostname) {
238 1
        throw new \Exception('no-sql-hostname');
239
      }
240
241 2
      if (!$this->username) {
242 1
        throw new \Exception('no-sql-username');
243
      }
244
245 1
      if (!$this->database) {
246 1
        throw new \Exception('no-sql-database');
247
      }
248
249
      return false;
250
    }
251
252 7
    return true;
253
  }
254
255
  /**
256
   * Open a new connection to the MySQL server.
257
   *
258
   * @return boolean
259
   */
260 9
  public function connect()
261
  {
262 9
    if ($this->isReady()) {
263 1
      return true;
264
    }
265
266 9
    \mysqli_report(MYSQLI_REPORT_STRICT);
267
    try {
268 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...
269
270 9
      if (Helper::isMysqlndIsUsed() === true) {
271 9
        \mysqli_options($this->link, MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
272 9
      }
273
274
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
275 9
      $this->connected = @\mysqli_real_connect(
276 9
          $this->link,
277 9
          $this->hostname,
278 9
          $this->username,
279 9
          $this->password,
280 9
          $this->database,
281 9
          $this->port,
282 9
          $this->socket
283 9
      );
284 9
    } catch (\Exception $e) {
285 3
      $this->_debug->displayError('Error connecting to mysql server: ' . $e->getMessage(), true);
286
    }
287 6
    \mysqli_report(MYSQLI_REPORT_OFF);
288
289 6
    if (!$this->connected) {
290
      $this->_debug->displayError('Error connecting to mysql server: ' . \mysqli_connect_error(), true);
291
    } else {
292 6
      $this->set_charset($this->charset);
293
    }
294
295 6
    return $this->isReady();
296
  }
297
298
  /**
299
   * Check if db-connection is ready.
300
   *
301
   * @return boolean
302
   */
303 43
  public function isReady()
304
  {
305 43
    return $this->connected ? true : false;
306
  }
307
308
  /**
309
   * Get a new "Prepare"-Object for your sql-query.
310
   *
311
   * @param string $query
312
   *
313
   * @return Prepare
314
   */
315 2
  public function prepare($query)
316
  {
317 2
    return new Prepare($this, $query);
318
  }
319
320
  /**
321
   * Execute a sql-query and return the result-array for select-statements.
322
   *
323
   * @param $query
324
   *
325
   * @return mixed
326
   * @deprecated
327
   * @throws \Exception
328
   */
329
  public static function qry($query)
330
  {
331
    $db = self::getInstance();
332
333
    $args = func_get_args();
334
    /** @noinspection SuspiciousAssignmentsInspection */
335
    $query = array_shift($args);
336
    $query = str_replace('?', '%s', $query);
337
    $args = array_map(
338
        array(
339
            $db,
340
            'escape',
341
        ),
342
        $args
343
    );
344
    array_unshift($args, $query);
345
    $query = call_user_func_array('sprintf', $args);
346
    $result = $db->query($query);
347
348
    if ($result instanceof Result) {
349
      return $result->fetchAllArray();
350
    } else {
351
      return $result;
352
    }
353
  }
354
355
  /**
356
   * getInstance()
357
   *
358
   * @param string      $hostname
359
   * @param string      $username
360
   * @param string      $password
361
   * @param string      $database
362
   * @param string      $port          default is (int)3306
363
   * @param string      $charset       default is 'utf8' or 'utf8mb4' (if supported)
364
   * @param bool|string $exit_on_error use a empty string "" or false to disable it
365
   * @param bool|string $echo_on_error use a empty string "" or false to disable it
366
   * @param string      $logger_class_name
367
   * @param string      $logger_level
368
   * @param bool|string $session_to_db use a empty string "" or false to disable it
369
   *
370
   * @return \voku\db\DB
371
   */
372 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 = '')
373
  {
374
    /**
375
     * @var $instance DB[]
376
     */
377 54
    static $instance = array();
378
379
    /**
380
     * @var $firstInstance DB
381
     */
382 54
    static $firstInstance = null;
383
384
    if (
385 54
        $hostname . $username . $password . $database . $port . $charset == ''
386 54
        &&
387 9
        null !== $firstInstance
388 54
    ) {
389 9
      return $firstInstance;
390
    }
391
392 54
    $connection = md5(
393 54
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . (int)$session_to_db
394 54
    );
395
396 54
    if (!isset($instance[$connection])) {
397 10
      $instance[$connection] = new self(
398 10
          $hostname,
399 10
          $username,
400 10
          $password,
401 10
          $database,
402 10
          $port,
403 10
          $charset,
404 10
          $exit_on_error,
405 10
          $echo_on_error,
406 10
          $logger_class_name,
407 10
          $logger_level,
408
          $session_to_db
409 10
      );
410
411 4
      if (null === $firstInstance) {
412 1
        $firstInstance = $instance[$connection];
413 1
      }
414 4
    }
415
416 54
    return $instance[$connection];
417
  }
418
419
  /**
420
   * Execute a sql-query.
421
   *
422
   * @param string        $sql            sql-query
423
   *
424
   * @param array|boolean $params         "array" of sql-query-parameters
425
   *                                      "false" if you don't need any parameter (default)
426
   *
427
   * @return bool|int|Result              "Result" by "<b>SELECT</b>"-queries<br />
428
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
429
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
430
   *                                      "true" by e.g. "DROP"-queries<br />
431
   *                                      "false" on error
432
   *
433
   * @throws \Exception
434
   */
435 33
  public function query($sql = '', $params = false)
436
  {
437 33
    if (!$this->isReady()) {
438
      return false;
439
    }
440
441 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...
442 4
      $this->_debug->displayError('Can\'t execute an empty Query', false);
443
444 4
      return false;
445
    }
446
447
    if (
448
        $params !== false
449 31
        &&
450 3
        is_array($params)
451 31
        &&
452 3
        count($params) > 0
453 31
    ) {
454 3
      $sql = $this->_parseQueryParams($sql, $params);
455 3
    }
456
457 31
    $query_start_time = microtime(true);
458 31
    $query_result = \mysqli_real_query($this->link, $sql);
459 31
    $query_duration = microtime(true) - $query_start_time;
460
461 31
    $this->query_count++;
462
463 31
    $mysqli_field_count = \mysqli_field_count($this->link);
464 31
    if ($mysqli_field_count) {
465 26
      $result = \mysqli_store_result($this->link);
466 26
    } else {
467 22
      $result = $query_result;
468
    }
469
470 31
    if ($result instanceof \mysqli_result) {
471
472
      // log the select query
473 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 454 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...
474
475
      // return query result object
476 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 454 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...
477
478 23
    } elseif ($query_result === true) {
479
480
      // "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...
481 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...
482 20
        $insert_id = (int)$this->insert_id();
483 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 454 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...
484
485 20
        return $insert_id;
486
      }
487
488
      // "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...
489 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...
490 7
        $affected_rows = (int)$this->affected_rows();
491 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 454 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 7
        return $affected_rows;
494
      }
495
496
      // log the ? query
497
      $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 454 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
      return true;
500
    }
501
502
    // log the error query
503 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 454 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...
504
505 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 454 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...
506
  }
507
508
  /**
509
   * _parseQueryParams
510
   *
511
   * @param string $sql
512
   * @param array  $params
513
   *
514
   * @return string
515
   */
516 3
  private function _parseQueryParams($sql, array $params)
517
  {
518
    // is there anything to parse?
519 3
    if (strpos($sql, '?') === false) {
520
      return $sql;
521
    }
522
523 3
    if (count($params) > 0) {
524 3
      $parseKey = md5(uniqid((string)mt_rand(), true));
525 3
      $sql = str_replace('?', $parseKey, $sql);
526
527 3
      $k = 0;
528 3
      while (strpos($sql, $parseKey) !== false) {
529 3
        $value = $this->secure($params[$k]);
530 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 530 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...
531 3
        $k++;
532 3
      }
533 3
    }
534
535 3
    return $sql;
536
  }
537
538
  /**
539
   * Try to secure a variable, so can you use it in sql-queries.
540
   *
541
   * <p>
542
   * <strong>int:</strong> (also strings that contains only an int-value)<br />
543
   * 1. parse into "int"
544
   * </p><br />
545
   *
546
   * <p>
547
   * <strong>float:</strong><br />
548
   * 1. return "float"
549
   * </p><br />
550
   *
551
   * <p>
552
   * <strong>string:</strong><br />
553
   * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'<br />
554
   * 2. trim whitespace<br />
555
   * 3. trim '<br />
556
   * 4. escape the string (and remove non utf-8 chars)<br />
557
   * 5. trim ' again (because we maybe removed some chars)<br />
558
   * 6. add ' around the new string<br />
559
   * </p><br />
560
   *
561
   * <p>
562
   * <strong>array:</strong><br />
563
   * 1. return null
564
   * </p><br />
565
   *
566
   * <p>
567
   * <strong>object:</strong><br />
568
   * 1. return false
569
   * </p><br />
570
   *
571
   * <p>
572
   * <strong>null:</strong><br />
573
   * 1. return null
574
   * </p>
575
   *
576
   * @param mixed $var
577
   *
578
   * @return mixed
579
   */
580 24
  public function secure($var)
581
  {
582
    if (
583
        $var === ''
584 24
        ||
585 24
        ($this->_convert_null_to_empty_string === true && $var === null)
586 24
    ) {
587 1
      return "''";
588
    }
589
590 24
    if (in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
591 1
      return $var;
592
    }
593
594 24
    if (is_string($var)) {
595 20
      $var = trim(trim($var), "'");
596 20
    }
597
598 24
    $var = $this->escape($var, false, false, null);
599
600 24
    if (is_string($var)) {
601 20
      $var = "'" . trim($var, "'") . "'";
602 20
    }
603
604 24
    return $var;
605
  }
606
607
  /**
608
   * Escape: Use "mysqli_real_escape_string" and clean non UTF-8 chars + some extra optional stuff.
609
   *
610
   * @param mixed     $var           boolean: convert into "integer"<br />
611
   *                                 int: int (don't change it)<br />
612
   *                                 float: float (don't change it)<br />
613
   *                                 null: null (don't change it)<br />
614
   *                                 array: run escape() for every key => value<br />
615
   *                                 string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
616
   * @param bool      $stripe_non_utf8
617
   * @param bool      $html_entity_decode
618
   * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
619
   *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
620
   *                                 <strong>null</strong> => Convert the array into null, every time.
621
   *
622
   * @return mixed
623
   */
624 30
  public function escape($var = '', $stripe_non_utf8 = true, $html_entity_decode = false, $convert_array = false)
625
  {
626 30
    if ($var === '') {
627 2
      return '';
628
    }
629
630 30
    if ($var === null) {
631 2
      return null;
632
    }
633
634
    // save the current value as int (for later usage)
635 30
    if (!is_object($var)) {
636 30
      $varInt = (int)$var;
637 30
    }
638
639
    /** @noinspection TypeUnsafeComparisonInspection */
640
    if (
641 30
        is_int($var)
642
        ||
643 30
        is_bool($var)
644 30
        ||
645 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...
646 30
    ) {
647
648
      // "int" || int || bool
649
650 21
      return (int)$var;
651
652 30
    } elseif (is_float($var)) {
653
654
      // float
655
656 5
      return $var;
657
658 30
    } elseif (is_array($var)) {
659
660
      // array
661
662 3
      if ($convert_array === null) {
663 3
        return null;
664
      }
665
666 1
      $varCleaned = array();
667 1
      foreach ((array)$var as $key => $value) {
668
669 1
        $key = $this->escape($key, $stripe_non_utf8, $html_entity_decode);
670 1
        $value = $this->escape($value, $stripe_non_utf8, $html_entity_decode);
671
672
        /** @noinspection OffsetOperationsInspection */
673 1
        $varCleaned[$key] = $value;
674 1
      }
675
676 1
      if ($convert_array === true) {
677 1
        $varCleaned = implode(',', $varCleaned);
678
679 1
        return $varCleaned;
680
      } else {
681 1
        return (array)$varCleaned;
682
      }
683
    }
684
685
    if (
686 30
        is_string($var)
687
        ||
688 3
        (is_object($var) && method_exists($var, '__toString'))
689 30
    ) {
690
691
      // "string"
692
693 30
      $var = (string)$var;
694
695 30
      if ($stripe_non_utf8 === true) {
696 10
        $var = UTF8::cleanup($var);
697 10
      }
698
699 30
      if ($html_entity_decode === true) {
700
        // use no-html-entity for db
701 1
        $var = UTF8::html_entity_decode($var);
702 1
      }
703
704 30
      $var = get_magic_quotes_gpc() ? stripslashes($var) : $var;
705
706 30
      $var = \mysqli_real_escape_string($this->getLink(), $var);
707
708 30
      return (string)$var;
709
710 3
    } elseif ($var instanceof \DateTime) {
711
712
      // "DateTime"-object
713
714
      try {
715 3
        return $this->escape($var->format('Y-m-d H:i:s'), false);
716
      } catch (\Exception $e) {
717
        return null;
718
      }
719
720
    } else {
721 2
      return false;
722
    }
723
  }
724
725
  /**
726
   * Get the mysqli-link (link identifier returned by mysqli-connect).
727
   *
728
   * @return \mysqli
729
   */
730 32
  public function getLink()
731
  {
732 32
    return $this->link;
733
  }
734
735
  /**
736
   * Returns the auto generated id used in the last query.
737
   *
738
   * @return int|string
739
   */
740 20
  public function insert_id()
741
  {
742 20
    return \mysqli_insert_id($this->link);
743
  }
744
745
  /**
746
   * Gets the number of affected rows in a previous MySQL operation.
747
   *
748
   * @return int
749
   */
750 7
  public function affected_rows()
751
  {
752 7
    return \mysqli_affected_rows($this->link);
753
  }
754
755
  /**
756
   * Error-handling for the sql-query.
757
   *
758
   * @param string     $errorMsg
759
   * @param string     $sql
760
   * @param array|bool $sqlParams false if there wasn't any parameter
761
   *
762
   * @throws \Exception
763
   *
764
   * @return bool
765
   */
766 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...
767
  {
768 9
    if ($errorMsg === 'DB server has gone away' || $errorMsg === 'MySQL server has gone away') {
769 1
      static $reconnectCounter;
770
771
      // exit if we have more then 3 "DB server has gone away"-errors
772 1
      if ($reconnectCounter > 3) {
773
        $this->_debug->mailToAdmin('SQL-Fatal-Error', $errorMsg . ":\n<br />" . $sql, 5);
774
        throw new \Exception($errorMsg);
775
      } else {
776 1
        $this->_debug->mailToAdmin('SQL-Error', $errorMsg . ":\n<br />" . $sql);
777
778
        // reconnect
779 1
        $reconnectCounter++;
780 1
        $this->reconnect(true);
781
782
        // re-run the current query
783 1
        return $this->query($sql, $sqlParams);
784
      }
785
    } else {
786 8
      $this->_debug->mailToAdmin('SQL-Warning', $errorMsg . ":\n<br />" . $sql);
787
788
      // this query returned an error, we must display it (only for dev) !!!
789 8
      $this->_debug->displayError($errorMsg . ' | ' . $sql);
790
    }
791
792 8
    return false;
793
  }
794
795
  /**
796
   * Reconnect to the MySQL-Server.
797
   *
798
   * @param bool $checkViaPing
799
   *
800
   * @return bool
801
   */
802 3
  public function reconnect($checkViaPing = false)
803
  {
804 3
    $ping = false;
805
806 3
    if ($checkViaPing === true) {
807 2
      $ping = $this->ping();
808 2
    }
809
810 3
    if ($ping !== true) {
811 3
      $this->connected = false;
812 3
      $this->connect();
813 3
    }
814
815 3
    return $this->isReady();
816
  }
817
818
  /**
819
   * Pings a server connection, or tries to reconnect
820
   * if the connection has gone down.
821
   *
822
   * @return boolean
823
   */
824 3
  public function ping()
825
  {
826
    if (
827 3
        $this->link
828 3
        &&
829 3
        $this->link instanceof \mysqli
830 3
    ) {
831
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
832 3
      return @\mysqli_ping($this->link);
833
    } else {
834
      return false;
835
    }
836
  }
837
838
  /**
839
   * Execute select/insert/update/delete sql-queries.
840
   *
841
   * @param string $query    sql-query
842
   * @param bool   $useCache use cache?
843
   * @param int    $cacheTTL cache-ttl in seconds
844
   *
845
   * @return mixed "array" by "<b>SELECT</b>"-queries<br />
846
   *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
847
   *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
848
   *               "true" by e.g. "DROP"-queries<br />
849
   *               "false" on error
850
   */
851 3
  public static function execSQL($query, $useCache = false, $cacheTTL = 3600)
852
  {
853 3
    $db = self::getInstance();
854
855 3
    if ($useCache === true) {
856 1
      $cache = new Cache(null, null, false, $useCache);
857 1
      $cacheKey = 'sql-' . md5($query);
858
859
      if (
860 1
          $cache->getCacheIsReady() === true
861 1
          &&
862 1
          $cache->existsItem($cacheKey)
863 1
      ) {
864 1
        return $cache->getItem($cacheKey);
865
      }
866
867 1
    } else {
868 3
      $cache = false;
869
    }
870
871 3
    $result = $db->query($query);
872
873 3
    if ($result instanceof Result) {
874
875 1
      $return = $result->fetchAllArray();
876
877
      if (
878 1
          isset($cacheKey)
879 1
          &&
880
          $useCache === true
881 1
          &&
882
          $cache instanceof Cache
883 1
          &&
884 1
          $cache->getCacheIsReady() === true
885 1
      ) {
886 1
        $cache->setItem($cacheKey, $return, $cacheTTL);
887 1
      }
888
889 1
    } else {
890 2
      $return = $result;
891
    }
892
893 3
    return $return;
894
  }
895
896
  /**
897
   * Get the current charset.
898
   *
899
   * @return string
900
   */
901 1
  public function get_charset()
902
  {
903 1
    return $this->charset;
904
  }
905
906
  /**
907
   * Enables or disables internal report functions
908
   *
909
   * @link http://php.net/manual/en/function.mysqli-report.php
910
   *
911
   * @param int $flags <p>
912
   *                   <table>
913
   *                   Supported flags
914
   *                   <tr valign="top">
915
   *                   <td>Name</td>
916
   *                   <td>Description</td>
917
   *                   </tr>
918
   *                   <tr valign="top">
919
   *                   <td><b>MYSQLI_REPORT_OFF</b></td>
920
   *                   <td>Turns reporting off</td>
921
   *                   </tr>
922
   *                   <tr valign="top">
923
   *                   <td><b>MYSQLI_REPORT_ERROR</b></td>
924
   *                   <td>Report errors from mysqli function calls</td>
925
   *                   </tr>
926
   *                   <tr valign="top">
927
   *                   <td><b>MYSQLI_REPORT_STRICT</b></td>
928
   *                   <td>
929
   *                   Throw <b>mysqli_sql_exception</b> for errors
930
   *                   instead of warnings
931
   *                   </td>
932
   *                   </tr>
933
   *                   <tr valign="top">
934
   *                   <td><b>MYSQLI_REPORT_INDEX</b></td>
935
   *                   <td>Report if no index or bad index was used in a query</td>
936
   *                   </tr>
937
   *                   <tr valign="top">
938
   *                   <td><b>MYSQLI_REPORT_ALL</b></td>
939
   *                   <td>Set all options (report all)</td>
940
   *                   </tr>
941
   *                   </table>
942
   *                   </p>
943
   *
944
   * @return bool
945
   */
946
  public function set_mysqli_report($flags)
947
  {
948
    return \mysqli_report($flags);
949
  }
950
951
  /**
952
   * Set the current charset.
953
   *
954
   * @param string $charset
955
   *
956
   * @return bool
957
   */
958 7
  public function set_charset($charset)
959
  {
960 7
    $charsetLower = strtolower($charset);
961 7
    if ($charsetLower === 'utf8' || $charsetLower === 'utf-8') {
962 7
      $charset = 'utf8';
963 7
    }
964 7
    if ($charset === 'utf8' && Helper::isUtf8mb4Supported($this) === true) {
965
      $charset = 'utf8mb4';
966
    }
967
968 7
    $this->charset = (string)$charset;
969
970 7
    $return = mysqli_set_charset($this->link, $charset);
971
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
972 7
    @\mysqli_query($this->link, 'SET CHARACTER SET ' . $charset);
973
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
974 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...
975
976 7
    return $return;
977
  }
978
979
  /**
980
   * Set the option to convert null to "''" (empty string).
981
   *
982
   * Used in secure() => select(), insert(), update(), delete()
983
   *
984
   * @param $bool
985
   */
986 1
  public function set_convert_null_to_empty_string($bool)
987
  {
988 1
    $this->_convert_null_to_empty_string = (bool)$bool;
989 1
  }
990
991
  /**
992
   * Get all table-names via "SHOW TABLES".
993
   *
994
   * @return array
995
   */
996 1
  public function getAllTables()
997
  {
998 1
    $query = 'SHOW TABLES';
999 1
    $result = $this->query($query);
1000
1001 1
    return $result->fetchAllArray();
1002
  }
1003
1004
  /**
1005
   * Execute a sql-multi-query.
1006
   *
1007
   * @param string $sql
1008
   *
1009
   * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
1010
   *                        "boolean" by only "<b>INSERT</b>"-queries<br />
1011
   *                        "boolean" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1012
   *                        "boolean" by only by e.g. "DROP"-queries<br />
1013
   *
1014
   * @throws \Exception
1015
   */
1016 1
  public function multi_query($sql)
1017
  {
1018 1
    if (!$this->isReady()) {
1019
      return false;
1020
    }
1021
1022 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...
1023 1
      $this->_debug->displayError('Can\'t execute an empty Query', false);
1024
1025 1
      return false;
1026
    }
1027
1028 1
    $query_start_time = microtime(true);
1029 1
    $resultTmp = \mysqli_multi_query($this->link, $sql);
1030 1
    $query_duration = microtime(true) - $query_start_time;
1031
1032 1
    $this->_debug->logQuery($sql, $query_duration, 0);
1033
1034 1
    $returnTheResult = false;
1035 1
    $result = array();
1036 1
    if ($resultTmp) {
1037
      do {
1038 1
        $resultTmpInner = \mysqli_store_result($this->link);
1039
1040 1
        if ($resultTmpInner instanceof \mysqli_result) {
1041 1
          $returnTheResult = true;
1042 1
          $result[] = new Result($sql, $resultTmpInner);
1043 1
        } else {
1044 1
          $errorMsg = \mysqli_error($this->link);
1045
1046
          // is the query successful
1047 1
          if ($resultTmpInner === true || !$errorMsg) {
1048 1
            $result[] = true;
1049 1
          } else {
1050
            $result[] = $this->queryErrorHandling($errorMsg, $sql);
1051
          }
1052
        }
1053 1
      } while (\mysqli_more_results($this->link) === true ? \mysqli_next_result($this->link) : false);
1054
1055 1
    } else {
1056
1057
      $errorMsg = \mysqli_error($this->link);
1058
1059
      if ($this->_debug->checkForDev() === true) {
1060
        echo "Info: maybe you have to increase your 'max_allowed_packet = 30M' in the config: 'my.conf' \n<br />";
1061
        echo 'Error:' . $errorMsg;
1062
      }
1063
1064
      $this->_debug->mailToAdmin('SQL-Error in mysqli_multi_query', $errorMsg . ":\n<br />" . $sql);
1065
    }
1066
1067
    // return the result only if there was a "SELECT"-query
1068 1
    if ($returnTheResult === true) {
1069 1
      return $result;
1070
    }
1071
1072 1
    if (!in_array(false, $result, true)) {
1073 1
      return true;
1074
    } else {
1075
      return false;
1076
    }
1077
  }
1078
1079
  /**
1080
   * alias: "beginTransaction()"
1081
   */
1082 1
  public function startTransaction()
1083
  {
1084 1
    $this->beginTransaction();
1085 1
  }
1086
1087
  /**
1088
   * Begins a transaction, by turning off auto commit.
1089
   *
1090
   * @return boolean this will return true or false indicating success of transaction
1091
   */
1092 4
  public function beginTransaction()
1093
  {
1094 4
    $this->clearErrors();
1095
1096 4
    if ($this->inTransaction() === true) {
1097 1
      $this->_debug->displayError('Error mysql server already in transaction!', true);
1098
1099
      return false;
1100 4
    } elseif (\mysqli_connect_errno()) {
1101
      $this->_debug->displayError('Error connecting to mysql server: ' . \mysqli_connect_error(), true);
1102
1103
      return false;
1104
    } else {
1105 4
      $this->_in_transaction = true;
1106 4
      \mysqli_autocommit($this->link, false);
1107
1108 4
      return true;
1109
1110
    }
1111
  }
1112
1113
  /**
1114
   * Clear the errors in "_debug->_errors".
1115
   *
1116
   * @return bool
1117
   */
1118 4
  public function clearErrors()
1119
  {
1120 4
    return $this->_debug->clearErrors();
1121
  }
1122
1123
  /**
1124
   * Check if we are in a transaction.
1125
   *
1126
   * @return boolean
1127
   */
1128 4
  public function inTransaction()
1129
  {
1130 4
    return $this->_in_transaction;
1131
  }
1132
1133
  /**
1134
   * Ends a transaction and commits if no errors, then ends autocommit.
1135
   *
1136
   * @return boolean this will return true or false indicating success of transactions
1137
   */
1138 2
  public function endTransaction()
1139
  {
1140
1141 2
    if (!$this->errors()) {
1142 1
      \mysqli_commit($this->link);
1143 1
      $return = true;
1144 1
    } else {
1145 1
      $this->rollback();
1146 1
      $return = false;
1147
    }
1148
1149 2
    \mysqli_autocommit($this->link, true);
1150 2
    $this->_in_transaction = false;
1151
1152 2
    return $return;
1153
  }
1154
1155
  /**
1156
   * Get all errors from "$this->_errors".
1157
   *
1158
   * @return array|false false === on errors
1159
   */
1160 2
  public function errors()
1161
  {
1162 2
    $errors = $this->_debug->getErrors();
1163
1164 2
    return count($errors) > 0 ? $errors : false;
1165
  }
1166
1167
  /**
1168
   * Rollback in a transaction.
1169
   */
1170 2
  public function rollback()
1171
  {
1172
    // init
1173 2
    $return = false;
1174
1175 2
    if ($this->_in_transaction === true) {
1176 2
      $return = \mysqli_rollback($this->link);
1177 2
      \mysqli_autocommit($this->link, true);
1178 2
      $this->_in_transaction = false;
1179 2
    }
1180
1181 2
    return $return;
1182
  }
1183
1184
  /**
1185
   * Execute a "insert"-query.
1186
   *
1187
   * @param string $table
1188
   * @param array  $data
1189
   *
1190
   * @return false|int false on error
1191
   */
1192 19
  public function insert($table, array $data = array())
1193
  {
1194 19
    $table = trim($table);
1195
1196 19
    if ($table === '') {
1197 2
      $this->_debug->displayError('invalid table name');
1198
1199 1
      return false;
1200
    }
1201
1202 18
    if (count($data) === 0) {
1203 3
      $this->_debug->displayError('empty data for INSERT');
1204
1205 2
      return false;
1206
    }
1207
1208 16
    $SET = $this->_parseArrayPair($data);
1209
1210 16
    $sql = 'INSERT INTO ' . $this->quote_string($table) . " SET $SET;";
1211
1212 16
    return $this->query($sql);
1213
  }
1214
1215
  /**
1216
   * Parses arrays with value pairs and generates SQL to use in queries.
1217
   *
1218
   * @param array  $arrayPair
1219
   * @param string $glue this is the separator
1220
   *
1221
   * @return string
1222
   */
1223 21
  private function _parseArrayPair($arrayPair, $glue = ',')
1224
  {
1225
    // init
1226 21
    $sql = '';
1227 21
    $pairs = array();
1228
1229
    /** @noinspection IsEmptyFunctionUsageInspection */
1230 21
    if (!empty($arrayPair)) {
1231
1232 21
      foreach ($arrayPair as $_key => $_value) {
1233 21
        $_connector = '=';
1234 21
        $_key_upper = strtoupper($_key);
1235
1236 21
        if (strpos($_key_upper, ' NOT') !== false) {
1237 2
          $_connector = 'NOT';
1238 2
        }
1239
1240 21
        if (strpos($_key_upper, ' IS') !== false) {
1241 1
          $_connector = 'IS';
1242 1
        }
1243
1244 21
        if (strpos($_key_upper, ' IS NOT') !== false) {
1245 1
          $_connector = 'IS NOT';
1246 1
        }
1247
1248 21
        if (strpos($_key_upper, ' IN') !== false) {
1249 1
          $_connector = 'IN';
1250 1
        }
1251
1252 21
        if (strpos($_key_upper, ' NOT IN') !== false) {
1253 1
          $_connector = 'NOT IN';
1254 1
        }
1255
1256 21
        if (strpos($_key_upper, ' BETWEEN') !== false) {
1257 1
          $_connector = 'BETWEEN';
1258 1
        }
1259
1260 21
        if (strpos($_key_upper, ' NOT BETWEEN') !== false) {
1261 1
          $_connector = 'NOT BETWEEN';
1262 1
        }
1263
1264 21
        if (strpos($_key_upper, ' LIKE') !== false) {
1265 2
          $_connector = 'LIKE';
1266 2
        }
1267
1268 21
        if (strpos($_key_upper, ' NOT LIKE') !== false) {
1269 2
          $_connector = 'NOT LIKE';
1270 2
        }
1271
1272 21 View Code Duplication
        if (strpos($_key_upper, ' >') !== false && strpos($_key_upper, ' =') === false) {
1273 2
          $_connector = '>';
1274 2
        }
1275
1276 21 View Code Duplication
        if (strpos($_key_upper, ' <') !== false && strpos($_key_upper, ' =') === false) {
1277 1
          $_connector = '<';
1278 1
        }
1279
1280 21
        if (strpos($_key_upper, ' >=') !== false) {
1281 2
          $_connector = '>=';
1282 2
        }
1283
1284 21
        if (strpos($_key_upper, ' <=') !== false) {
1285 1
          $_connector = '<=';
1286 1
        }
1287
1288 21
        if (strpos($_key_upper, ' <>') !== false) {
1289 1
          $_connector = '<>';
1290 1
        }
1291
1292 21
        if (is_array($_value) === true) {
1293 1
          foreach ($_value as $oldKey => $oldValue) {
1294 1
            $_value[$oldKey] = $this->secure($oldValue);
1295 1
          }
1296
1297 1
          if ($_connector === 'NOT IN' || $_connector === 'IN') {
1298 1
            $_value = '(' . implode(',', $_value) . ')';
1299 1
          } elseif ($_connector === 'NOT BETWEEN' || $_connector === 'BETWEEN') {
1300 1
            $_value = '(' . implode(' AND ', $_value) . ')';
1301 1
          }
1302
1303 1
        } else {
1304 21
          $_value = $this->secure($_value);
1305
        }
1306
1307 21
        $quoteString = $this->quote_string(trim(str_ireplace($_connector, '', $_key)));
1308 21
        $pairs[] = ' ' . $quoteString . ' ' . $_connector . ' ' . $_value . " \n";
1309 21
      }
1310
1311 21
      $sql = implode($glue, $pairs);
1312 21
    }
1313
1314 21
    return $sql;
1315
  }
1316
1317
  /**
1318
   * Quote && Escape e.g. a table name string.
1319
   *
1320
   * @param string $str
1321
   *
1322
   * @return string
1323
   */
1324 23
  public function quote_string($str)
1325
  {
1326 23
    return '`' . $this->escape($str, false) . '`';
1327
  }
1328
1329
  /**
1330
   * Get errors from "$this->_errors".
1331
   *
1332
   * @return array
1333
   */
1334 1
  public function getErrors()
1335
  {
1336 1
    return $this->_debug->getErrors();
1337
  }
1338
1339
  /**
1340
   * Execute a "replace"-query.
1341
   *
1342
   * @param string $table
1343
   * @param array  $data
1344
   *
1345
   * @return false|int false on error
1346
   */
1347 1
  public function replace($table, array $data = array())
1348
  {
1349
1350 1
    $table = trim($table);
1351
1352 1
    if ($table === '') {
1353 1
      $this->_debug->displayError('invalid table name');
1354
1355 1
      return false;
1356
    }
1357
1358 1
    if (count($data) === 0) {
1359 1
      $this->_debug->displayError('empty data for REPLACE');
1360
1361 1
      return false;
1362
    }
1363
1364
    // extracting column names
1365 1
    $columns = array_keys($data);
1366 1
    foreach ($columns as $k => $_key) {
1367
      /** @noinspection AlterInForeachInspection */
1368 1
      $columns[$k] = $this->quote_string($_key);
1369 1
    }
1370
1371 1
    $columns = implode(',', $columns);
1372
1373
    // extracting values
1374 1
    foreach ($data as $k => $_value) {
1375
      /** @noinspection AlterInForeachInspection */
1376 1
      $data[$k] = $this->secure($_value);
1377 1
    }
1378 1
    $values = implode(',', $data);
1379
1380 1
    $sql = 'REPLACE INTO ' . $this->quote_string($table) . " ($columns) VALUES ($values);";
1381
1382 1
    return $this->query($sql);
1383
  }
1384
1385
  /**
1386
   * Execute a "update"-query.
1387
   *
1388
   * @param string       $table
1389
   * @param array        $data
1390
   * @param array|string $where
1391
   *
1392
   * @return false|int false on error
1393
   */
1394 6
  public function update($table, array $data = array(), $where = '1=1')
1395
  {
1396 6
    $table = trim($table);
1397
1398 6
    if ($table === '') {
1399 1
      $this->_debug->displayError('invalid table name');
1400
1401 1
      return false;
1402
    }
1403
1404 6
    if (count($data) === 0) {
1405 2
      $this->_debug->displayError('empty data for UPDATE');
1406
1407 2
      return false;
1408
    }
1409
1410 6
    $SET = $this->_parseArrayPair($data);
1411
1412 6
    if (is_string($where)) {
1413 2
      $WHERE = $this->escape($where, false);
1414 6
    } elseif (is_array($where)) {
1415 4
      $WHERE = $this->_parseArrayPair($where, 'AND');
1416 4
    } else {
1417 1
      $WHERE = '';
1418
    }
1419
1420 6
    $sql = 'UPDATE ' . $this->quote_string($table) . " SET $SET WHERE ($WHERE);";
1421
1422 6
    return $this->query($sql);
1423
  }
1424
1425
  /**
1426
   * Execute a "delete"-query.
1427
   *
1428
   * @param string       $table
1429
   * @param string|array $where
1430
   *
1431
   * @return false|int false on error
1432
   */
1433 1 View Code Duplication
  public function delete($table, $where)
1434
  {
1435
1436 1
    $table = trim($table);
1437
1438 1
    if ($table === '') {
1439 1
      $this->_debug->displayError('invalid table name');
1440
1441 1
      return false;
1442
    }
1443
1444 1
    if (is_string($where)) {
1445 1
      $WHERE = $this->escape($where, false);
1446 1
    } elseif (is_array($where)) {
1447 1
      $WHERE = $this->_parseArrayPair($where, 'AND');
1448 1
    } else {
1449 1
      $WHERE = '';
1450
    }
1451
1452 1
    $sql = 'DELETE FROM ' . $this->quote_string($table) . " WHERE ($WHERE);";
1453
1454 1
    return $this->query($sql);
1455
  }
1456
1457
  /**
1458
   * Execute a "select"-query.
1459
   *
1460
   * @param string       $table
1461
   * @param string|array $where
1462
   *
1463
   * @return false|Result false on error
1464
   */
1465 20 View Code Duplication
  public function select($table, $where = '1=1')
1466
  {
1467
1468 20
    if ($table === '') {
1469 1
      $this->_debug->displayError('invalid table name');
1470
1471 1
      return false;
1472
    }
1473
1474 20
    if (is_string($where)) {
1475 5
      $WHERE = $this->escape($where, false);
1476 20
    } elseif (is_array($where)) {
1477 16
      $WHERE = $this->_parseArrayPair($where, 'AND');
1478 16
    } else {
1479 1
      $WHERE = '';
1480
    }
1481
1482 20
    $sql = 'SELECT * FROM ' . $this->quote_string($table) . " WHERE ($WHERE);";
1483
1484 20
    return $this->query($sql);
1485
  }
1486
1487
  /**
1488
   * Get the last sql-error.
1489
   *
1490
   * @return string false on error
1491
   */
1492 1
  public function lastError()
1493
  {
1494 1
    $errors = $this->_debug->getErrors();
1495
1496 1
    return count($errors) > 0 ? end($errors) : false;
1497
  }
1498
1499
  /**
1500
   * @return Debug
1501
   */
1502 9
  public function getDebugger()
1503
  {
1504 9
    return $this->_debug;
1505
  }
1506
1507
  /**
1508
   * __destruct
1509
   *
1510
   */
1511
  public function __destruct()
1512
  {
1513
    // close the connection only if we don't save PHP-SESSION's in DB
1514
    if ($this->session_to_db === false) {
1515
      $this->close();
1516
    }
1517
  }
1518
1519
  /**
1520
   * Closes a previously opened database connection.
1521
   */
1522 2
  public function close()
1523
  {
1524 2
    $this->connected = false;
1525
1526 2
    if ($this->link) {
1527 2
      \mysqli_close($this->link);
1528 2
    }
1529 2
  }
1530
1531
  /**
1532
   * Prevent the instance from being cloned.
1533
   *
1534
   * @return void
1535
   */
1536
  private function __clone()
1537
  {
1538
  }
1539
1540
  /**
1541
   * __wakeup
1542
   *
1543
   * @return void
1544
   */
1545 2
  public function __wakeup()
1546
  {
1547 2
    $this->reconnect();
1548 2
  }
1549
1550
}
1551