Completed
Push — master ( 6895c9...01f26e )
by Lars
08:35 queued 56s
created

DB::errors()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 0
crap 2
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
    }
351
352
    return $result;
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 55
  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 55
    static $instance = array();
378
379
    /**
380
     * @var $firstInstance DB
381
     */
382 55
    static $firstInstance = null;
383
384
    if (
385 55
        $hostname . $username . $password . $database . $port . $charset == ''
386 55
        &&
387 9
        null !== $firstInstance
388 55
    ) {
389 9
      return $firstInstance;
390
    }
391
392 55
    $connection = md5(
393 55
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . (int)$session_to_db
394 55
    );
395
396 55
    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 55
    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
    }
479
480 23
    if ($query_result === true) {
481
482
      // "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...
483 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...
484 20
        $insert_id = (int)$this->insert_id();
485 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...
486
487 20
        return $insert_id;
488
      }
489
490
      // "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...
491 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...
492 7
        $affected_rows = (int)$this->affected_rows();
493 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...
494
495 7
        return $affected_rows;
496
      }
497
498
      // log the ? query
499
      $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...
500
501
      return true;
502
    }
503
504
    // log the error query
505 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...
506
507 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...
508
  }
509
510
  /**
511
   * _parseQueryParams
512
   *
513
   * @param string $sql
514
   * @param array  $params
515
   *
516
   * @return string
517
   */
518 3
  private function _parseQueryParams($sql, array $params)
519
  {
520
    // is there anything to parse?
521 3
    if (strpos($sql, '?') === false) {
522
      return $sql;
523
    }
524
525 3
    if (count($params) > 0) {
526 3
      $parseKey = md5(uniqid((string)mt_rand(), true));
527 3
      $sql = str_replace('?', $parseKey, $sql);
528
529 3
      $k = 0;
530 3
      while (strpos($sql, $parseKey) !== false) {
531 3
        $value = $this->secure($params[$k]);
532 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 532 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...
533 3
        $k++;
534 3
      }
535 3
    }
536
537 3
    return $sql;
538
  }
539
540
  /**
541
   * Try to secure a variable, so can you use it in sql-queries.
542
   *
543
   * <p>
544
   * <strong>int:</strong> (also strings that contains only an int-value)<br />
545
   * 1. parse into "int"
546
   * </p><br />
547
   *
548
   * <p>
549
   * <strong>float:</strong><br />
550
   * 1. return "float"
551
   * </p><br />
552
   *
553
   * <p>
554
   * <strong>string:</strong><br />
555
   * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'<br />
556
   * 2. trim whitespace<br />
557
   * 3. trim '<br />
558
   * 4. escape the string (and remove non utf-8 chars)<br />
559
   * 5. trim ' again (because we maybe removed some chars)<br />
560
   * 6. add ' around the new string<br />
561
   * </p><br />
562
   *
563
   * <p>
564
   * <strong>array:</strong><br />
565
   * 1. return null
566
   * </p><br />
567
   *
568
   * <p>
569
   * <strong>object:</strong><br />
570
   * 1. return false
571
   * </p><br />
572
   *
573
   * <p>
574
   * <strong>null:</strong><br />
575
   * 1. return null
576
   * </p>
577
   *
578
   * @param mixed $var
579
   *
580
   * @return mixed
581
   */
582 24
  public function secure($var)
583
  {
584
    if (
585
        $var === ''
586 24
        ||
587 24
        ($this->_convert_null_to_empty_string === true && $var === null)
588 24
    ) {
589 1
      return "''";
590
    }
591
592 24
    if (in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
593 1
      return $var;
594
    }
595
596 24
    if (is_string($var)) {
597 20
      $var = trim(trim($var), "'");
598 20
    }
599
600 24
    $var = $this->escape($var, false, false, null);
601
602 24
    if (is_string($var)) {
603 20
      $var = "'" . trim($var, "'") . "'";
604 20
    }
605
606 24
    return $var;
607
  }
608
609
  /**
610
   * Escape: Use "mysqli_real_escape_string" and clean non UTF-8 chars + some extra optional stuff.
611
   *
612
   * @param mixed     $var           boolean: convert into "integer"<br />
613
   *                                 int: int (don't change it)<br />
614
   *                                 float: float (don't change it)<br />
615
   *                                 null: null (don't change it)<br />
616
   *                                 array: run escape() for every key => value<br />
617
   *                                 string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
618
   * @param bool      $stripe_non_utf8
619
   * @param bool      $html_entity_decode
620
   * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
621
   *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
622
   *                                 <strong>null</strong> => Convert the array into null, every time.
623
   *
624
   * @return mixed
625
   */
626 31
  public function escape($var = '', $stripe_non_utf8 = true, $html_entity_decode = false, $convert_array = false)
627
  {
628 31
    if ($var === '') {
629 2
      return '';
630
    }
631
632 31
    if ($var === null) {
633 2
      return null;
634
    }
635
636
    // save the current value as int (for later usage)
637 31
    if (!is_object($var)) {
638 31
      $varInt = (int)$var;
639 31
    }
640
641
    /** @noinspection TypeUnsafeComparisonInspection */
642
    if (
643 31
        is_int($var)
644
        ||
645 31
        is_bool($var)
646 31
        ||
647
        (
648 31
            isset($varInt, $var[0])
649 31
            &&
650 31
            $var[0] != '0'
651 31
            &&
652
            "$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...
653 31
        )
654 31
    ) {
655
656
      // "int" || int || bool
657
658 22
      return (int)$var;
659
    }
660
661 31
    if (is_float($var)) {
662
663
      // float
664
665 5
      return $var;
666
    }
667
668 31
    if (is_array($var)) {
669
670
      // array
671
672 3
      if ($convert_array === null) {
673 3
        return null;
674
      }
675
676 1
      $varCleaned = array();
677 1
      foreach ((array)$var as $key => $value) {
678
679 1
        $key = $this->escape($key, $stripe_non_utf8, $html_entity_decode);
680 1
        $value = $this->escape($value, $stripe_non_utf8, $html_entity_decode);
681
682
        /** @noinspection OffsetOperationsInspection */
683 1
        $varCleaned[$key] = $value;
684 1
      }
685
686 1
      if ($convert_array === true) {
687 1
        $varCleaned = implode(',', $varCleaned);
688
689 1
        return $varCleaned;
690
      }
691
692 1
      return (array)$varCleaned;
693
    }
694
695
    if (
696 31
        is_string($var)
697
        ||
698
        (
699 3
            is_object($var)
700 3
            &&
701 3
            method_exists($var, '__toString')
702 3
        )
703 31
    ) {
704
705
      // "string"
706
707 31
      $var = (string)$var;
708
709 31
      if ($stripe_non_utf8 === true) {
710 10
        $var = UTF8::cleanup($var);
711 10
      }
712
713 31
      if ($html_entity_decode === true) {
714
        // use no-html-entity for db
715 1
        $var = UTF8::html_entity_decode($var);
716 1
      }
717
718 31
      $var = get_magic_quotes_gpc() ? stripslashes($var) : $var;
719
720 31
      $var = \mysqli_real_escape_string($this->getLink(), $var);
721
722 31
      return (string)$var;
723
724
    }
725
726 3
    if ($var instanceof \DateTime) {
727
728
      // "DateTime"-object
729
730
      try {
731 3
        return $this->escape($var->format('Y-m-d H:i:s'), false);
732
      } catch (\Exception $e) {
733
        return null;
734
      }
735
736
    } else {
737 2
      return false;
738
    }
739
  }
740
741
  /**
742
   * Get the mysqli-link (link identifier returned by mysqli-connect).
743
   *
744
   * @return \mysqli
745
   */
746 33
  public function getLink()
747
  {
748 33
    return $this->link;
749
  }
750
751
  /**
752
   * Returns the auto generated id used in the last query.
753
   *
754
   * @return int|string
755
   */
756 20
  public function insert_id()
757
  {
758 20
    return \mysqli_insert_id($this->link);
759
  }
760
761
  /**
762
   * Gets the number of affected rows in a previous MySQL operation.
763
   *
764
   * @return int
765
   */
766 7
  public function affected_rows()
767
  {
768 7
    return \mysqli_affected_rows($this->link);
769
  }
770
771
  /**
772
   * Error-handling for the sql-query.
773
   *
774
   * @param string     $errorMsg
775
   * @param string     $sql
776
   * @param array|bool $sqlParams false if there wasn't any parameter
777
   *
778
   * @throws \Exception
779
   *
780
   * @return bool
781
   */
782 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...
783
  {
784 9
    if ($errorMsg === 'DB server has gone away' || $errorMsg === 'MySQL server has gone away') {
785 1
      static $RECONNECT_COUNTER;
786
787
      // exit if we have more then 3 "DB server has gone away"-errors
788 1
      if ($RECONNECT_COUNTER > 3) {
789
        $this->_debug->mailToAdmin('SQL-Fatal-Error', $errorMsg . ":\n<br />" . $sql, 5);
790
        throw new \Exception($errorMsg);
791
      }
792
793 1
      $this->_debug->mailToAdmin('SQL-Error', $errorMsg . ":\n<br />" . $sql);
794
795
      // reconnect
796 1
      $RECONNECT_COUNTER++;
797 1
      $this->reconnect(true);
798
799
      // re-run the current query
800 1
      return $this->query($sql, $sqlParams);
801
    }
802
803 8
    $this->_debug->mailToAdmin('SQL-Warning', $errorMsg . ":\n<br />" . $sql);
804
805
    // this query returned an error, we must display it (only for dev) !!!
806 8
    $this->_debug->displayError($errorMsg . ' | ' . $sql);
807
808 8
    return false;
809
  }
810
811
  /**
812
   * Reconnect to the MySQL-Server.
813
   *
814
   * @param bool $checkViaPing
815
   *
816
   * @return bool
817
   */
818 3
  public function reconnect($checkViaPing = false)
819
  {
820 3
    $ping = false;
821
822 3
    if ($checkViaPing === true) {
823 2
      $ping = $this->ping();
824 2
    }
825
826 3
    if ($ping !== true) {
827 3
      $this->connected = false;
828 3
      $this->connect();
829 3
    }
830
831 3
    return $this->isReady();
832
  }
833
834
  /**
835
   * Pings a server connection, or tries to reconnect
836
   * if the connection has gone down.
837
   *
838
   * @return boolean
839
   */
840 3
  public function ping()
841
  {
842
    if (
843 3
        $this->link
844 3
        &&
845 3
        $this->link instanceof \mysqli
846 3
    ) {
847
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
848 3
      return @\mysqli_ping($this->link);
849
    }
850
851
    return false;
852
  }
853
854
  /**
855
   * Execute select/insert/update/delete sql-queries.
856
   *
857
   * @param string $query    sql-query
858
   * @param bool   $useCache use cache?
859
   * @param int    $cacheTTL cache-ttl in seconds
860
   *
861
   * @return mixed "array" by "<b>SELECT</b>"-queries<br />
862
   *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
863
   *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
864
   *               "true" by e.g. "DROP"-queries<br />
865
   *               "false" on error
866
   */
867 3
  public static function execSQL($query, $useCache = false, $cacheTTL = 3600)
868
  {
869
    // init
870 3
    $cacheKey = null;
871 3
    $db = self::getInstance();
872
873 3
    if ($useCache === true) {
874 1
      $cache = new Cache(null, null, false, $useCache);
875 1
      $cacheKey = 'sql-' . md5($query);
876
877
      if (
878 1
          $cache->getCacheIsReady() === true
879 1
          &&
880 1
          $cache->existsItem($cacheKey)
881 1
      ) {
882 1
        return $cache->getItem($cacheKey);
883
      }
884
885 1
    } else {
886 3
      $cache = false;
887
    }
888
889 3
    $result = $db->query($query);
890
891 3
    if ($result instanceof Result) {
892
893 1
      $return = $result->fetchAllArray();
894
895
      if (
896
          $cacheKey !== null
897 1
          &&
898
          $useCache === true
899 1
          &&
900
          $cache instanceof Cache
901 1
          &&
902 1
          $cache->getCacheIsReady() === true
903 1
      ) {
904 1
        $cache->setItem($cacheKey, $return, $cacheTTL);
905 1
      }
906
907 1
    } else {
908 2
      $return = $result;
909
    }
910
911 3
    return $return;
912
  }
913
914
  /**
915
   * Get the current charset.
916
   *
917
   * @return string
918
   */
919 1
  public function get_charset()
920
  {
921 1
    return $this->charset;
922
  }
923
924
  /**
925
   * Enables or disables internal report functions
926
   *
927
   * @link http://php.net/manual/en/function.mysqli-report.php
928
   *
929
   * @param int $flags <p>
930
   *                   <table>
931
   *                   Supported flags
932
   *                   <tr valign="top">
933
   *                   <td>Name</td>
934
   *                   <td>Description</td>
935
   *                   </tr>
936
   *                   <tr valign="top">
937
   *                   <td><b>MYSQLI_REPORT_OFF</b></td>
938
   *                   <td>Turns reporting off</td>
939
   *                   </tr>
940
   *                   <tr valign="top">
941
   *                   <td><b>MYSQLI_REPORT_ERROR</b></td>
942
   *                   <td>Report errors from mysqli function calls</td>
943
   *                   </tr>
944
   *                   <tr valign="top">
945
   *                   <td><b>MYSQLI_REPORT_STRICT</b></td>
946
   *                   <td>
947
   *                   Throw <b>mysqli_sql_exception</b> for errors
948
   *                   instead of warnings
949
   *                   </td>
950
   *                   </tr>
951
   *                   <tr valign="top">
952
   *                   <td><b>MYSQLI_REPORT_INDEX</b></td>
953
   *                   <td>Report if no index or bad index was used in a query</td>
954
   *                   </tr>
955
   *                   <tr valign="top">
956
   *                   <td><b>MYSQLI_REPORT_ALL</b></td>
957
   *                   <td>Set all options (report all)</td>
958
   *                   </tr>
959
   *                   </table>
960
   *                   </p>
961
   *
962
   * @return bool
963
   */
964
  public function set_mysqli_report($flags)
965
  {
966
    return \mysqli_report($flags);
967
  }
968
969
  /**
970
   * Set the current charset.
971
   *
972
   * @param string $charset
973
   *
974
   * @return bool
975
   */
976 7
  public function set_charset($charset)
977
  {
978 7
    $charsetLower = strtolower($charset);
979 7
    if ($charsetLower === 'utf8' || $charsetLower === 'utf-8') {
980 7
      $charset = 'utf8';
981 7
    }
982 7
    if ($charset === 'utf8' && Helper::isUtf8mb4Supported($this) === true) {
983
      $charset = 'utf8mb4';
984
    }
985
986 7
    $this->charset = (string)$charset;
987
988 7
    $return = mysqli_set_charset($this->link, $charset);
989
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
990 7
    @\mysqli_query($this->link, 'SET CHARACTER SET ' . $charset);
991
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
992 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...
993
994 7
    return $return;
995
  }
996
997
  /**
998
   * Set the option to convert null to "''" (empty string).
999
   *
1000
   * Used in secure() => select(), insert(), update(), delete()
1001
   *
1002
   * @param $bool
1003
   */
1004 1
  public function set_convert_null_to_empty_string($bool)
1005
  {
1006 1
    $this->_convert_null_to_empty_string = (bool)$bool;
1007 1
  }
1008
1009
  /**
1010
   * Get all table-names via "SHOW TABLES".
1011
   *
1012
   * @return array
1013
   */
1014 1
  public function getAllTables()
1015
  {
1016 1
    $query = 'SHOW TABLES';
1017 1
    $result = $this->query($query);
1018
1019 1
    return $result->fetchAllArray();
1020
  }
1021
1022
  /**
1023
   * Execute a sql-multi-query.
1024
   *
1025
   * @param string $sql
1026
   *
1027
   * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
1028
   *                        "boolean" by only "<b>INSERT</b>"-queries<br />
1029
   *                        "boolean" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1030
   *                        "boolean" by only by e.g. "DROP"-queries<br />
1031
   *
1032
   * @throws \Exception
1033
   */
1034 1
  public function multi_query($sql)
1035
  {
1036 1
    if (!$this->isReady()) {
1037
      return false;
1038
    }
1039
1040 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...
1041 1
      $this->_debug->displayError('Can\'t execute an empty Query', false);
1042
1043 1
      return false;
1044
    }
1045
1046 1
    $query_start_time = microtime(true);
1047 1
    $resultTmp = \mysqli_multi_query($this->link, $sql);
1048 1
    $query_duration = microtime(true) - $query_start_time;
1049
1050 1
    $this->_debug->logQuery($sql, $query_duration, 0);
1051
1052 1
    $returnTheResult = false;
1053 1
    $result = array();
1054 1
    if ($resultTmp) {
1055
      do {
1056 1
        $resultTmpInner = \mysqli_store_result($this->link);
1057
1058 1
        if ($resultTmpInner instanceof \mysqli_result) {
1059 1
          $returnTheResult = true;
1060 1
          $result[] = new Result($sql, $resultTmpInner);
1061 1
        } else {
1062 1
          $errorMsg = \mysqli_error($this->link);
1063
1064
          // is the query successful
1065 1
          if ($resultTmpInner === true || !$errorMsg) {
1066 1
            $result[] = true;
1067 1
          } else {
1068
            $result[] = $this->queryErrorHandling($errorMsg, $sql);
1069
          }
1070
        }
1071 1
      } while (\mysqli_more_results($this->link) === true ? \mysqli_next_result($this->link) : false);
1072
1073 1
    } else {
1074
1075
      $errorMsg = \mysqli_error($this->link);
1076
1077
      if ($this->_debug->checkForDev() === true) {
1078
        echo "Info: maybe you have to increase your 'max_allowed_packet = 30M' in the config: 'my.conf' \n<br />";
1079
        echo 'Error:' . $errorMsg;
1080
      }
1081
1082
      $this->_debug->mailToAdmin('SQL-Error in mysqli_multi_query', $errorMsg . ":\n<br />" . $sql);
1083
    }
1084
1085
    // return the result only if there was a "SELECT"-query
1086 1
    if ($returnTheResult === true) {
1087 1
      return $result;
1088
    }
1089
1090 1
    if (in_array(false, $result, true) === false) {
1091 1
      return true;
1092
    }
1093
1094
    return false;
1095
  }
1096
1097
  /**
1098
   * alias: "beginTransaction()"
1099
   */
1100 1
  public function startTransaction()
1101
  {
1102 1
    $this->beginTransaction();
1103 1
  }
1104
1105
  /**
1106
   * Begins a transaction, by turning off auto commit.
1107
   *
1108
   * @return boolean this will return true or false indicating success of transaction
1109
   */
1110 4
  public function beginTransaction()
1111
  {
1112 4
    $this->clearErrors();
1113
1114 4
    if ($this->inTransaction() === true) {
1115 1
      $this->_debug->displayError('Error mysql server already in transaction!', true);
1116
1117
      return false;
1118
    }
1119
1120 4
    if (\mysqli_connect_errno()) {
1121
      $this->_debug->displayError('Error connecting to mysql server: ' . \mysqli_connect_error(), true);
1122
1123
      return false;
1124
    }
1125
1126 4
    $this->_in_transaction = true;
1127 4
    \mysqli_autocommit($this->link, false);
1128
1129 4
    return true;
1130
  }
1131
1132
  /**
1133
   * Clear the errors in "_debug->_errors".
1134
   *
1135
   * @return bool
1136
   */
1137 4
  public function clearErrors()
1138
  {
1139 4
    return $this->_debug->clearErrors();
1140
  }
1141
1142
  /**
1143
   * Check if we are in a transaction.
1144
   *
1145
   * @return boolean
1146
   */
1147 4
  public function inTransaction()
1148
  {
1149 4
    return $this->_in_transaction;
1150
  }
1151
1152
  /**
1153
   * Ends a transaction and commits if no errors, then ends autocommit.
1154
   *
1155
   * @return boolean this will return true or false indicating success of transactions
1156
   */
1157 2
  public function endTransaction()
1158
  {
1159
1160 2
    if (!$this->errors()) {
1161 1
      \mysqli_commit($this->link);
1162 1
      $return = true;
1163 1
    } else {
1164 1
      $this->rollback();
1165 1
      $return = false;
1166
    }
1167
1168 2
    \mysqli_autocommit($this->link, true);
1169 2
    $this->_in_transaction = false;
1170
1171 2
    return $return;
1172
  }
1173
1174
  /**
1175
   * Get all errors from "$this->_errors".
1176
   *
1177
   * @return array|false false === on errors
1178
   */
1179 2
  public function errors()
1180
  {
1181 2
    $errors = $this->_debug->getErrors();
1182
1183 2
    return count($errors) > 0 ? $errors : false;
1184
  }
1185
1186
  /**
1187
   * Rollback in a transaction.
1188
   */
1189 2
  public function rollback()
1190
  {
1191
    // init
1192 2
    $return = false;
1193
1194 2
    if ($this->_in_transaction === true) {
1195 2
      $return = \mysqli_rollback($this->link);
1196 2
      \mysqli_autocommit($this->link, true);
1197 2
      $this->_in_transaction = false;
1198 2
    }
1199
1200 2
    return $return;
1201
  }
1202
1203
  /**
1204
   * Execute a "insert"-query.
1205
   *
1206
   * @param string      $table
1207
   * @param array       $data
1208
   * @param string|null $databaseName <p>use <strong>null</strong> if you will use the current database</p>
1209
   *
1210
   * @return false|int false on error
1211
   */
1212 19
  public function insert($table, array $data = array(), $databaseName = null)
1213
  {
1214
    // init
1215 19
    $table = trim($table);
1216
1217 19
    if ($table === '') {
1218 2
      $this->_debug->displayError('invalid table name');
1219
1220 1
      return false;
1221
    }
1222
1223 18
    if (count($data) === 0) {
1224 3
      $this->_debug->displayError('empty data for INSERT');
1225
1226 2
      return false;
1227
    }
1228
1229 16
    $SET = $this->_parseArrayPair($data);
1230
1231 16
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1232
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1233
    }
1234
1235 16
    $sql = 'INSERT INTO ' . $databaseName . $this->quote_string($table) . " SET $SET;";
1236
1237 16
    return $this->query($sql);
1238
  }
1239
1240
  /**
1241
   * Parses arrays with value pairs and generates SQL to use in queries.
1242
   *
1243
   * @param array  $arrayPair
1244
   * @param string $glue this is the separator
1245
   *
1246
   * @return string
1247
   */
1248 21
  private function _parseArrayPair($arrayPair, $glue = ',')
1249
  {
1250
    // init
1251 21
    $sql = '';
1252
1253
    /** @noinspection IsEmptyFunctionUsageInspection */
1254 21
    if (!empty($arrayPair)) {
1255
1256 21
      $arrayPairCounter = 0;
1257 21
      foreach ($arrayPair as $_key => $_value) {
1258 21
        $_connector = '=';
1259 21
        $_glueHelper = '';
1260 21
        $_key_upper = strtoupper($_key);
1261
1262 21
        if (strpos($_key_upper, ' NOT') !== false) {
1263 2
          $_connector = 'NOT';
1264 2
        }
1265
1266 21
        if (strpos($_key_upper, ' IS') !== false) {
1267 1
          $_connector = 'IS';
1268 1
        }
1269
1270 21
        if (strpos($_key_upper, ' IS NOT') !== false) {
1271 1
          $_connector = 'IS NOT';
1272 1
        }
1273
1274 21
        if (strpos($_key_upper, ' IN') !== false) {
1275 1
          $_connector = 'IN';
1276 1
        }
1277
1278 21
        if (strpos($_key_upper, ' NOT IN') !== false) {
1279 1
          $_connector = 'NOT IN';
1280 1
        }
1281
1282 21
        if (strpos($_key_upper, ' BETWEEN') !== false) {
1283 1
          $_connector = 'BETWEEN';
1284 1
        }
1285
1286 21
        if (strpos($_key_upper, ' NOT BETWEEN') !== false) {
1287 1
          $_connector = 'NOT BETWEEN';
1288 1
        }
1289
1290 21
        if (strpos($_key_upper, ' LIKE') !== false) {
1291 2
          $_connector = 'LIKE';
1292 2
        }
1293
1294 21
        if (strpos($_key_upper, ' NOT LIKE') !== false) {
1295 2
          $_connector = 'NOT LIKE';
1296 2
        }
1297
1298 21 View Code Duplication
        if (strpos($_key_upper, ' >') !== false && strpos($_key_upper, ' =') === false) {
1299 2
          $_connector = '>';
1300 2
        }
1301
1302 21 View Code Duplication
        if (strpos($_key_upper, ' <') !== false && strpos($_key_upper, ' =') === false) {
1303 1
          $_connector = '<';
1304 1
        }
1305
1306 21
        if (strpos($_key_upper, ' >=') !== false) {
1307 2
          $_connector = '>=';
1308 2
        }
1309
1310 21
        if (strpos($_key_upper, ' <=') !== false) {
1311 1
          $_connector = '<=';
1312 1
        }
1313
1314 21
        if (strpos($_key_upper, ' <>') !== false) {
1315 1
          $_connector = '<>';
1316 1
        }
1317
1318 21
        if (strpos($_key_upper, ' OR') !== false) {
1319 2
          $_glueHelper = 'OR';
1320 2
        }
1321
1322 21
        if (strpos($_key_upper, ' AND') !== false) {
1323 1
          $_glueHelper = 'AND';
1324 1
        }
1325
1326 21
        if (is_array($_value) === true) {
1327 2
          foreach ($_value as $oldKey => $oldValue) {
1328 2
            $_value[$oldKey] = $this->secure($oldValue);
1329 2
          }
1330
1331 2
          if ($_connector === 'NOT IN' || $_connector === 'IN') {
1332 1
            $_value = '(' . implode(',', $_value) . ')';
1333 2
          } elseif ($_connector === 'NOT BETWEEN' || $_connector === 'BETWEEN') {
1334 1
            $_value = '(' . implode(' AND ', $_value) . ')';
1335 1
          }
1336
1337 2
        } else {
1338 21
          $_value = $this->secure($_value);
1339
        }
1340
1341 21
        $quoteString = $this->quote_string(
1342 21
            trim(
1343 21
                str_ireplace(
1344
                    array(
1345 21
                        $_connector,
1346 21
                        $_glueHelper,
1347 21
                    ),
1348 21
                    '',
1349
                    $_key
1350 21
                )
1351 21
            )
1352 21
        );
1353
1354 21
        if (!is_array($_value)) {
1355 21
          $_value = array($_value);
1356 21
        }
1357
1358 21
        if (!$_glueHelper) {
1359 21
          $_glueHelper = $glue;
1360 21
        }
1361
1362 21
        $tmpCounter = 0;
1363 21
        foreach ($_value as $valueInner) {
1364
1365 21
          $_glueHelperInner = $_glueHelper;
1366
1367 21
          if ($arrayPairCounter === 0) {
1368
1369 21
            if ($tmpCounter === 0 && $_glueHelper === 'OR') {
1370 1
              $_glueHelperInner = '1 = 1 AND ('; // first "OR"-query glue
1371 21
            } elseif ($tmpCounter === 0) {
1372 21
              $_glueHelperInner = ''; // first query glue e.g. for "INSERT"-query -> skip the first ","
1373 21
            }
1374
1375 21
          } elseif ($tmpCounter === 0 && $_glueHelper === 'OR') {
1376 1
            $_glueHelperInner = 'AND ('; // inner-loop "OR"-query glue
1377 1
          }
1378
1379 21
          $sql .= ' ' . $_glueHelperInner . ' ' . $quoteString . ' ' . $_connector . ' ' . $valueInner . " \n";
1380 21
          $tmpCounter++;
1381 21
        }
1382
1383 21
        if ($_glueHelper === 'OR') {
1384 2
          $sql .= ' ) ';
1385 2
        }
1386
1387 21
        $arrayPairCounter++;
1388 21
      }
1389 21
    }
1390
1391 21
    return $sql;
1392
  }
1393
1394
  /**
1395
   * Quote && Escape e.g. a table name string.
1396
   *
1397
   * @param string $str
1398
   *
1399
   * @return string
1400
   */
1401 24
  public function quote_string($str)
1402
  {
1403 24
    $str = str_replace(
1404 24
        '`',
1405 24
        '``',
1406 24
        trim(
1407 24
            $this->escape($str, false),
1408
            '`'
1409 24
        )
1410 24
    );
1411
1412 24
    return '`' . $str . '`';
1413
  }
1414
1415
  /**
1416
   * Get errors from "$this->_errors".
1417
   *
1418
   * @return array
1419
   */
1420 1
  public function getErrors()
1421
  {
1422 1
    return $this->_debug->getErrors();
1423
  }
1424
1425
  /**
1426
   * Execute a "replace"-query.
1427
   *
1428
   * @param string      $table
1429
   * @param array       $data
1430
   * @param null|string $databaseName <p>use <strong>null</strong> if you will use the current database</p>
1431
   *
1432
   * @return false|int false on error
1433
   */
1434 1
  public function replace($table, array $data = array(), $databaseName = null)
1435
  {
1436
    // init
1437 1
    $table = trim($table);
1438
1439 1
    if ($table === '') {
1440 1
      $this->_debug->displayError('invalid table name');
1441
1442 1
      return false;
1443
    }
1444
1445 1
    if (count($data) === 0) {
1446 1
      $this->_debug->displayError('empty data for REPLACE');
1447
1448 1
      return false;
1449
    }
1450
1451
    // extracting column names
1452 1
    $columns = array_keys($data);
1453 1
    foreach ($columns as $k => $_key) {
1454
      /** @noinspection AlterInForeachInspection */
1455 1
      $columns[$k] = $this->quote_string($_key);
1456 1
    }
1457
1458 1
    $columns = implode(',', $columns);
1459
1460
    // extracting values
1461 1
    foreach ($data as $k => $_value) {
1462
      /** @noinspection AlterInForeachInspection */
1463 1
      $data[$k] = $this->secure($_value);
1464 1
    }
1465 1
    $values = implode(',', $data);
1466
1467 1
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1468
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1469
    }
1470
1471 1
    $sql = 'REPLACE INTO ' . $databaseName . $this->quote_string($table) . " ($columns) VALUES ($values);";
1472
1473 1
    return $this->query($sql);
1474
  }
1475
1476
  /**
1477
   * Execute a "update"-query.
1478
   *
1479
   * @param string       $table
1480
   * @param array        $data
1481
   * @param array|string $where
1482
   * @param null|string  $databaseName <p>use <strong>null</strong> if you will use the current database</p>
1483
   *
1484
   * @return false|int false on error
1485
   */
1486 6
  public function update($table, array $data = array(), $where = '1=1', $databaseName = null)
1487
  {
1488
    // init
1489 6
    $table = trim($table);
1490
1491 6
    if ($table === '') {
1492 1
      $this->_debug->displayError('invalid table name');
1493
1494 1
      return false;
1495
    }
1496
1497 6
    if (count($data) === 0) {
1498 2
      $this->_debug->displayError('empty data for UPDATE');
1499
1500 2
      return false;
1501
    }
1502
1503 6
    $SET = $this->_parseArrayPair($data);
1504
1505 6
    if (is_string($where)) {
1506 2
      $WHERE = $this->escape($where, false);
1507 6
    } elseif (is_array($where)) {
1508 4
      $WHERE = $this->_parseArrayPair($where, 'AND');
1509 4
    } else {
1510 1
      $WHERE = '';
1511
    }
1512
1513 6
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1514
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1515
    }
1516
1517 6
    $sql = 'UPDATE ' . $databaseName . $this->quote_string($table) . " SET $SET WHERE ($WHERE);";
1518
1519 6
    return $this->query($sql);
1520
  }
1521
1522
  /**
1523
   * Execute a "delete"-query.
1524
   *
1525
   * @param string       $table
1526
   * @param string|array $where
1527
   * @param string|null  $databaseName <p>use <strong>null</strong> if you will use the current database</p>
1528
   *
1529
   * @return false|int false on error
1530
   */
1531 1
  public function delete($table, $where, $databaseName = null)
1532
  {
1533
    // init
1534 1
    $table = trim($table);
1535
1536 1
    if ($table === '') {
1537 1
      $this->_debug->displayError('invalid table name');
1538
1539 1
      return false;
1540
    }
1541
1542 1
    if (is_string($where)) {
1543 1
      $WHERE = $this->escape($where, false);
1544 1
    } elseif (is_array($where)) {
1545 1
      $WHERE = $this->_parseArrayPair($where, 'AND');
1546 1
    } else {
1547 1
      $WHERE = '';
1548
    }
1549
1550 1
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1551
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1552
    }
1553
1554 1
    $sql = 'DELETE FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE);";
1555
1556 1
    return $this->query($sql);
1557
  }
1558
1559
  /**
1560
   * Execute a "select"-query.
1561
   *
1562
   * @param string       $table
1563
   * @param string|array $where
1564
   * @param string|null  $databaseName <p>use <strong>null</strong> if you will use the current database</p>
1565
   *
1566
   * @return false|Result false on error
1567
   */
1568 20
  public function select($table, $where = '1=1', $databaseName = null)
1569
  {
1570
    // init
1571 20
    $table = trim($table);
1572
1573 20
    if ($table === '') {
1574 1
      $this->_debug->displayError('invalid table name');
1575
1576 1
      return false;
1577
    }
1578
1579 20
    if (is_string($where)) {
1580 5
      $WHERE = $this->escape($where, false);
1581 20
    } elseif (is_array($where)) {
1582 16
      $WHERE = $this->_parseArrayPair($where, 'AND');
1583 16
    } else {
1584 1
      $WHERE = '';
1585
    }
1586
1587 20
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1588
      $databaseName = $this->quote_string(trim($databaseName)) . '.';
1589
    }
1590
1591 20
    $sql = 'SELECT * FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE);";
1592
1593 20
    return $this->query($sql);
1594
  }
1595
1596
  /**
1597
   * Get the last sql-error.
1598
   *
1599
   * @return string false on error
1600
   */
1601 1
  public function lastError()
1602
  {
1603 1
    $errors = $this->_debug->getErrors();
1604
1605 1
    return count($errors) > 0 ? end($errors) : false;
1606
  }
1607
1608
  /**
1609
   * @return Debug
1610
   */
1611 9
  public function getDebugger()
1612
  {
1613 9
    return $this->_debug;
1614
  }
1615
1616
  /**
1617
   * __destruct
1618
   *
1619
   */
1620
  public function __destruct()
1621
  {
1622
    // close the connection only if we don't save PHP-SESSION's in DB
1623
    if ($this->session_to_db === false) {
1624
      $this->close();
1625
    }
1626
  }
1627
1628
  /**
1629
   * Closes a previously opened database connection.
1630
   */
1631 2
  public function close()
1632
  {
1633 2
    $this->connected = false;
1634
1635 2
    if ($this->link) {
1636 2
      \mysqli_close($this->link);
1637 2
    }
1638 2
  }
1639
1640
  /**
1641
   * Prevent the instance from being cloned.
1642
   *
1643
   * @return void
1644
   */
1645
  private function __clone()
1646
  {
1647
  }
1648
1649
  /**
1650
   * __wakeup
1651
   *
1652
   * @return void
1653
   */
1654 2
  public function __wakeup()
1655
  {
1656 2
    $this->reconnect();
1657 2
  }
1658
1659
}
1660