Completed
Push — work-fleets ( f81083...2b6e2a )
by SuperNova.WS
06:05
created

db_mysql::doSelectDanger()   B

Complexity

Conditions 7
Paths 16

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 12
c 1
b 0
f 0
nc 16
nop 5
dl 0
loc 20
rs 8.2222
ccs 0
cts 15
cp 0
crap 56
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
   */
217
  protected function queryDriver($query) {
218
    if (!$this->connected) {
219
      $this->sn_db_connect();
220
    }
221
222
    $stringQuery = $query;
223
    $stringQuery = trim($stringQuery);
224
    // You can't do it - 'cause you can break commented statement with line-end comments
225
    // $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...
226
227
    $this->security_watch_user_queries($stringQuery);
228
    $this->security_query_check_bad_words($stringQuery);
229
    $this->logQuery($stringQuery);
230
231
    $stringQuery = $this->replaceTablePlaceholders($stringQuery);
232
233
    $queryTrace = $this->traceQuery();
234
235
    $queryResult = null;
236
    try {
237
      $queryResult = $this->db_sql_query($stringQuery . DbSqlHelper::quoteComment($queryTrace));
238
      if (!$queryResult) {
239
        throw new Exception();
240
      }
241
    } catch (Exception $e) {
242
      classSupernova::$debug->error($this->db_error() . "<br />{$query}<br />", 'SQL Error');
243
    }
244
245
    return $queryResult;
246
  }
247
248
249
  // Just wrappers to distinguish query types
250
  /**
251
   * Executes non-data manipulation statements
252
   *
253
   * Can execute queries with check skip
254
   * Honor current state of query checking
255
   *
256
   * @param string $query
257
   * @param bool   $skip_query_check
258
   *
259
   * @return array|bool|mysqli_result|null
260
   */
261
  public function doSql($query, $skip_query_check = false) {
262
    $prevState = false;
263
    if ($skip_query_check) {
264
      $prevState = $this->skipQueryCheck;
265
      $this->skipQueryCheck = true;
266
    }
267
    // TODO - disable watch ??
268
    $result = $this->queryDriver($query);
269
    if ($skip_query_check) {
270
      $this->skipQueryCheck = $prevState;
271
    }
272
273
    return $result;
274
  }
275
276
277
  // SELECTS
278
  public function doSelect($query) {
279
    return $this->doSql($query);
280
  }
281
282
  /**
283
   * DANGER! Fields and Where can be danger
284
   *
285
   * @param string $table
286
   * @param array  $fields
287
   * @param array  $where
288
   * @param bool   $isOneRecord
289
   *
290
   * @return array|bool|mysqli_result|null
291
   */
292
  public function doSelectDanger($table, $fields, $where = array(), $isOneRecord = DB_RECORDS_ALL, $forUpdate = DB_SELECT_PLAIN) {
293
    // TODO - TEMPORARY UNTIL DbQuery
294
    if(!empty($where)) {
295
      foreach ($where as $key => &$value) {
296
        if(!is_int($key)) {
297
          $value = "`$key` = '" . $this->db_escape($value). "'";
298
        }
299
      }
300
    }
301
302
    $query =
303
      "SELECT " . implode(',', $fields) .
304
      " FROM `{{{$table}}}`" .
305
      (!empty($where) ? ' WHERE ' . implode(' AND ', $where) : '') .
306
      ($isOneRecord == DB_RECORD_ONE ? ' LIMIT 1' : '') .
307
      ($forUpdate == DB_SELECT_FOR_UPDATE ? ' FOR UPDATE' : '')
308
    ;
309
310
    return $this->doSql($query);
311
  }
312
313
  /**
314
   * @param string $query
315
   *
316
   * @return array|null
317
   */
318
  public function doSelectFetch($query) {
319
    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...
320
  }
321
322
  /**
323
   * @param string $query
324
   *
325
   * @return mixed|null
326
   */
327
  public function doSelectFetchValue($query) {
328
    $row = $this->doSelectFetch($query);
329
330
    return is_array($row) ? reset($row) : null;
331
  }
332
333
334
  // INSERT/REPLACE
335
  protected function doSet($table, $fieldsAndValues, $replace = DB_INSERT_PLAIN) {
336
    $query = DbQuery::build($this)
337
      ->setTable($table)
338
      ->setValues($fieldsAndValues)
339
      ->insertSet($replace);
340
341
    return $this->doSql($query);
342
  }
343
344
  // TODO - batch insert and replace here
345
  // TODO - перед тем, как переделывать данные из депрекейтов - убедится, что
346
  // null - это null, а не строка'NULL'
347
  /**
348
   * Values should be passed as-is
349
   *
350
   * DANGER! Values should be properly escaped before passing here
351
   *
352
   * @param string   $table
353
   * @param array    $fields
354
   * @param string[] $valuesDanger
355
   * @param bool     $replace
356
   *
357
   * @return array|bool|mysqli_result|null
358
   * @deprecated
359
   */
360
  protected function doInsertBatchDanger($table, $fields, &$valuesDanger, $replace = DB_INSERT_PLAIN) {
361
    $query = DbQuery::build($this)
362
      ->setTable($table)
363
      ->setFields($fields)
364
      ->setValuesDanger($valuesDanger)
365
      ->insertBatch($replace);
366
367
    return $this->doSql($query);
368
  }
369
370
371
  // INSERTERS
372
  public function doInsertComplex($query) {
373
    return $this->doSql($query);
374
  }
375
  /**
376
   * @param string $table
377
   * @param array  $fieldsAndValues
378
   * @param int    $replace - DB_INSERT_PLAIN || DB_INSERT_IGNORE
379
   *
380
   * @return array|bool|mysqli_result|null
381
   */
382
  public function doInsertSet($table, $fieldsAndValues, $replace = DB_INSERT_PLAIN) {
383
    return $this->doSet($table, $fieldsAndValues, $replace);
384
  }
385
  /**
386
   * Values should be passed as-is
387
   *
388
   * @param string   $table
389
   * @param array    $fields
390
   * @param string[] $values
391
   *
392
   * @return array|bool|mysqli_result|null
393
   * @deprecated
394
   */
395
  public function doInsertValuesDeprecated($table, $fields, &$values) {
396
    return $this->doInsertBatchDanger($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::doInsertBatchDanger() has been deprecated.

This method has been deprecated.

Loading history...
397
  }
398
399
400
401
  // REPLACERS
402
  /**
403
   * Replaces record in DB
404
   *
405
   * There are no DANGER replace operations
406
   *
407
   * @param string $table
408
   * @param array  $fieldsAndValues
409
   *
410
   * @return array|bool|mysqli_result|null
411
   */
412
  public function doReplaceSet($table, $fieldsAndValues) {
413
    return $this->doSet($table, $fieldsAndValues, DB_INSERT_REPLACE);
414
  }
415
  /**
416
   * Values should be passed as-is
417
   *
418
   * @param string   $table
419
   * @param array    $fields
420
   * @param string[] $values
421
   *
422
   * @return array|bool|mysqli_result|null
423
   * @deprecated
424
   */
425
  public function doReplaceValuesDeprecated($table, $fields, &$values) {
426
    return $this->doInsertBatchDanger($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::doInsertBatchDanger() has been deprecated.

This method has been deprecated.

Loading history...
427
  }
428
429
430
  // UPDATERS
431
  public function doUpdateReallyComplex($query) {
432
    return $this->doSql($query);
433
  }
434
435
  /**
436
   * Executes self-contained SQL UPDATE query
437
   *
438
   * Self-contained - means no params used
439
   * Such queries usually used to make large amount of in-base calculations
440
   *
441
   * @param $query
442
   *
443
   * @return array|bool|mysqli_result|null
444
   */
445
  public function doUpdateSqlNoParam($query) {
446
    return $this->doSql($query);
447
  }
448
449
450
451
  /**
452
   * @param $DbQuery DbQuery
453
   */
454
  protected function doUpdateDbQuery($DbQuery) {
455
    return $this->doSql($DbQuery->update());
456
  }
457
458
  /**
459
   * @param $DbQuery DbQuery
460
   */
461
  public function doUpdateDbQueryAdjust($DbQuery) {
462
    return $this->doUpdateDbQuery($DbQuery);
463
  }
464
465
466
  protected function doUpdateWhere($table, $fieldsSet, $fieldsAdjust = array(), $where = array(), $isOneRecord = DB_RECORDS_ALL, $whereDanger = array()) {
0 ignored issues
show
Unused Code introduced by
The parameter $whereDanger is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
467
//    $query = DbQuery::build($this)
0 ignored issues
show
Unused Code Comprehensibility introduced by
52% 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...
468
//      ->setTable($table)
469
//      ->setValues($fieldsSet)
470
//      ->setAdjustDanger($fieldsAdjust)
471
//
472
//      // TODO - separate danger WHEREs
473
//      ->setWhereArray($where)
474
//      ->setWhereArrayDanger($whereDanger)
475
//      ->setOneRow($isOneRecord)
476
//
477
//      ->update();
478
//
479
    $tableSafe = $this->db_escape($table);
480
481
    $safeFields = array();
482
    // Adjusts overwritten by Sets
483
    if ($safeAdjust = implode(',', $this->safeFieldsAdjust($fieldsAdjust))) {
484
      $safeFields[] = &$safeAdjust;
485
    }
486
    if ($safeFieldsEqualValues = implode(',', $this->safeFieldsEqualValues($fieldsSet))) {
487
      $safeFields[] = &$safeFieldsEqualValues;
488
    }
489
    $safeFieldsString = implode(',', $safeFields);
490
491
    // TODO - Exception of $safeFieldsString
492
493
    $safeWhereAnd = implode(' AND ', $this->safeFieldsEqualValues($where));
494
    $query = "UPDATE `{{{$tableSafe}}}` SET {$safeFieldsString}"
495
      . (!empty($safeWhereAnd) ? " WHERE {$safeWhereAnd}" : '')
496
      . ($isOneRecord == DB_RECORD_ONE ? ' LIMIT 1' : '');
497
498
    return $this->doSql($query);
499
  }
500
501
  public function doUpdateRowSet($table, $fieldsAndValues, $where) {
502
    return $this->doUpdateWhere($table, $fieldsAndValues, array(), $where, DB_RECORD_ONE);
503
  }
504
505
  public function doUpdateTableSet($table, $fieldsAndValues, $where = array()) {
506
    return $this->doUpdateWhere($table, $fieldsAndValues, array(), $where, DB_RECORDS_ALL);
507
  }
508
509
  public function doUpdateRowAdjust($table, $fieldsSet, $fieldsAdjust, $where) {
510
    return $this->doUpdateWhere($table, $fieldsSet, $fieldsAdjust, $where, DB_RECORD_ONE);
511
  }
512
513
  public function doUpdateTableAdjust($table, $fieldsSet, $fieldsAdjust, $where, $whereDanger = array()) {
514
    return $this->doUpdateWhere($table, $fieldsSet, $fieldsAdjust, $where, DB_RECORDS_ALL, $whereDanger);
515
  }
516
517
518
  // DELETERS
519
  /**
520
   * @param string $table
521
   * @param array  $where
522
   * @param bool   $isOneRecord
523
   *
524
   * @return DbQuery
525
   */
526
  protected function buildDeleteQuery($table, $where, $isOneRecord = DB_RECORDS_ALL) {
527
    return DbQuery::build($this)
528
      ->setTable($table)
529
      ->setWhereArray($where)
530
      ->setOneRow($isOneRecord);
531
  }
532
533
  /**
534
   * @param string $table
535
   * @param array  $where - simple WHERE statement list which can be combined with AND
536
   * @param bool   $isOneRecord
537
   *
538
   * @return array|bool|mysqli_result|null
539
   */
540
  public function doDeleteWhere($table, $where, $isOneRecord = DB_RECORDS_ALL) {
541
    return $this->doSql($this->buildDeleteQuery($table, $where, $isOneRecord)->delete());
542
  }
543
544
  /**
545
   * Early deprecated function for complex delete conditions
546
   *
547
   * Used for malformed $where conditions
548
   * Also whereDanger can contain references for other {{tables}}
549
   *
550
   * @param string $table
551
   * @param array  $where
552
   * @param array  $whereDanger
553
   *
554
   * @return array|bool|mysqli_result|null
555
   * @deprecated
556
   */
557
  public function doDeleteDanger($table, $where, $whereDanger) {
558
    return $this->doSql($this->buildDeleteQuery($table, $where, DB_RECORDS_ALL)->setWhereArrayDanger($whereDanger)->delete());
559
  }
560
561
  /**
562
   * @param string $table
563
   * @param array  $where - simple WHERE statement list which can be combined with AND
564
   *
565
   * @return array|bool|mysqli_result|null
566
   */
567
  public function doDeleteRow($table, $where) {
568
    return $this->doDeleteWhere($table, $where, DB_RECORD_ONE);
569
  }
570
571
  /**
572
   * Perform simple delete queries on fixed tables w/o params
573
   *
574
   * @param string $query
575
   *
576
   * @return array|bool|mysqli_result|null
577
   */
578
  public function doDeleteSql($query) {
579
    return $this->doSql($query);
580
  }
581
582
583
  // Misc functions
584
  //
585 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...
586
    switch (gettype($value)) {
587
      case TYPE_INTEGER:
588
      case TYPE_DOUBLE:
589
        // do nothing
590
      break;
591
592
      case TYPE_BOOLEAN:
593
        $value = $value ? 1 : 0;
594
      break;
595
596
      case TYPE_NULL:
597
        $value = 'NULL';
598
      break;
599
600
      /** @noinspection PhpMissingBreakStatementInspection */
601
      case TYPE_ARRAY:
602
        $value = serialize($value);
603
      // Continuing with serialized array value
604
      case TYPE_STRING:
605
        // Empty type is string
606
      case TYPE_EMPTY:
607
        // No-type defaults to string
608
      default:
609
        $value = "'" . $this->db_escape((string)$value) . "'";
610
      break;
611
    }
612
613
    return $value;
614
  }
615
616
  /**
617
   * Make field list safe
618
   *
619
   * Support expressions - expression index should be strictly integer!
620
   *
621
   * @param array $fields - array of pair $fieldName => $fieldValue
622
   *
623
   * @return array
624
   */
625 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...
626
    $result = array();
627
628
    if (!is_array($fields) || empty($fields)) {
629
      return $result;
630
    }
631
632
    foreach ($fields as $fieldName => $fieldValue) {
633
      // Integer $fieldName means "leave as is" - for expressions and already processed fields
634
      if (is_int($fieldName)) {
635
        $result[$fieldName] = $fieldValue;
636
      } else {
637
        $result[$fieldName] = "`{$fieldName}` = " . $this->castAsDbValue($fieldValue);
638
      }
639
    }
640
641
    return $result;
642
  }
643
644
  /**
645
   * Make fields adjustment safe
646
   *
647
   * Convert "key => value" pair to string "`key` = `key` + (value)"
648
   * Supports expressions - expression index should be strictly integer!
649
   *
650
   * @param array $fields - array of pair $fieldName => $fieldValue
651
   *
652
   * @return array
653
   */
654 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...
655
    $result = array();
656
657
    if (!is_array($fields) || empty($fields)) {
658
      return $result;
659
    }
660
661
    foreach ($fields as $fieldName => $fieldValue) {
662
      // Integer $fieldName means "leave as is" - for expressions and already processed fields
663
      if (is_int($fieldName)) {
664
        $result[$fieldName] = $fieldValue;
665
      } else {
666
        $result[$fieldName] = "`{$fieldName}` = `{$fieldName}` + (" . $this->castAsDbValue($fieldValue) . ")";
667
      }
668
    }
669
670
    return $result;
671
  }
672
673
674
  /**
675
   * Returns iterator to iterate through mysqli_result
676
   *
677
   * @param string $query
678
   *
679
   * return DbResultIterator
680
   *
681
   * @return DbEmptyIterator|DbMysqliResultIterator
682
   */
683
  public function doSelectIterator($query) {
684
    $queryResult = $this->doSelect($query);
685
686
    if ($queryResult instanceof mysqli_result) {
687
      $result = new DbMysqliResultIterator($queryResult);
688
    } else {
689
      $result = new DbEmptyIterator();
690
    }
691
692
    return $result;
693
  }
694
695
  /**
696
   * @param DbQueryConstructor $stmt
697
   * @param bool               $skip_query_check
698
   */
699
  public function doStmtLockAll($stmt, $skip_query_check = false) {
700
    $this->doSql(
701
      $stmt
702
        ->select()
703
        ->field(1)
704
        ->setForUpdate()
705
        ->__toString(),
706
      $skip_query_check
707
    );
708
  }
709
710
  // TODO Заменить это на новый логгер
711
  protected function security_watch_user_queries($query) {
712
    global $user;
713
714
    if (
715
      !$this->isWatching // Not already watching
716
      && !empty(classSupernova::$config->game_watchlist_array) // There is some players in watchlist
717
      && in_array($user['id'], classSupernova::$config->game_watchlist_array) // Current player is in watchlist
718
      && !preg_match('/^(select|commit|rollback|start transaction)/i', $query) // Current query should be watched
719
    ) {
720
      $this->isWatching = true;
721
      $msg = "\$query = \"{$query}\"\n\r";
722
      if (!empty($_POST)) {
723
        $msg .= "\n\r" . dump($_POST, '$_POST');
724
      }
725
      if (!empty($_GET)) {
726
        $msg .= "\n\r" . dump($_GET, '$_GET');
727
      }
728
      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...
729
      $this->isWatching = false;
730
    }
731
  }
732
733
734
  public function security_query_check_bad_words($query) {
735
    if ($this->skipQueryCheck) {
736
      return;
737
    }
738
739
    global $user, $dm_change_legit, $mm_change_legit;
740
741
    switch (true) {
742
      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...
743
      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...
744
      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...
745
      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...
746
      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...
747
      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...
748
      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...
749
      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...
750
      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...
751
      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...
752
        $report = "Hacking attempt (" . date("d.m.Y H:i:s") . " - [" . time() . "]):\n";
753
        $report .= ">Database Inforamation\n";
754
        $report .= "\tID - " . $user['id'] . "\n";
755
        $report .= "\tUser - " . $user['username'] . "\n";
756
        $report .= "\tAuth level - " . $user['authlevel'] . "\n";
757
        $report .= "\tAdmin Notes - " . $user['adminNotes'] . "\n";
758
        $report .= "\tCurrent Planet - " . $user['current_planet'] . "\n";
759
        $report .= "\tUser IP - " . $user['user_lastip'] . "\n";
760
        $report .= "\tUser IP at Reg - " . $user['ip_at_reg'] . "\n";
761
        $report .= "\tUser Agent- " . $_SERVER['HTTP_USER_AGENT'] . "\n";
762
        $report .= "\tCurrent Page - " . $user['current_page'] . "\n";
763
        $report .= "\tRegister Time - " . $user['register_time'] . "\n";
764
        $report .= "\n";
765
766
        $report .= ">Query Information\n";
767
        $report .= "\tQuery - " . $query . "\n";
768
        $report .= "\n";
769
770
        $report .= ">\$_SERVER Information\n";
771
        $report .= "\tIP - " . $_SERVER['REMOTE_ADDR'] . "\n";
772
        $report .= "\tHost Name - " . $_SERVER['HTTP_HOST'] . "\n";
773
        $report .= "\tUser Agent - " . $_SERVER['HTTP_USER_AGENT'] . "\n";
774
        $report .= "\tRequest Method - " . $_SERVER['REQUEST_METHOD'] . "\n";
775
        $report .= "\tCame From - " . $_SERVER['HTTP_REFERER'] . "\n";
776
        $report .= "\tPage is - " . $_SERVER['SCRIPT_NAME'] . "\n";
777
        $report .= "\tUses Port - " . $_SERVER['REMOTE_PORT'] . "\n";
778
        $report .= "\tServer Protocol - " . $_SERVER['SERVER_PROTOCOL'] . "\n";
779
780
        $report .= "\n--------------------------------------------------------------------------------------------------\n";
781
782
        $fp = fopen(SN_ROOT_PHYSICAL . 'badqrys.txt', 'a');
783
        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 775
  1. Fetching key HTTP_REFERER from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 775
  2. Path: Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned in includes/classes/db_mysql.php on line 773
  1. Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 773
  2. $report is assigned
    in includes/classes/db_mysql.php on line 775
  3. Path: Fetching key HTTP_HOST from $_SERVER, and $report is assigned in includes/classes/db_mysql.php on line 772
  1. Fetching key HTTP_HOST from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 772
  2. $report is assigned
    in includes/classes/db_mysql.php on line 773
  3. $report is assigned
    in includes/classes/db_mysql.php on line 775
  4. Path: Read from $_GET in includes/classes/db_mysql.php on line 726
  1. Read from $_GET
    in includes/classes/db_mysql.php on line 726
  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 726
  4. $msg is passed to debug::warning()
    in includes/classes/db_mysql.php on line 728
  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 268
  9. $stringQuery is assigned
    in includes/classes/db_mysql.php on line 222
  10. $stringQuery is passed through trim(), and $stringQuery is assigned
    in includes/classes/db_mysql.php on line 223
  11. $stringQuery is passed to db_mysql::security_query_check_bad_words()
    in includes/classes/db_mysql.php on line 228
  12. $report is assigned
    in includes/classes/db_mysql.php on line 767
  13. $report is assigned
    in includes/classes/db_mysql.php on line 772
  14. $report is assigned
    in includes/classes/db_mysql.php on line 773
  15. $report is assigned
    in includes/classes/db_mysql.php on line 775
  5. Path: Read from $_POST in includes/classes/db_mysql.php on line 723
  1. Read from $_POST
    in includes/classes/db_mysql.php on line 723
  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 723
  4. $msg is passed to debug::warning()
    in includes/classes/db_mysql.php on line 728
  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 268
  9. $stringQuery is assigned
    in includes/classes/db_mysql.php on line 222
  10. $stringQuery is passed through trim(), and $stringQuery is assigned
    in includes/classes/db_mysql.php on line 223
  11. $stringQuery is passed to db_mysql::security_query_check_bad_words()
    in includes/classes/db_mysql.php on line 228
  12. $report is assigned
    in includes/classes/db_mysql.php on line 767
  13. $report is assigned
    in includes/classes/db_mysql.php on line 772
  14. $report is assigned
    in includes/classes/db_mysql.php on line 773
  15. $report is assigned
    in includes/classes/db_mysql.php on line 775
  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 1303
  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 268
  9. $stringQuery is assigned
    in includes/classes/db_mysql.php on line 222
  10. $stringQuery is passed through trim(), and $stringQuery is assigned
    in includes/classes/db_mysql.php on line 223
  11. $stringQuery is passed to db_mysql::security_query_check_bad_words()
    in includes/classes/db_mysql.php on line 228
  12. $report is assigned
    in includes/classes/db_mysql.php on line 767
  13. $report is assigned
    in includes/classes/db_mysql.php on line 772
  14. $report is assigned
    in includes/classes/db_mysql.php on line 773
  15. $report is assigned
    in includes/classes/db_mysql.php on line 775
  7. Path: Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned in includes/classes/db_mysql.php on line 761
  1. Fetching key HTTP_USER_AGENT 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 767
  3. $report is assigned
    in includes/classes/db_mysql.php on line 772
  4. $report is assigned
    in includes/classes/db_mysql.php on line 773
  5. $report is assigned
    in includes/classes/db_mysql.php on line 775

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