Completed
Push — work-fleets ( 41da5d...c94fa6 )
by SuperNova.WS
06:35
created

db_mysql::db_insert_id()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
1
<?php
2
3
define('DB_INSERT_PLAIN', 0);
4
define('DB_INSERT_REPLACE', 1);
5
define('DB_INSERT_IGNORE', 2);
6
7
define('DB_RECORDS_ALL', false);
8
define('DB_RECORD_ONE', true);
9
10
/**
11
 * Created by Gorlum 01.09.2015 15:58
12
 */
13
class db_mysql {
14
  const TRANSACTION_SERIALIZABLE = 'SERIALIZABLE';
15
  const TRANSACTION_REPEATABLE_READ = 'REPEATABLE READ';
16
  const TRANSACTION_READ_COMMITTED = 'READ COMMITTED';
17
  const TRANSACTION_READ_UNCOMMITTED = 'READ UNCOMMITTED';
18
19
  /**
20
   * Статус соеднения с MySQL
21
   *
22
   * @var bool
23
   */
24
  public $connected = false;
25
  /**
26
   * Префикс названий таблиц в БД
27
   *
28
   * @var string
29
   */
30
  public $db_prefix = '';
31
  /**
32
   * Список таблиц в БД
33
   *
34
   * @var array
35
   */
36
  public $table_list = array();
37
38
  /**
39
   * Настройки БД
40
   *
41
   * @var array
42
   */
43
  protected $dbsettings = array();
44
  /**
45
   * Драйвер для прямого обращения к MySQL
46
   *
47
   * @var db_mysql_v5 $driver
48
   */
49
  public $driver = null;
50
51
  /**
52
   * Общее время запросов
53
   *
54
   * @var float $time_mysql_total
55
   */
56
  public $time_mysql_total = 0.0;
57
58
  /**
59
   * Amount of queries on this DB
60
   *
61
   * @var int
62
   */
63
  public $queryCount = 0;
64
65
  public $isWatching = false;
66
67
  /**
68
   * @var \DBAL\DbTransaction $transaction
69
   */
70
  protected $transaction;
71
72
  /**
73
   * Should query check be skipped?
74
   *
75
   * Used for altering scheme of DB
76
   *
77
   * @var bool $skipQueryCheck
78
   */
79
  protected $skipQueryCheck = false;
80
81
  /**
82
   * @var SnCache $snCache
83
   */
84
  public $snCache;
85
86
  /**
87
   * db_mysql constructor.
88
   *
89
   * @param \Common\GlobalContainer $gc
90
   */
91
  public function __construct($gc) {
92
    $this->transaction = new \DBAL\DbTransaction($gc, $this);
93
    $this->snCache = new $gc->snCacheClass($gc, $this);
94
  }
95
96
  public function load_db_settings($configFile = '') {
97
    $dbsettings = array();
98
99
    empty($configFile) ? $configFile = SN_ROOT_PHYSICAL . "config" . DOT_PHP_EX : false;
100
101
    require $configFile;
102
103
    $this->dbsettings = $dbsettings;
104
  }
105
106
  /**
107
   * @param null|array $external_db_settings
108
   *
109
   * @return bool
110
   */
111
  public function sn_db_connect($external_db_settings = null) {
112
    $this->db_disconnect();
113
114
    if (!empty($external_db_settings) && is_array($external_db_settings)) {
115
      $this->dbsettings = $external_db_settings;
116
    }
117
118
    if (empty($this->dbsettings)) {
119
      $this->load_db_settings(SN_ROOT_PHYSICAL . "config" . DOT_PHP_EX);
120
    }
121
122
    // TODO - фатальные (?) ошибки на каждом шагу. Хотя - скорее Эксепшны
123
    if (!empty($this->dbsettings)) {
124
      $driver_name = empty($this->dbsettings['sn_driver']) ? 'db_mysql_v5' : $this->dbsettings['sn_driver'];
125
      $this->driver = new $driver_name();
126
      $this->db_prefix = $this->dbsettings['prefix'];
127
128
      $this->connected = $this->connected || $this->driver_connect();
129
130
      if ($this->connected) {
131
        $this->table_list = $this->db_get_table_list();
132
        // TODO Проверка на пустоту
133
      }
134
    } else {
135
      $this->connected = false;
136
    }
137
138
    return $this->connected;
139
  }
140
141
  protected function driver_connect() {
142
    if (!is_object($this->driver)) {
143
      classSupernova::$debug->error_fatal('DB Error - No driver for MySQL found!');
144
    }
145
146
    if (!method_exists($this->driver, 'mysql_connect')) {
147
      classSupernova::$debug->error_fatal('DB Error - WRONG MySQL driver!');
148
    }
149
150
    return $this->driver->mysql_connect($this->dbsettings);
151
  }
152
153
  public function db_disconnect() {
154
    if ($this->connected) {
155
      $this->connected = !$this->driver_disconnect();
156
      $this->connected = false;
157
    }
158
159
    return !$this->connected;
160
  }
161
162
  /**
163
   * @param string $query
164
   *
165
   * @return mixed|string
166
   */
167
  public function replaceTablePlaceholders($query) {
168
    $sql = $query;
169
    if (strpos($sql, '{{') !== false) {
170
      foreach ($this->table_list as $tableName) {
171
        $sql = str_replace("{{{$tableName}}}", $this->db_prefix . $tableName, $sql);
172
      }
173
    }
174
175
    return $sql;
176
  }
177
178
  /**
179
   * @param $query
180
   */
181
  protected function logQuery($query) {
182
    if (!classSupernova::$config->debug) {
183
      return;
184
    }
185
186
    $this->queryCount++;
187
    $arr = debug_backtrace();
188
    $file = end(explode('/', $arr[0]['file']));
0 ignored issues
show
Bug introduced by
explode('/', $arr[0]['file']) cannot be passed to end() as the parameter $array expects a reference.
Loading history...
189
    $line = $arr[0]['line'];
190
    classSupernova::$debug->add("<tr><th>Query {$this->queryCount}: </th><th>$query</th><th>{$file} @ {$line}</th><th>&nbsp;</th></tr>");
191
  }
192
193
194
  /**
195
   * @return string
196
   */
197
  public function queryTrace() {
198
    if (!defined('DEBUG_SQL_COMMENT') || constant('DEBUG_SQL_ERROR') !== true) {
199
      return '';
200
    }
201
202
    $backtrace = debug_backtrace();
203
    $sql_comment = classSupernova::$debug->compact_backtrace($backtrace, defined('DEBUG_SQL_COMMENT_LONG'));
204
205
    if (defined('DEBUG_SQL_ERROR') && constant('DEBUG_SQL_ERROR') === true) {
206
      classSupernova::$debug->add_to_array($sql_comment);
207
    }
208
209
    $sql_commented = implode("\r\n", $sql_comment);
210
    if (defined('DEBUG_SQL_ONLINE') && constant('DEBUG_SQL_ONLINE') === true) {
211
      classSupernova::$debug->warning($sql_commented, 'SQL Debug', LOG_DEBUG_SQL);
212
    }
213
214
    return $sql_commented;
215
  }
216
217
  /**
218
   * @param string $query
219
   *
220
   * @return array|bool|mysqli_result|null
221
   * @internal param bool $skip_query_check
222
   *
223
   */
224
  protected function doquery($query) {
225
    if (!$this->connected) {
226
      $this->sn_db_connect();
227
    }
228
229
    $stringQuery = $query;
230
    $stringQuery = trim($stringQuery);
231
    // You can't do it - 'cause you can break commented statement with line-end comments
232
    // $stringQuery = preg_replace("/\s+/", ' ', $stringQuery);
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% 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...
233
234
    $this->security_watch_user_queries($stringQuery);
235
    $this->security_query_check_bad_words($stringQuery);
236
    $this->logQuery($stringQuery);
237
238
    $stringQuery = $this->replaceTablePlaceholders($stringQuery);
239
240
    $queryTrace = $this->queryTrace();
241
242
    $queryResult = null;
243
    try {
244
      $queryResult = $this->db_sql_query($stringQuery . DbSqlHelper::quoteComment($queryTrace));
245
      if (!$queryResult) {
246
        throw new Exception();
247
      }
248
    } catch (Exception $e) {
249
      classSupernova::$debug->error($this->db_error() . "<br />{$query}<br />", 'SQL Error');
250
    }
251
252
    return $queryResult;
253
  }
254
255
256
  // Just wrappers to distinguish query types
257
  /**
258
   * Executes non-data manipulation statements
259
   *
260
   * Can execute queries with check skip
261
   * Honor current state of query checking
262
   *
263
   * @param string $query
264
   * @param bool   $skip_query_check
265
   *
266
   * @return array|bool|mysqli_result|null
267
   */
268
  public function doExecute($query, $skip_query_check = false) {
269
    $prevState = false;
270
    if ($skip_query_check) {
271
      $prevState = $this->skipQueryCheck;
272
      $this->skipQueryCheck = true;
273
    }
274
    // TODO - disable watch ??
275
    $result = $this->doquery($query);
276
    if ($skip_query_check) {
277
      $this->skipQueryCheck = $prevState;
278
    }
279
280
    return $result;
281
  }
282
283
284
  public function doSelect($query) {
285
    return $this->doExecute($query);
286
  }
287
288
  /**
289
   * @param string $query
290
   *
291
   * @return array|null
292
   */
293
  public function doSelectFetch($query) {
294
    return $this->db_fetch($this->doSelect($query));
0 ignored issues
show
Bug introduced by
$this->doSelect($query) cannot be passed to db_fetch() as the parameter $query expects a reference.
Loading history...
Bug introduced by
It seems like $this->doSelect($query) targeting db_mysql::doSelect() can also be of type boolean; however, db_mysql::db_fetch() does only seem to accept object<mysqli_result>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
295
  }
296
297
  /**
298
   * @param string $query
299
   *
300
   * @return mixed|null
301
   */
302
  public function doSelectFetchValue($query) {
303
    $row = $this->doSelectFetch($query);
304
305
    return is_array($row) ? reset($row) : null;
306
  }
307
308
309
  public function doInsertComplex($query) {
310
    return $this->doExecute($query);
311
  }
312
313
  protected function doSet($table, $fieldsAndValues, $replace = DB_INSERT_PLAIN) {
314
    $tableSafe = $this->db_escape($table);
315
    $safeFieldsAndValues = implode(',', $this->safeFieldsEqualValues($fieldsAndValues));
316
//    $command = $replace == DB_INSERT_REPLACE ? 'REPLACE' : 'INSERT';
317
//    $command .= $replace == DB_INSERT_IGNORE ? ' IGNORE' : '';
318
    switch($replace) {
319
      case DB_INSERT_IGNORE:
320
        $command = 'INSERT IGNORE';
321
      break;
322
      case DB_INSERT_REPLACE:
323
        $command = 'REPLACE';
324
      break;
325
      default:
326
        $command = 'INSERT';
327
      break;
328
    }
329
    $query = "{$command} INTO `{{{$tableSafe}}}` SET {$safeFieldsAndValues}";
330
331
    return $this->doExecute($query);
332
  }
333
334
  /**
335
   * @param string $table
336
   * @param array  $fieldsAndValues
337
   * @param int    $replace - DB_INSERT_PLAIN || DB_INSERT_IGNORE
338
   *
339
   * @return array|bool|mysqli_result|null
340
   */
341
  public function doInsertSet($table, $fieldsAndValues, $replace = DB_INSERT_PLAIN) {
342
    return $this->doSet($table, $fieldsAndValues, $replace);
343
  }
344
345
  public function doReplaceSet($table, $fieldsAndValues) {
346
    return $this->doSet($table, $fieldsAndValues, DB_INSERT_REPLACE);
347
  }
348
349
  /**
350
   * Values should be passed as-is
351
   *
352
   * @param string   $table
353
   * @param array    $fields
354
   * @param string[] $values
355
   * @param bool     $replace
356
   *
357
   * @return array|bool|mysqli_result|null
358
   * @deprecated
359
   */
360
  protected function doValuesDeprecated($table, $fields, &$values, $replace = DB_INSERT_PLAIN) {
361
    $tableSafe = $this->db_escape($table);
362
    $safeFields = implode(',', $this->safeFields($fields));
363
    $safeValues = implode(',', $values);
364
    $command = $replace == DB_INSERT_REPLACE ? 'REPLACE' : 'INSERT';
365
    $query = "{$command} INTO `{{{$tableSafe}}}` ({$safeFields}) VALUES {$safeValues}";
366
367
    return $this->doExecute($query);
368
  }
369
370
  // TODO - batch insert and replace here
371
372
  // TODO - перед тем, как переделывать данные из депрекейтов - убедится, что
373
  // null - это null, а не строка'NULL'
374
375
  /**
376
   * Values should be passed as-is
377
   *
378
   * @param string   $table
379
   * @param array    $fields
380
   * @param string[] $values
381
   *
382
   * @return array|bool|mysqli_result|null
383
   * @deprecated
384
   */
385
  public function doInsertValuesDeprecated($table, $fields, &$values) {
386
    return $this->doValuesDeprecated($table, $fields, $values, DB_INSERT_PLAIN);
0 ignored issues
show
Documentation introduced by
DB_INSERT_PLAIN is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Deprecated Code introduced by
The method db_mysql::doValuesDeprecated() has been deprecated.

This method has been deprecated.

Loading history...
387
  }
388
389
  /**
390
   * Values should be passed as-is
391
   *
392
   * @param string   $table
393
   * @param array    $fields
394
   * @param string[] $values
395
   *
396
   * @return array|bool|mysqli_result|null
397
   * @deprecated
398
   */
399
  public function doReplaceValuesDeprecated($table, $fields, &$values) {
400
    return $this->doValuesDeprecated($table, $fields, $values, DB_INSERT_REPLACE);
0 ignored issues
show
Documentation introduced by
DB_INSERT_REPLACE is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Deprecated Code introduced by
The method db_mysql::doValuesDeprecated() has been deprecated.

This method has been deprecated.

Loading history...
401
  }
402
403
404
  public function doUpdateComplex($query) {
405
    return $this->doExecute($query);
406
  }
407
408
  public function doUpdateReallyComplex($query) {
409
    return $this->doExecute($query);
410
  }
411
412
  /**
413
   * Executes self-contained SQL UPDATE query
414
   *
415
   * Self-contained - means no params used
416
   * Such queries usually used to make large amount of in-base calculations
417
   *
418
   * @param $query
419
   *
420
   * @return array|bool|mysqli_result|null
421
   */
422
  public function doUpdateSqlNoParam($query) {
423
    return $this->doExecute($query);
424
  }
425
426
  protected function doUpdateWhere($table, $fieldsSet, $fieldsAdjust = array(), $where = array(), $isOneRecord = DB_RECORDS_ALL) {
427
    $tableSafe = $this->db_escape($table);
428
429
    $safeFields = array();
430
    // Adjusts overwritten by Sets
431
    if($safeAdjust = implode(',', $this->safeFieldsAdjust($fieldsAdjust))) {
432
      $safeFields[] = &$safeAdjust;
433
    }
434
    if($safeFieldsEqualValues = implode(',', $this->safeFieldsEqualValues($fieldsSet))) {
435
      $safeFields[] = &$safeFieldsEqualValues;
436
    }
437
    $safeFieldsString = implode(',', $safeFields);
438
439
    // TODO - Exception of $safeFieldsString
440
441
    $safeWhereAnd = implode(' AND ', $this->safeFieldsEqualValues($where));
442
    $query = "UPDATE `{{{$tableSafe}}}` SET {$safeFieldsString}"
443
      . (!empty($safeWhereAnd) ? " WHERE {$safeWhereAnd}" : '')
444
      . ($isOneRecord == DB_RECORD_ONE ? ' LIMIT 1' : '');
445
446
    return $this->doExecute($query);
447
  }
448
449
  public function doUpdateRowSet($table, $fieldsAndValues, $where) {
450
    return $this->doUpdateWhere($table, $fieldsAndValues, array(), $where, DB_RECORD_ONE);
451
  }
452
453
  public function doUpdateTableSet($table, $fieldsAndValues, $where = array()) {
454
    return $this->doUpdateWhere($table, $fieldsAndValues, array(), $where, DB_RECORDS_ALL);
455
  }
456
457
  public function doUpdateRowAdjust($table, $fieldsSet, $fieldsAdjust, $where) {
458
    return $this->doUpdateWhere($table, $fieldsSet, $fieldsAdjust, $where, DB_RECORD_ONE);
459
  }
460
461
  public function doUpdateTableAdjust($table, $fieldsSet, $fieldsAdjust, $where) {
462
    return $this->doUpdateWhere($table, $fieldsSet, $fieldsAdjust, $where, DB_RECORDS_ALL);
463
  }
464
465
  public function doUpdateAdjustDeprecated($query) {
466
    return $this->doExecute($query);
467
  }
468
469
470
471
  /**
472
   * For update_old - would be deprecated
473
   *
474
   * @param string $query
475
   *
476
   * @return array|bool|mysqli_result|null
477
   * @deprecated
478
   */
479
  public function doUpdateOld($query) {
480
    return $this->doExecute($query);
481
  }
482
483
484
  /**
485
   * @param string $query
486
   *
487
   * @return array|bool|mysqli_result|null
488
   */
489
  public function doDelete($query) {
490
    return $this->doExecute($query);
491
  }
492
493
  /**
494
   * @param string $table
495
   * @param array  $where - simple WHERE statement list which can be combined with AND
496
   * @param bool   $isOneRecord
497
   *
498
   * @return array|bool|mysqli_result|null
499
   */
500
  public function doDeleteWhere($table, $where, $isOneRecord = false) {
501
    $tableSafe = $this->db_escape($table);
502
    $safeWhere = implode(' AND ', $this->safeFieldsEqualValues($where));
503
    $query = "DELETE FROM `{{{$tableSafe}}}` WHERE {$safeWhere}"
504
      . ($isOneRecord ? ' LIMIT 1' : '');
505
506
    return $this->doDelete($query);
507
  }
508
509
  /**
510
   * @param string $table
511
   * @param array  $where - simple WHERE statement list which can be combined with AND
512
   *
513
   * @return array|bool|mysqli_result|null
514
   */
515
  public function doDeleteRowWhere($table, $where) {
516
    return $this->doDeleteWhere($table, $where, true);
517
  }
518
519
  /**
520
   * @param string $query
521
   *
522
   * @return array|bool|mysqli_result|null
523
   */
524
  public function doDeleteComplex($query) {
525
    return $this->doDelete($query);
526
  }
527
528
  /**
529
   * Early deprecated function for complex delete conditions
530
   *
531
   * Usually used for mallformed $where conditions
532
   *
533
   * @param $table
534
   * @param $where
535
   *
536
   * @return array|bool|mysqli_result|null
537
   * @deprecated
538
   */
539
  public function doDeleteDeprecated($table, $where) {
540
    return $this->doDeleteWhere($table, $where, false);
541
  }
542
543
544
  protected function castAsDbValue($value) {
545
    switch(gettype($value)) {
546
      case TYPE_INTEGER:
547
      case TYPE_DOUBLE:
548
        // do nothing
549
      break;
550
551
      case TYPE_BOOLEAN:
552
        $value = $value ? 1 : 0;
553
      break;
554
555
      case TYPE_NULL:
556
        $value = 'NULL';
557
      break;
558
559
      /** @noinspection PhpMissingBreakStatementInspection */
560
      case TYPE_ARRAY:
561
        $value = serialize($value);
562
      // Continuing with serialized array value
563
      case TYPE_STRING:
564
        // Empty type is string
565
      case TYPE_EMPTY:
566
        // No-type defaults to string
567
      default:
568
        $value = "'" . $this->db_escape((string)$value) . "'";
569
      break;
570
    }
571
572
    return $value;
573
  }
574
575
  /**
576
   * Make field list safe
577
   *
578
   * Support expressions - expression index should be strictly integer!
579
   *
580
   * @param array $fields - array of pair $fieldName => $fieldValue
581
   *
582
   * @return array
583
   */
584 View Code Duplication
  protected function safeFieldsEqualValues($fields) {
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...
585
    $result = array();
586
587
    if (!is_array($fields) || empty($fields)) {
588
      return $result;
589
    }
590
591
    foreach ($fields as $fieldName => $fieldValue) {
592
      // Integer $fieldName means "leave as is" - for expressions and already processed fields
593
      if (is_int($fieldName)) {
594
        $result[$fieldName] = $fieldValue;
595
      } else {
596
        $result[$fieldName] = "`{$fieldName}` = " . $this->castAsDbValue($fieldValue);
597
      }
598
    }
599
600
    return $result;
601
  }
602
603
  /**
604
   * Make fields adjustment safe
605
   *
606
   * Convert "key => value" pair to string "`key` = `key` + (value)"
607
   * Supports expressions - expression index should be strictly integer!
608
   *
609
   * @param array $fields - array of pair $fieldName => $fieldValue
610
   *
611
   * @return array
612
   */
613 View Code Duplication
  protected function safeFieldsAdjust($fields) {
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...
614
    $result = array();
615
616
    if (!is_array($fields) || empty($fields)) {
617
      return $result;
618
    }
619
620
    foreach ($fields as $fieldName => $fieldValue) {
621
      // Integer $fieldName means "leave as is" - for expressions and already processed fields
622
      if (is_int($fieldName)) {
623
        $result[$fieldName] = $fieldValue;
624
      } else {
625
        $result[$fieldName] = "`{$fieldName}` = `{$fieldName}` + (" . $this->castAsDbValue($fieldValue) . ")";
626
      }
627
    }
628
629
    return $result;
630
  }
631
632
  // TODO - redo as callable usage with array_map/array_walk
633 View Code Duplication
  public function safeValues($values) {
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...
634
    $result = array();
635
636
    if (!is_array($values) || empty($values)) {
637
      return $result;
638
    }
639
640
    foreach ($values as $key => $value) {
641
      $result[$key] = $this->castAsDbValue($value);
642
    }
643
644
    return $result;
645
  }
646
647
  // TODO - redo as callable usage with array_map/array_walk
648 View Code Duplication
  public function safeFields($fields) {
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...
649
    $result = array();
650
651
    if (!is_array($fields) || empty($fields)) {
652
      return $result;
653
    }
654
655
    foreach ($fields as $key => $value) {
656
      $result[$key] = "`" . $this->db_escape($value) . "`";
657
    }
658
659
    return $result;
660
  }
661
662
663
  /**
664
   * Returns iterator to iterate through mysqli_result
665
   *
666
   * @param string $query
667
   *
668
   * return DbResultIterator
669
   *
670
   * @return DbEmptyIterator|DbMysqliResultIterator
671
   */
672
  public function doSelectIterator($query) {
673
    $queryResult = $this->doSelect($query);
674
675
    if ($queryResult instanceof mysqli_result) {
676
      $result = new DbMysqliResultIterator($queryResult);
677
    } else {
678
      $result = new DbEmptyIterator();
679
    }
680
681
    return $result;
682
  }
683
684
  /**
685
   * @param DbQueryConstructor $stmt
686
   * @param bool               $skip_query_check
687
   */
688
  public function doStmtLockAll($stmt, $skip_query_check = false) {
689
    $this->doExecute(
690
      $stmt
691
        ->select()
692
        ->field(1)
693
        ->setForUpdate()
694
        ->__toString(),
695
      $skip_query_check
696
    );
697
  }
698
699
  // TODO Заменить это на новый логгер
700
  protected function security_watch_user_queries($query) {
701
    global $user;
702
703
    if (
704
      !$this->isWatching // Not already watching
705
      && !empty(classSupernova::$config->game_watchlist_array) // There is some players in watchlist
706
      && in_array($user['id'], classSupernova::$config->game_watchlist_array) // Current player is in watchlist
707
      && !preg_match('/^(select|commit|rollback|start transaction)/i', $query) // Current query should be watched
708
    ) {
709
      $this->isWatching = true;
710
      $msg = "\$query = \"{$query}\"\n\r";
711
      if (!empty($_POST)) {
712
        $msg .= "\n\r" . dump($_POST, '$_POST');
713
      }
714
      if (!empty($_GET)) {
715
        $msg .= "\n\r" . dump($_GET, '$_GET');
716
      }
717
      classSupernova::$debug->warning($msg, "Watching user {$user['id']}", 399, array('base_dump' => true));
0 ignored issues
show
Documentation introduced by
array('base_dump' => true) is of type array<string,boolean,{"base_dump":"boolean"}>, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
718
      $this->isWatching = false;
719
    }
720
  }
721
722
723
  public function security_query_check_bad_words($query) {
724
    if ($this->skipQueryCheck) {
725
      return;
726
    }
727
728
    global $user, $dm_change_legit, $mm_change_legit;
729
730
    switch(true) {
731
      case stripos($query, 'RUNCATE TABL') != false:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'RUNCATE TABL') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
732
      case stripos($query, 'ROP TABL') != false:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'ROP TABL') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
733
      case stripos($query, 'ENAME TABL') != false:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'ENAME TABL') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
734
      case stripos($query, 'REATE DATABAS') != false:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'REATE DATABAS') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
735
      case stripos($query, 'REATE TABL') != false:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'REATE TABL') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
736
      case stripos($query, 'ET PASSWOR') != false:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'ET PASSWOR') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
737
      case stripos($query, 'EOAD DAT') != false:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'EOAD DAT') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
738
      case stripos($query, 'RPG_POINTS') != false && stripos(trim($query), 'UPDATE ') === 0 && !$dm_change_legit:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'RPG_POINTS') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
739
      case stripos($query, 'METAMATTER') != false && stripos(trim($query), 'UPDATE ') === 0 && !$mm_change_legit:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'METAMATTER') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
740
      case stripos($query, 'AUTHLEVEL') != false && $user['authlevel'] < 3 && stripos($query, 'SELECT') !== 0:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'AUTHLEVEL') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
741
        $report = "Hacking attempt (" . date("d.m.Y H:i:s") . " - [" . time() . "]):\n";
742
        $report .= ">Database Inforamation\n";
743
        $report .= "\tID - " . $user['id'] . "\n";
744
        $report .= "\tUser - " . $user['username'] . "\n";
745
        $report .= "\tAuth level - " . $user['authlevel'] . "\n";
746
        $report .= "\tAdmin Notes - " . $user['adminNotes'] . "\n";
747
        $report .= "\tCurrent Planet - " . $user['current_planet'] . "\n";
748
        $report .= "\tUser IP - " . $user['user_lastip'] . "\n";
749
        $report .= "\tUser IP at Reg - " . $user['ip_at_reg'] . "\n";
750
        $report .= "\tUser Agent- " . $_SERVER['HTTP_USER_AGENT'] . "\n";
751
        $report .= "\tCurrent Page - " . $user['current_page'] . "\n";
752
        $report .= "\tRegister Time - " . $user['register_time'] . "\n";
753
        $report .= "\n";
754
755
        $report .= ">Query Information\n";
756
        $report .= "\tQuery - " . $query . "\n";
757
        $report .= "\n";
758
759
        $report .= ">\$_SERVER Information\n";
760
        $report .= "\tIP - " . $_SERVER['REMOTE_ADDR'] . "\n";
761
        $report .= "\tHost Name - " . $_SERVER['HTTP_HOST'] . "\n";
762
        $report .= "\tUser Agent - " . $_SERVER['HTTP_USER_AGENT'] . "\n";
763
        $report .= "\tRequest Method - " . $_SERVER['REQUEST_METHOD'] . "\n";
764
        $report .= "\tCame From - " . $_SERVER['HTTP_REFERER'] . "\n";
765
        $report .= "\tPage is - " . $_SERVER['SCRIPT_NAME'] . "\n";
766
        $report .= "\tUses Port - " . $_SERVER['REMOTE_PORT'] . "\n";
767
        $report .= "\tServer Protocol - " . $_SERVER['SERVER_PROTOCOL'] . "\n";
768
769
        $report .= "\n--------------------------------------------------------------------------------------------------\n";
770
771
        $fp = fopen(SN_ROOT_PHYSICAL . 'badqrys.txt', 'a');
772
        fwrite($fp, $report);
0 ignored issues
show
Security File Manipulation introduced by
$report can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

7 paths for user data to reach this point

  1. Path: Fetching key HTTP_REFERER from $_SERVER, and $report is assigned in includes/classes/db_mysql.php on line 764
  1. Fetching key HTTP_REFERER from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 764
  2. Path: Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned in includes/classes/db_mysql.php on line 762
  1. Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 762
  2. $report is assigned
    in includes/classes/db_mysql.php on line 764
  3. Path: Fetching key HTTP_HOST from $_SERVER, and $report is assigned in includes/classes/db_mysql.php on line 761
  1. Fetching key HTTP_HOST from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 761
  2. $report is assigned
    in includes/classes/db_mysql.php on line 762
  3. $report is assigned
    in includes/classes/db_mysql.php on line 764
  4. Path: Read from $_GET in includes/classes/db_mysql.php on line 715
  1. Read from $_GET
    in includes/classes/db_mysql.php on line 715
  2. Data is passed through gettype()
    in vendor/docs/txt2html.php on line 18
  3. $msg is assigned
    in includes/classes/db_mysql.php on line 715
  4. $msg is passed to debug::warning()
    in includes/classes/db_mysql.php on line 717
  5. Data is escaped by mysqli_real_escape_string() for sql context(s)
    in vendor/includes/classes/db_mysql_v5.php on line 87
  6. $query is assigned
    in includes/classes/debug.php on line 258
  7. $query is passed to db_mysql::doExecute()
    in includes/classes/debug.php on line 263
  8. $query is passed to db_mysql::doquery()
    in includes/classes/db_mysql.php on line 275
  9. $stringQuery is assigned
    in includes/classes/db_mysql.php on line 229
  10. $stringQuery is passed through trim(), and $stringQuery is assigned
    in includes/classes/db_mysql.php on line 230
  11. $stringQuery is passed to db_mysql::security_query_check_bad_words()
    in includes/classes/db_mysql.php on line 235
  12. $report is assigned
    in includes/classes/db_mysql.php on line 756
  13. $report is assigned
    in includes/classes/db_mysql.php on line 761
  14. $report is assigned
    in includes/classes/db_mysql.php on line 762
  15. $report is assigned
    in includes/classes/db_mysql.php on line 764
  5. Path: Read from $_POST in includes/classes/db_mysql.php on line 712
  1. Read from $_POST
    in includes/classes/db_mysql.php on line 712
  2. Data is passed through gettype()
    in vendor/docs/txt2html.php on line 18
  3. $msg is assigned
    in includes/classes/db_mysql.php on line 712
  4. $msg is passed to debug::warning()
    in includes/classes/db_mysql.php on line 717
  5. Data is escaped by mysqli_real_escape_string() for sql context(s)
    in vendor/includes/classes/db_mysql_v5.php on line 87
  6. $query is assigned
    in includes/classes/debug.php on line 258
  7. $query is passed to db_mysql::doExecute()
    in includes/classes/debug.php on line 263
  8. $query is passed to db_mysql::doquery()
    in includes/classes/db_mysql.php on line 275
  9. $stringQuery is assigned
    in includes/classes/db_mysql.php on line 229
  10. $stringQuery is passed through trim(), and $stringQuery is assigned
    in includes/classes/db_mysql.php on line 230
  11. $stringQuery is passed to db_mysql::security_query_check_bad_words()
    in includes/classes/db_mysql.php on line 235
  12. $report is assigned
    in includes/classes/db_mysql.php on line 756
  13. $report is assigned
    in includes/classes/db_mysql.php on line 761
  14. $report is assigned
    in includes/classes/db_mysql.php on line 762
  15. $report is assigned
    in includes/classes/db_mysql.php on line 764
  6. Path: Read from $_POST in includes/general.php on line 258
  1. Read from $_POST
    in includes/general.php on line 258
  2. sys_get_param() returns tainted data
    in includes/general.php on line 290
  3. Data is passed through strip_tags(), and Data is passed through trim()
    in vendor/includes/general.php on line 1289
  4. sys_get_param_str_unsafe() returns tainted data, and sys_get_param_str_unsafe('uni_name') is passed through strip_tags(), and strip_tags(sys_get_param_str_unsafe('uni_name')) is passed through sprintf(), and sprintf(\classLocale::$lang['uni_msg_admin_rename'], $user['id'], $user['username'], $uni_price, $uni_system ? \classLocale::$lang['uni_system_of'] : \classLocale::$lang['uni_galaxy_of'], $uni_galaxy, $uni_system ? ":{$uni_system}" : '', strip_tags(sys_get_param_str_unsafe('uni_name'))) is passed to debug::warning()
    in includes/includes/uni_rename.php on line 55
  5. Data is escaped by mysqli_real_escape_string() for sql context(s)
    in vendor/includes/classes/db_mysql_v5.php on line 87
  6. $query is assigned
    in includes/classes/debug.php on line 258
  7. $query is passed to db_mysql::doExecute()
    in includes/classes/debug.php on line 263
  8. $query is passed to db_mysql::doquery()
    in includes/classes/db_mysql.php on line 275
  9. $stringQuery is assigned
    in includes/classes/db_mysql.php on line 229
  10. $stringQuery is passed through trim(), and $stringQuery is assigned
    in includes/classes/db_mysql.php on line 230
  11. $stringQuery is passed to db_mysql::security_query_check_bad_words()
    in includes/classes/db_mysql.php on line 235
  12. $report is assigned
    in includes/classes/db_mysql.php on line 756
  13. $report is assigned
    in includes/classes/db_mysql.php on line 761
  14. $report is assigned
    in includes/classes/db_mysql.php on line 762
  15. $report is assigned
    in includes/classes/db_mysql.php on line 764
  7. Path: Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned in includes/classes/db_mysql.php on line 750
  1. Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 750
  2. $report is assigned
    in includes/classes/db_mysql.php on line 756
  3. $report is assigned
    in includes/classes/db_mysql.php on line 761
  4. $report is assigned
    in includes/classes/db_mysql.php on line 762
  5. $report is assigned
    in includes/classes/db_mysql.php on line 764

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
773
        fclose($fp);
774
775
        $message = 'Привет, я не знаю то, что Вы пробовали сделать, но команда, которую Вы только послали базе данных, не выглядела очень дружественной и она была заблокированна.<br /><br />Ваш IP, и другие данные переданны администрации сервера. Удачи!.';
776
        die($message);
777
      break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
778
    }
779
  }
780
781
  /**
782
   * @param bool $prefixed_only
783
   *
784
   * @return array
785
   */
786
  public function db_get_table_list($prefixed_only = true) {
787
    $query = $this->mysql_get_table_list();
788
789
    $prefix_length = strlen($this->db_prefix);
790
791
    $tl = array();
792
    while($row = $this->db_fetch($query)) {
0 ignored issues
show
Bug introduced by
It seems like $query defined by $this->mysql_get_table_list() on line 787 can also be of type boolean; however, db_mysql::db_fetch() does only seem to accept object<mysqli_result>, 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...
793
      foreach ($row as $table_name) {
794
        if (strpos($table_name, $this->db_prefix) === 0) {
795
          $table_name = substr($table_name, $prefix_length);
796
        } elseif ($prefixed_only) {
797
          continue;
798
        }
799
        // $table_name = str_replace($db_prefix, '', $table_name);
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% 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...
800
        $tl[$table_name] = $table_name;
801
      }
802
    }
803
804
    return $tl;
805
  }
806
807
  /**
808
   * @param string $statement
809
   *
810
   * @return bool|mysqli_stmt
811
   */
812 View Code Duplication
  public function db_prepare($statement) {
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...
813
    $microtime = microtime(true);
814
    $result = $this->driver->mysql_prepare($statement);
815
    $this->time_mysql_total += microtime(true) - $microtime;
816
817
    return $result;
818
  }
819
820
821
  /**
822
   * L1 perform the query
823
   *
824
   * @param $query_string
825
   *
826
   * @return bool|mysqli_result
827
   */
828 View Code Duplication
  public function db_sql_query($query_string) {
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...
829
    $microtime = microtime(true);
830
    $result = $this->driver->mysql_query($query_string);
831
    $this->time_mysql_total += microtime(true) - $microtime;
832
833
    return $result;
834
  }
835
836
  /**
837
   * L1 fetch assoc array
838
   *
839
   * @param mysqli_result $query
840
   *
841
   * @return array|null
842
   */
843 View Code Duplication
  public function db_fetch(&$query) {
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...
844
    $microtime = microtime(true);
845
    $result = $this->driver->mysql_fetch_assoc($query);
846
    $this->time_mysql_total += microtime(true) - $microtime;
847
848
    return $result;
849
  }
850
851
  public function db_fetch_row(&$query) {
852
    return $this->driver->mysql_fetch_row($query);
853
  }
854
855
  public function db_escape($unescaped_string) {
856
    return $this->driver->mysql_real_escape_string($unescaped_string);
857
  }
858
859
  public function driver_disconnect() {
860
    return $this->driver->mysql_close_link();
861
  }
862
863
  public function db_error() {
864
    return $this->driver->mysql_error();
865
  }
866
867
  public function db_insert_id() {
868
    return $this->driver->mysql_insert_id();
869
  }
870
871
  public function db_num_rows(&$result) {
872
    return $this->driver->mysql_num_rows($result);
873
  }
874
875
  public function db_affected_rows() {
876
    return $this->driver->mysql_affected_rows();
877
  }
878
879
  /**
880
   * @return string
881
   */
882
  public function db_get_client_info() {
883
    return $this->driver->mysql_get_client_info();
884
  }
885
886
  /**
887
   * @return string
888
   */
889
  public function db_get_server_info() {
890
    return $this->driver->mysql_get_server_info();
891
  }
892
893
  /**
894
   * @return string
895
   */
896
  public function db_get_host_info() {
897
    return $this->driver->mysql_get_host_info();
898
  }
899
900
  public function db_get_server_stat() {
901
    $result = array();
902
903
    $status = explode('  ', $this->driver->mysql_stat());
904
    foreach ($status as $value) {
905
      $row = explode(': ', $value);
906
      $result[$row[0]] = $row[1];
907
    }
908
909
    return $result;
910
  }
911
912
  /**
913
   * @return array
914
   * @throws Exception
915
   */
916
  public function db_core_show_status() {
917
    $result = array();
918
919
    $query = $this->db_sql_query('SHOW STATUS;');
920
    if (is_bool($query)) {
921
      throw new Exception('Result of SHOW STATUS command is boolean - which should never happen. Connection to DB is lost?');
922
    }
923
    while($row = db_fetch($query)) {
924
      $result[$row['Variable_name']] = $row['Value'];
925
    }
926
927
    return $result;
928
  }
929
930
  public function mysql_get_table_list() {
931
    return $this->db_sql_query('SHOW TABLES;');
932
  }
933
934
  public function mysql_get_innodb_status() {
935
    return $this->db_sql_query('SHOW ENGINE INNODB STATUS;');
936
  }
937
938
  /**
939
   * @return \DBAL\DbTransaction
940
   */
941
  public function getTransaction() {
942
    return $this->transaction;
943
  }
944
945
  // Some wrappers to DbTransaction
946
  // Unused for now
947
  public function transactionCheck($status = null) {
948
    return $this->transaction->check($status);
949
  }
950
951
  public function transactionStart($level = '') {
952
    return $this->transaction->start($level);
953
  }
954
955
  public function transactionCommit() {
956
    return $this->transaction->commit();
957
  }
958
959
  public function transactionRollback() {
960
    return $this->transaction->rollback();
961
  }
962
963
}
964