Completed
Push — work-fleets ( c62b27...6257a1 )
by SuperNova.WS
06:02
created

db_mysql::traceQuery()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
eloc 11
nc 5
nop 0
dl 0
loc 19
ccs 0
cts 13
cp 0
crap 56
rs 8.2222
c 0
b 0
f 0
1
<?php
2
3
use \DBAL\DbQuery;
4
5
/**
6
 * Created by Gorlum 01.09.2015 15:58
7
 */
8
class db_mysql {
9
  const TRANSACTION_SERIALIZABLE = 'SERIALIZABLE';
10
  const TRANSACTION_REPEATABLE_READ = 'REPEATABLE READ';
11
  const TRANSACTION_READ_COMMITTED = 'READ COMMITTED';
12
  const TRANSACTION_READ_UNCOMMITTED = 'READ UNCOMMITTED';
13
14
  /**
15
   * Статус соеднения с MySQL
16
   *
17
   * @var bool
18
   */
19
  public $connected = false;
20
  /**
21
   * Префикс названий таблиц в БД
22
   *
23
   * @var string
24
   */
25
  public $db_prefix = '';
26
  /**
27
   * Список таблиц в БД
28
   *
29
   * @var array
30
   */
31
  public $table_list = array();
32
33
  /**
34
   * Настройки БД
35
   *
36
   * @var array
37
   */
38
  protected $dbsettings = array();
39
  /**
40
   * Драйвер для прямого обращения к MySQL
41
   *
42
   * @var db_mysql_v5 $driver
43
   */
44
  public $driver = null;
45
46
  /**
47
   * Общее время запросов
48
   *
49
   * @var float $time_mysql_total
50
   */
51
  public $time_mysql_total = 0.0;
52
53
  /**
54
   * Amount of queries on this DB
55
   *
56
   * @var int
57
   */
58
  public $queryCount = 0;
59
60
  public $isWatching = false;
61
62
  /**
63
   * @var \DBAL\DbTransaction $transaction
64
   */
65
  protected $transaction;
66
67
  /**
68
   * Should query check be skipped?
69
   *
70
   * Used for altering scheme of DB
71
   *
72
   * @var bool $skipQueryCheck
73
   */
74
  protected $skipQueryCheck = false;
75
76
  /**
77
   * @var SnCache $snCache
78
   */
79
  public $snCache;
80
81
  /**
82
   * db_mysql constructor.
83
   *
84
   * @param \Common\GlobalContainer $gc
85
   */
86
  public function __construct($gc) {
87
    $this->transaction = new \DBAL\DbTransaction($gc, $this);
88
    $this->snCache = new $gc->snCacheClass($gc, $this);
89
  }
90
91
  public function load_db_settings($configFile = '') {
92
    $dbsettings = array();
93
94
    empty($configFile) ? $configFile = SN_ROOT_PHYSICAL . "config" . DOT_PHP_EX : false;
95
96
    require $configFile;
97
98
    $this->dbsettings = $dbsettings;
99
  }
100
101
  /**
102
   * @param null|array $external_db_settings
103
   *
104
   * @return bool
105
   */
106
  public function sn_db_connect($external_db_settings = null) {
107
    $this->db_disconnect();
108
109
    if (!empty($external_db_settings) && is_array($external_db_settings)) {
110
      $this->dbsettings = $external_db_settings;
111
    }
112
113
    if (empty($this->dbsettings)) {
114
      $this->load_db_settings(SN_ROOT_PHYSICAL . "config" . DOT_PHP_EX);
115
    }
116
117
    // TODO - фатальные (?) ошибки на каждом шагу. Хотя - скорее Эксепшны
118
    if (!empty($this->dbsettings)) {
119
      $driver_name = empty($this->dbsettings['sn_driver']) ? 'db_mysql_v5' : $this->dbsettings['sn_driver'];
120
      $this->driver = new $driver_name();
121
      $this->db_prefix = $this->dbsettings['prefix'];
122
123
      $this->connected = $this->connected || $this->driver_connect();
124
125
      if ($this->connected) {
126
        $this->table_list = $this->db_get_table_list();
127
        // TODO Проверка на пустоту
128
      }
129
    } else {
130
      $this->connected = false;
131
    }
132
133
    return $this->connected;
134
  }
135
136
  protected function driver_connect() {
137
    if (!is_object($this->driver)) {
138
      classSupernova::$debug->error_fatal('DB Error - No driver for MySQL found!');
139
    }
140
141
    if (!method_exists($this->driver, 'mysql_connect')) {
142
      classSupernova::$debug->error_fatal('DB Error - WRONG MySQL driver!');
143
    }
144
145
    return $this->driver->mysql_connect($this->dbsettings);
146
  }
147
148
  public function db_disconnect() {
149
    if ($this->connected) {
150
      $this->connected = !$this->driver_disconnect();
151
      $this->connected = false;
152
    }
153
154
    return !$this->connected;
155
  }
156
157
  /**
158
   * @param string $query
159
   *
160
   * @return mixed|string
161
   */
162
  public function replaceTablePlaceholders($query) {
163
    $sql = $query;
164
    if (strpos($sql, '{{') !== false) {
165
      foreach ($this->table_list as $tableName) {
166
        $sql = str_replace("{{{$tableName}}}", $this->db_prefix . $tableName, $sql);
167
      }
168
    }
169
170
    return $sql;
171
  }
172
173
  /**
174
   * @param $query
175
   */
176
  protected function logQuery($query) {
177
    if (!classSupernova::$config->debug) {
178
      return;
179
    }
180
181
    $this->queryCount++;
182
    $arr = debug_backtrace();
183
    $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...
184
    $line = $arr[0]['line'];
185
    classSupernova::$debug->add("<tr><th>Query {$this->queryCount}: </th><th>$query</th><th>{$file} @ {$line}</th><th>&nbsp;</th></tr>");
186
  }
187
188
189
  /**
190
   * @return string
191
   */
192
  public function traceQuery() {
193
    if (!defined('DEBUG_SQL_COMMENT') || constant('DEBUG_SQL_ERROR') !== true) {
194
      return '';
195
    }
196
197
    $backtrace = debug_backtrace();
198
    $sql_comment = classSupernova::$debug->compact_backtrace($backtrace, defined('DEBUG_SQL_COMMENT_LONG'));
199
200
    if (defined('DEBUG_SQL_ERROR') && constant('DEBUG_SQL_ERROR') === true) {
201
      classSupernova::$debug->add_to_array($sql_comment);
202
    }
203
204
    $sql_commented = implode("\r\n", $sql_comment);
205
    if (defined('DEBUG_SQL_ONLINE') && constant('DEBUG_SQL_ONLINE') === true) {
206
      classSupernova::$debug->warning($sql_commented, 'SQL Debug', LOG_DEBUG_SQL);
207
    }
208
209
    return $sql_commented;
210
  }
211
212
  /**
213
   * @param string $query
214
   *
215
   * @return array|bool|mysqli_result|null
216
   * @internal param bool $skip_query_check
217
   *
218
   */
219
  protected function queryDriver($query) {
220
    if (!$this->connected) {
221
      $this->sn_db_connect();
222
    }
223
224
    $stringQuery = $query;
225
    $stringQuery = trim($stringQuery);
226
    // You can't do it - 'cause you can break commented statement with line-end comments
227
    // $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...
228
229
    $this->security_watch_user_queries($stringQuery);
230
    $this->security_query_check_bad_words($stringQuery);
231
    $this->logQuery($stringQuery);
232
233
    $stringQuery = $this->replaceTablePlaceholders($stringQuery);
234
235
    $queryTrace = $this->traceQuery();
236
237
    $queryResult = null;
238
    try {
239
      $queryResult = $this->db_sql_query($stringQuery . DbSqlHelper::quoteComment($queryTrace));
240
      if (!$queryResult) {
241
        throw new Exception();
242
      }
243
    } catch (Exception $e) {
244
      classSupernova::$debug->error($this->db_error() . "<br />{$query}<br />", 'SQL Error');
245
    }
246
247
    return $queryResult;
248
  }
249
250
251
  // Just wrappers to distinguish query types
252
  /**
253
   * Executes non-data manipulation statements
254
   *
255
   * Can execute queries with check skip
256
   * Honor current state of query checking
257
   *
258
   * @param string $query
259
   * @param bool   $skip_query_check
260
   *
261
   * @return array|bool|mysqli_result|null
262
   */
263
  public function doSql($query, $skip_query_check = false) {
264
    $prevState = false;
265
    if ($skip_query_check) {
266
      $prevState = $this->skipQueryCheck;
267
      $this->skipQueryCheck = true;
268
    }
269
    // TODO - disable watch ??
270
    $result = $this->queryDriver($query);
271
    if ($skip_query_check) {
272
      $this->skipQueryCheck = $prevState;
273
    }
274
275
    return $result;
276
  }
277
278
279
  public function doSelect($query) {
280
    return $this->doSql($query);
281
  }
282
283
  /**
284
   * @param string $query
285
   *
286
   * @return array|null
287
   */
288
  public function doSelectFetch($query) {
289
    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...
290
  }
291
292
  /**
293
   * @param string $query
294
   *
295
   * @return mixed|null
296
   */
297
  public function doSelectFetchValue($query) {
298
    $row = $this->doSelectFetch($query);
299
300
    return is_array($row) ? reset($row) : null;
301
  }
302
303
304
  protected function doSet($table, $fieldsAndValues, $replace = DB_INSERT_PLAIN) {
305
    $tableSafe = $this->db_escape($table);
306
    $safeFieldsAndValues = implode(',', $this->safeFieldsEqualValues($fieldsAndValues));
307
//    $command = $replace == DB_INSERT_REPLACE ? 'REPLACE' : 'INSERT';
308
//    $command .= $replace == DB_INSERT_IGNORE ? ' IGNORE' : '';
309
    switch ($replace) {
310
      case DB_INSERT_IGNORE:
311
        $command = 'INSERT IGNORE';
312
      break;
313
      case DB_INSERT_REPLACE:
314
        $command = 'REPLACE';
315
      break;
316
      default:
317
        $command = 'INSERT';
318
      break;
319
    }
320
    $query = "{$command} INTO `{{{$tableSafe}}}` SET {$safeFieldsAndValues}";
321
322
    return $this->doSql($query);
323
  }
324
325
  // TODO - batch insert and replace here
326
  // TODO - перед тем, как переделывать данные из депрекейтов - убедится, что
327
  // null - это null, а не строка'NULL'
328
  /**
329
   * Values should be passed as-is
330
   *
331
   * @param string   $table
332
   * @param array    $fields
333
   * @param string[] $values
334
   * @param bool     $replace
335
   *
336
   * @return array|bool|mysqli_result|null
337
   * @deprecated
338
   */
339
  protected function doValuesDeprecated($table, $fields, &$values, $replace = DB_INSERT_PLAIN) {
340
    $tableSafe = $this->db_escape($table);
341
    $safeFields = implode(',', $this->safeFields($fields));
342
    $safeValues = implode(',', $values);
343
    $command = $replace == DB_INSERT_REPLACE ? 'REPLACE' : 'INSERT';
344
    $query = "{$command} INTO `{{{$tableSafe}}}` ({$safeFields}) VALUES {$safeValues}";
345
346
    return $this->doSql($query);
347
  }
348
349
350
351
  // INSERTERS
352
  public function doInsertComplex($query) {
353
    return $this->doSql($query);
354
  }
355
356
  /**
357
   * @param string $table
358
   * @param array  $fieldsAndValues
359
   * @param int    $replace - DB_INSERT_PLAIN || DB_INSERT_IGNORE
360
   *
361
   * @return array|bool|mysqli_result|null
362
   */
363
  public function doInsertSet($table, $fieldsAndValues, $replace = DB_INSERT_PLAIN) {
364
    return $this->doSet($table, $fieldsAndValues, $replace);
365
  }
366
367
368
  /**
369
   * Values should be passed as-is
370
   *
371
   * @param string   $table
372
   * @param array    $fields
373
   * @param string[] $values
374
   *
375
   * @return array|bool|mysqli_result|null
376
   * @deprecated
377
   */
378
  public function doInsertValuesDeprecated($table, $fields, &$values) {
379
    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...
380
  }
381
382
383
384
  // REPLACERS
385
  /**
386
   * Replaces record in DB
387
   *
388
   * There are no DANGER replace operations
389
   *
390
   * @param string $table
391
   * @param array  $fieldsAndValues
392
   *
393
   * @return array|bool|mysqli_result|null
394
   */
395
  public function doReplaceSet($table, $fieldsAndValues) {
396
    return $this->doSet($table, $fieldsAndValues, DB_INSERT_REPLACE);
397
  }
398
399
  /**
400
   * Values should be passed as-is
401
   *
402
   * @param string   $table
403
   * @param array    $fields
404
   * @param string[] $values
405
   *
406
   * @return array|bool|mysqli_result|null
407
   * @deprecated
408
   */
409
  public function doReplaceValuesDeprecated($table, $fields, &$values) {
410
    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...
411
  }
412
413
414
  // UPDATERS
415
  public function doUpdateComplex($query) {
416
    return $this->doSql($query);
417
  }
418
419
  public function doUpdateReallyComplex($query) {
420
    return $this->doSql($query);
421
  }
422
423
  /**
424
   * Executes self-contained SQL UPDATE query
425
   *
426
   * Self-contained - means no params used
427
   * Such queries usually used to make large amount of in-base calculations
428
   *
429
   * @param $query
430
   *
431
   * @return array|bool|mysqli_result|null
432
   */
433
  public function doUpdateSqlNoParam($query) {
434
    return $this->doSql($query);
435
  }
436
437
  protected function doUpdateWhere($table, $fieldsSet, $fieldsAdjust = array(), $where = array(), $isOneRecord = DB_RECORDS_ALL) {
438
    $tableSafe = $this->db_escape($table);
439
440
    $safeFields = array();
441
    // Adjusts overwritten by Sets
442
    if ($safeAdjust = implode(',', $this->safeFieldsAdjust($fieldsAdjust))) {
443
      $safeFields[] = &$safeAdjust;
444
    }
445
    if ($safeFieldsEqualValues = implode(',', $this->safeFieldsEqualValues($fieldsSet))) {
446
      $safeFields[] = &$safeFieldsEqualValues;
447
    }
448
    $safeFieldsString = implode(',', $safeFields);
449
450
    // TODO - Exception of $safeFieldsString
451
452
    $safeWhereAnd = implode(' AND ', $this->safeFieldsEqualValues($where));
453
    $query = "UPDATE `{{{$tableSafe}}}` SET {$safeFieldsString}"
454
      . (!empty($safeWhereAnd) ? " WHERE {$safeWhereAnd}" : '')
455
      . ($isOneRecord == DB_RECORD_ONE ? ' LIMIT 1' : '');
456
457
    return $this->doSql($query);
458
  }
459
460
  public function doUpdateRowSet($table, $fieldsAndValues, $where) {
461
    return $this->doUpdateWhere($table, $fieldsAndValues, array(), $where, DB_RECORD_ONE);
462
  }
463
464
  public function doUpdateTableSet($table, $fieldsAndValues, $where = array()) {
465
    return $this->doUpdateWhere($table, $fieldsAndValues, array(), $where, DB_RECORDS_ALL);
466
  }
467
468
  public function doUpdateRowAdjust($table, $fieldsSet, $fieldsAdjust, $where) {
469
    return $this->doUpdateWhere($table, $fieldsSet, $fieldsAdjust, $where, DB_RECORD_ONE);
470
  }
471
472
  public function doUpdateTableAdjust($table, $fieldsSet, $fieldsAdjust, $where) {
473
    return $this->doUpdateWhere($table, $fieldsSet, $fieldsAdjust, $where, DB_RECORDS_ALL);
474
  }
475
476
  /**
477
   * In this call means that used some conditions in $where different than field = value
478
   *
479
   * It should be rewrote later
480
   *
481
   * @param $table
482
   * @param $fieldsSet
483
   * @param $fieldsAdjust
484
   * @param $where
485
   *
486
   * @return array|bool|mysqli_result|null
487
   * @deprecated
488
   */
489
  public function doUpdateTableAdjustDanger($table, $fieldsSet, $fieldsAdjust, $where) {
490
    return $this->doUpdateWhere($table, $fieldsSet, $fieldsAdjust, $where, DB_RECORDS_ALL);
491
  }
492
493
  /**
494
   * For update_old - would be deprecated
495
   *
496
   * @param string $query
497
   *
498
   * @return array|bool|mysqli_result|null
499
   * @deprecated
500
   */
501
  public function doUpdateOld($query) {
502
    return $this->doSql($query);
503
  }
504
505
506
507
  // DELETERS
508
  /**
509
   * @param string $table
510
   * @param array  $where
511
   * @param bool   $isOneRecord
512
   *
513
   * @return DbQuery
514
   */
515
  protected function buildDeleteQuery($table, $where, $isOneRecord = DB_RECORDS_ALL) {
516
    return DbQuery::build($this)
517
      ->table($table)
518
      ->whereArray($where)
519
      ->oneRow($isOneRecord);
520
  }
521
522
  /**
523
   * @param string $table
524
   * @param array  $where - simple WHERE statement list which can be combined with AND
525
   * @param bool   $isOneRecord
526
   *
527
   * @return array|bool|mysqli_result|null
528
   */
529
  public function doDeleteWhere($table, $where, $isOneRecord = DB_RECORDS_ALL) {
530
    return $this->doSql($this->buildDeleteQuery($table, $where, $isOneRecord)->delete());
531
  }
532
533
  /**
534
   * Early deprecated function for complex delete conditions
535
   *
536
   * Used for malformed $where conditions
537
   * Also whereDanger can contain references for other {{tables}}
538
   *
539
   * @param string $table
540
   * @param array  $where
541
   * @param array  $whereDanger
542
   *
543
   * @return array|bool|mysqli_result|null
544
   * @deprecated
545
   */
546
  public function doDeleteDanger($table, $where, $whereDanger) {
547
    return $this->doSql($this->buildDeleteQuery($table, $where, false)->whereArrayDanger($whereDanger)->delete());
0 ignored issues
show
Deprecated Code introduced by
The method DBAL\DbQuery::whereArrayDanger() has been deprecated.

This method has been deprecated.

Loading history...
548
  }
549
550
  /**
551
   * @param string $table
552
   * @param array  $where - simple WHERE statement list which can be combined with AND
553
   *
554
   * @return array|bool|mysqli_result|null
555
   */
556
  public function doDeleteRow($table, $where) {
557
    return $this->doDeleteWhere($table, $where, true);
558
  }
559
560
  /**
561
   * Perform simple delete queries on fixed tables w/o params
562
   *
563
   * @param string $query
564
   *
565
   * @return array|bool|mysqli_result|null
566
   */
567
  public function doDeleteSimple($query) {
568
    return $this->doSql($query);
569
  }
570
571
572
  // Misc functions
573
  //
574 View Code Duplication
  protected function castAsDbValue($value) {
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...
575
    switch (gettype($value)) {
576
      case TYPE_INTEGER:
577
      case TYPE_DOUBLE:
578
        // do nothing
579
      break;
580
581
      case TYPE_BOOLEAN:
582
        $value = $value ? 1 : 0;
583
      break;
584
585
      case TYPE_NULL:
586
        $value = 'NULL';
587
      break;
588
589
      /** @noinspection PhpMissingBreakStatementInspection */
590
      case TYPE_ARRAY:
591
        $value = serialize($value);
592
      // Continuing with serialized array value
593
      case TYPE_STRING:
594
        // Empty type is string
595
      case TYPE_EMPTY:
596
        // No-type defaults to string
597
      default:
598
        $value = "'" . $this->db_escape((string)$value) . "'";
599
      break;
600
    }
601
602
    return $value;
603
  }
604
605
  /**
606
   * Make field list safe
607
   *
608
   * Support expressions - expression index should be strictly integer!
609
   *
610
   * @param array $fields - array of pair $fieldName => $fieldValue
611
   *
612
   * @return array
613
   */
614 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...
615
    $result = array();
616
617
    if (!is_array($fields) || empty($fields)) {
618
      return $result;
619
    }
620
621
    foreach ($fields as $fieldName => $fieldValue) {
622
      // Integer $fieldName means "leave as is" - for expressions and already processed fields
623
      if (is_int($fieldName)) {
624
        $result[$fieldName] = $fieldValue;
625
      } else {
626
        $result[$fieldName] = "`{$fieldName}` = " . $this->castAsDbValue($fieldValue);
627
      }
628
    }
629
630
    return $result;
631
  }
632
633
  /**
634
   * Make fields adjustment safe
635
   *
636
   * Convert "key => value" pair to string "`key` = `key` + (value)"
637
   * Supports expressions - expression index should be strictly integer!
638
   *
639
   * @param array $fields - array of pair $fieldName => $fieldValue
640
   *
641
   * @return array
642
   */
643 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...
644
    $result = array();
645
646
    if (!is_array($fields) || empty($fields)) {
647
      return $result;
648
    }
649
650
    foreach ($fields as $fieldName => $fieldValue) {
651
      // Integer $fieldName means "leave as is" - for expressions and already processed fields
652
      if (is_int($fieldName)) {
653
        $result[$fieldName] = $fieldValue;
654
      } else {
655
        $result[$fieldName] = "`{$fieldName}` = `{$fieldName}` + (" . $this->castAsDbValue($fieldValue) . ")";
656
      }
657
    }
658
659
    return $result;
660
  }
661
662
  // TODO - redo as callable usage with array_map/array_walk
663 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...
664
    $result = array();
665
666
    if (!is_array($values) || empty($values)) {
667
      return $result;
668
    }
669
670
    foreach ($values as $key => $value) {
671
      $result[$key] = $this->castAsDbValue($value);
672
    }
673
674
    return $result;
675
  }
676
677
  // TODO - redo as callable usage with array_map/array_walk
678 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...
679
    $result = array();
680
681
    if (!is_array($fields) || empty($fields)) {
682
      return $result;
683
    }
684
685
    foreach ($fields as $key => $value) {
686
      $result[$key] = "`" . $this->db_escape($value) . "`";
687
    }
688
689
    return $result;
690
  }
691
692
693
  /**
694
   * Returns iterator to iterate through mysqli_result
695
   *
696
   * @param string $query
697
   *
698
   * return DbResultIterator
699
   *
700
   * @return DbEmptyIterator|DbMysqliResultIterator
701
   */
702
  public function doSelectIterator($query) {
703
    $queryResult = $this->doSelect($query);
704
705
    if ($queryResult instanceof mysqli_result) {
706
      $result = new DbMysqliResultIterator($queryResult);
707
    } else {
708
      $result = new DbEmptyIterator();
709
    }
710
711
    return $result;
712
  }
713
714
  /**
715
   * @param DbQueryConstructor $stmt
716
   * @param bool               $skip_query_check
717
   */
718
  public function doStmtLockAll($stmt, $skip_query_check = false) {
719
    $this->doSql(
720
      $stmt
721
        ->select()
722
        ->field(1)
723
        ->setForUpdate()
724
        ->__toString(),
725
      $skip_query_check
726
    );
727
  }
728
729
  // TODO Заменить это на новый логгер
730
  protected function security_watch_user_queries($query) {
731
    global $user;
732
733
    if (
734
      !$this->isWatching // Not already watching
735
      && !empty(classSupernova::$config->game_watchlist_array) // There is some players in watchlist
736
      && in_array($user['id'], classSupernova::$config->game_watchlist_array) // Current player is in watchlist
737
      && !preg_match('/^(select|commit|rollback|start transaction)/i', $query) // Current query should be watched
738
    ) {
739
      $this->isWatching = true;
740
      $msg = "\$query = \"{$query}\"\n\r";
741
      if (!empty($_POST)) {
742
        $msg .= "\n\r" . dump($_POST, '$_POST');
743
      }
744
      if (!empty($_GET)) {
745
        $msg .= "\n\r" . dump($_GET, '$_GET');
746
      }
747
      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...
748
      $this->isWatching = false;
749
    }
750
  }
751
752
753
  public function security_query_check_bad_words($query) {
754
    if ($this->skipQueryCheck) {
755
      return;
756
    }
757
758
    global $user, $dm_change_legit, $mm_change_legit;
759
760
    switch (true) {
761
      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...
762
      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...
763
      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...
764
      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...
765
      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...
766
      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...
767
      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...
768
      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...
769
      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...
770
      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...
771
        $report = "Hacking attempt (" . date("d.m.Y H:i:s") . " - [" . time() . "]):\n";
772
        $report .= ">Database Inforamation\n";
773
        $report .= "\tID - " . $user['id'] . "\n";
774
        $report .= "\tUser - " . $user['username'] . "\n";
775
        $report .= "\tAuth level - " . $user['authlevel'] . "\n";
776
        $report .= "\tAdmin Notes - " . $user['adminNotes'] . "\n";
777
        $report .= "\tCurrent Planet - " . $user['current_planet'] . "\n";
778
        $report .= "\tUser IP - " . $user['user_lastip'] . "\n";
779
        $report .= "\tUser IP at Reg - " . $user['ip_at_reg'] . "\n";
780
        $report .= "\tUser Agent- " . $_SERVER['HTTP_USER_AGENT'] . "\n";
781
        $report .= "\tCurrent Page - " . $user['current_page'] . "\n";
782
        $report .= "\tRegister Time - " . $user['register_time'] . "\n";
783
        $report .= "\n";
784
785
        $report .= ">Query Information\n";
786
        $report .= "\tQuery - " . $query . "\n";
787
        $report .= "\n";
788
789
        $report .= ">\$_SERVER Information\n";
790
        $report .= "\tIP - " . $_SERVER['REMOTE_ADDR'] . "\n";
791
        $report .= "\tHost Name - " . $_SERVER['HTTP_HOST'] . "\n";
792
        $report .= "\tUser Agent - " . $_SERVER['HTTP_USER_AGENT'] . "\n";
793
        $report .= "\tRequest Method - " . $_SERVER['REQUEST_METHOD'] . "\n";
794
        $report .= "\tCame From - " . $_SERVER['HTTP_REFERER'] . "\n";
795
        $report .= "\tPage is - " . $_SERVER['SCRIPT_NAME'] . "\n";
796
        $report .= "\tUses Port - " . $_SERVER['REMOTE_PORT'] . "\n";
797
        $report .= "\tServer Protocol - " . $_SERVER['SERVER_PROTOCOL'] . "\n";
798
799
        $report .= "\n--------------------------------------------------------------------------------------------------\n";
800
801
        $fp = fopen(SN_ROOT_PHYSICAL . 'badqrys.txt', 'a');
802
        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 794
  1. Fetching key HTTP_REFERER from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 794
  2. Path: Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned in includes/classes/db_mysql.php on line 792
  1. Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 792
  2. $report is assigned
    in includes/classes/db_mysql.php on line 794
  3. Path: Fetching key HTTP_HOST from $_SERVER, and $report is assigned in includes/classes/db_mysql.php on line 791
  1. Fetching key HTTP_HOST from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 791
  2. $report is assigned
    in includes/classes/db_mysql.php on line 792
  3. $report is assigned
    in includes/classes/db_mysql.php on line 794
  4. Path: Read from $_GET in includes/classes/db_mysql.php on line 745
  1. Read from $_GET
    in includes/classes/db_mysql.php on line 745
  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 745
  4. $msg is passed to debug::warning()
    in includes/classes/db_mysql.php on line 747
  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::doSql()
    in includes/classes/debug.php on line 263
  8. $query is passed to db_mysql::queryDriver()
    in includes/classes/db_mysql.php on line 270
  9. $stringQuery is assigned
    in includes/classes/db_mysql.php on line 224
  10. $stringQuery is passed through trim(), and $stringQuery is assigned
    in includes/classes/db_mysql.php on line 225
  11. $stringQuery is passed to db_mysql::security_query_check_bad_words()
    in includes/classes/db_mysql.php on line 230
  12. $report is assigned
    in includes/classes/db_mysql.php on line 786
  13. $report is assigned
    in includes/classes/db_mysql.php on line 791
  14. $report is assigned
    in includes/classes/db_mysql.php on line 792
  15. $report is assigned
    in includes/classes/db_mysql.php on line 794
  5. Path: Read from $_POST in includes/classes/db_mysql.php on line 742
  1. Read from $_POST
    in includes/classes/db_mysql.php on line 742
  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 742
  4. $msg is passed to debug::warning()
    in includes/classes/db_mysql.php on line 747
  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::doSql()
    in includes/classes/debug.php on line 263
  8. $query is passed to db_mysql::queryDriver()
    in includes/classes/db_mysql.php on line 270
  9. $stringQuery is assigned
    in includes/classes/db_mysql.php on line 224
  10. $stringQuery is passed through trim(), and $stringQuery is assigned
    in includes/classes/db_mysql.php on line 225
  11. $stringQuery is passed to db_mysql::security_query_check_bad_words()
    in includes/classes/db_mysql.php on line 230
  12. $report is assigned
    in includes/classes/db_mysql.php on line 786
  13. $report is assigned
    in includes/classes/db_mysql.php on line 791
  14. $report is assigned
    in includes/classes/db_mysql.php on line 792
  15. $report is assigned
    in includes/classes/db_mysql.php on line 794
  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::doSql()
    in includes/classes/debug.php on line 263
  8. $query is passed to db_mysql::queryDriver()
    in includes/classes/db_mysql.php on line 270
  9. $stringQuery is assigned
    in includes/classes/db_mysql.php on line 224
  10. $stringQuery is passed through trim(), and $stringQuery is assigned
    in includes/classes/db_mysql.php on line 225
  11. $stringQuery is passed to db_mysql::security_query_check_bad_words()
    in includes/classes/db_mysql.php on line 230
  12. $report is assigned
    in includes/classes/db_mysql.php on line 786
  13. $report is assigned
    in includes/classes/db_mysql.php on line 791
  14. $report is assigned
    in includes/classes/db_mysql.php on line 792
  15. $report is assigned
    in includes/classes/db_mysql.php on line 794
  7. Path: Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned in includes/classes/db_mysql.php on line 780
  1. Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 780
  2. $report is assigned
    in includes/classes/db_mysql.php on line 786
  3. $report is assigned
    in includes/classes/db_mysql.php on line 791
  4. $report is assigned
    in includes/classes/db_mysql.php on line 792
  5. $report is assigned
    in includes/classes/db_mysql.php on line 794

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...
803
        fclose($fp);
804
805
        $message = 'Привет, я не знаю то, что Вы пробовали сделать, но команда, которую Вы только послали базе данных, не выглядела очень дружественной и она была заблокированна.<br /><br />Ваш IP, и другие данные переданны администрации сервера. Удачи!.';
806
        die($message);
807
      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...
808
    }
809
  }
810
811
  /**
812
   * @param bool $prefixed_only
813
   *
814
   * @return array
815
   */
816
  public function db_get_table_list($prefixed_only = true) {
817
    $query = $this->mysql_get_table_list();
818
819
    $prefix_length = strlen($this->db_prefix);
820
821
    $tl = array();
822
    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 817 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...
823
      foreach ($row as $table_name) {
824
        if (strpos($table_name, $this->db_prefix) === 0) {
825
          $table_name = substr($table_name, $prefix_length);
826
        } elseif ($prefixed_only) {
827
          continue;
828
        }
829
        // $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...
830
        $tl[$table_name] = $table_name;
831
      }
832
    }
833
834
    return $tl;
835
  }
836
837
  /**
838
   * @param string $statement
839
   *
840
   * @return bool|mysqli_stmt
841
   */
842 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...
843
    $microtime = microtime(true);
844
    $result = $this->driver->mysql_prepare($statement);
845
    $this->time_mysql_total += microtime(true) - $microtime;
846
847
    return $result;
848
  }
849
850
851
  /**
852
   * L1 perform the query
853
   *
854
   * @param $query_string
855
   *
856
   * @return bool|mysqli_result
857
   */
858 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...
859
    $microtime = microtime(true);
860
    $result = $this->driver->mysql_query($query_string);
861
    $this->time_mysql_total += microtime(true) - $microtime;
862
863
    return $result;
864
  }
865
866
  /**
867
   * L1 fetch assoc array
868
   *
869
   * @param mysqli_result $query
870
   *
871
   * @return array|null
872
   */
873 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...
874
    $microtime = microtime(true);
875
    $result = $this->driver->mysql_fetch_assoc($query);
876
    $this->time_mysql_total += microtime(true) - $microtime;
877
878
    return $result;
879
  }
880
881
  public function db_fetch_row(&$query) {
882
    return $this->driver->mysql_fetch_row($query);
883
  }
884
885
  public function db_escape($unescaped_string) {
886
    return $this->driver->mysql_real_escape_string($unescaped_string);
887
  }
888
889
  public function driver_disconnect() {
890
    return $this->driver->mysql_close_link();
891
  }
892
893
  public function db_error() {
894
    return $this->driver->mysql_error();
895
  }
896
897
  public function db_insert_id() {
898
    return $this->driver->mysql_insert_id();
899
  }
900
901
  public function db_num_rows(&$result) {
902
    return $this->driver->mysql_num_rows($result);
903
  }
904
905
  public function db_affected_rows() {
906
    return $this->driver->mysql_affected_rows();
907
  }
908
909
  /**
910
   * @return string
911
   */
912
  public function db_get_client_info() {
913
    return $this->driver->mysql_get_client_info();
914
  }
915
916
  /**
917
   * @return string
918
   */
919
  public function db_get_server_info() {
920
    return $this->driver->mysql_get_server_info();
921
  }
922
923
  /**
924
   * @return string
925
   */
926
  public function db_get_host_info() {
927
    return $this->driver->mysql_get_host_info();
928
  }
929
930
  public function db_get_server_stat() {
931
    $result = array();
932
933
    $status = explode('  ', $this->driver->mysql_stat());
934
    foreach ($status as $value) {
935
      $row = explode(': ', $value);
936
      $result[$row[0]] = $row[1];
937
    }
938
939
    return $result;
940
  }
941
942
  /**
943
   * @return array
944
   * @throws Exception
945
   */
946
  public function db_core_show_status() {
947
    $result = array();
948
949
    $query = $this->db_sql_query('SHOW STATUS;');
950
    if (is_bool($query)) {
951
      throw new Exception('Result of SHOW STATUS command is boolean - which should never happen. Connection to DB is lost?');
952
    }
953
    while ($row = db_fetch($query)) {
954
      $result[$row['Variable_name']] = $row['Value'];
955
    }
956
957
    return $result;
958
  }
959
960
  public function mysql_get_table_list() {
961
    return $this->db_sql_query('SHOW TABLES;');
962
  }
963
964
  public function mysql_get_innodb_status() {
965
    return $this->db_sql_query('SHOW ENGINE INNODB STATUS;');
966
  }
967
968
  /**
969
   * @return \DBAL\DbTransaction
970
   */
971
  public function getTransaction() {
972
    return $this->transaction;
973
  }
974
975
  // Some wrappers to DbTransaction
976
  // Unused for now
977
  public function transactionCheck($status = null) {
978
    return $this->transaction->check($status);
979
  }
980
981
  public function transactionStart($level = '') {
982
    return $this->transaction->start($level);
983
  }
984
985
  public function transactionCommit() {
986
    return $this->transaction->commit();
987
  }
988
989
  public function transactionRollback() {
990
    return $this->transaction->rollback();
991
  }
992
993
}
994