Completed
Pull Request — master (#23)
by Lars
14:40 queued 01:07
created

DB::execSQL()   C

Complexity

Conditions 9
Paths 7

Size

Total Lines 47
Code Lines 28

Duplication

Lines 26
Ratio 55.32 %

Code Coverage

Tests 26
CRAP Score 9

Importance

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