Completed
Push — work-fleets ( 6257a1...22b5bc )
by SuperNova.WS
06:47
created

db_mysql::doInsertBatchDanger()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 7
c 1
b 0
f 1
nc 1
nop 4
dl 0
loc 9
ccs 0
cts 0
cp 0
crap 2
rs 9.6666
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
   * @param string $query
284
   *
285
   * @return array|null
286
   */
287
  public function doSelectFetch($query) {
288
    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...
289
  }
290
291
  /**
292
   * @param string $query
293
   *
294
   * @return mixed|null
295
   */
296
  public function doSelectFetchValue($query) {
297
    $row = $this->doSelectFetch($query);
298
299
    return is_array($row) ? reset($row) : null;
300
  }
301
302
303
  // INSERT/REPLACE
304
  protected function doSet($table, $fieldsAndValues, $replace = DB_INSERT_PLAIN) {
305
    $query = DbQuery::build($this)
306
      ->setTable($table)
307
      ->setValues($fieldsAndValues)
308
      ->insertSet($replace);
309
310
    return $this->doSql($query);
311
  }
312
313
  // TODO - batch insert and replace here
314
  // TODO - перед тем, как переделывать данные из депрекейтов - убедится, что
315
  // null - это null, а не строка'NULL'
316
  /**
317
   * Values should be passed as-is
318
   *
319
   * DANGER! Values should be properly escaped before passing here
320
   *
321
   * @param string   $table
322
   * @param array    $fields
323
   * @param string[] $valuesDanger
324
   * @param bool     $replace
325
   *
326
   * @return array|bool|mysqli_result|null
327
   * @deprecated
328
   */
329
  protected function doInsertBatchDanger($table, $fields, &$valuesDanger, $replace = DB_INSERT_PLAIN) {
330
    $query = DbQuery::build($this)
331
      ->setTable($table)
332
      ->setFields($fields)
333
      ->setValuesDanger($valuesDanger)
334
      ->insertBatch($replace);
335
336
    return $this->doSql($query);
337
  }
338
339
340
  // INSERTERS
341
  public function doInsertComplex($query) {
342
    return $this->doSql($query);
343
  }
344
345
  /**
346
   * @param string $table
347
   * @param array  $fieldsAndValues
348
   * @param int    $replace - DB_INSERT_PLAIN || DB_INSERT_IGNORE
349
   *
350
   * @return array|bool|mysqli_result|null
351
   */
352
  public function doInsertSet($table, $fieldsAndValues, $replace = DB_INSERT_PLAIN) {
353
    return $this->doSet($table, $fieldsAndValues, $replace);
354
  }
355
356
357
  /**
358
   * Values should be passed as-is
359
   *
360
   * @param string   $table
361
   * @param array    $fields
362
   * @param string[] $values
363
   *
364
   * @return array|bool|mysqli_result|null
365
   * @deprecated
366
   */
367
  public function doInsertValuesDeprecated($table, $fields, &$values) {
368
    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...
369
  }
370
371
372
373
  // REPLACERS
374
  /**
375
   * Replaces record in DB
376
   *
377
   * There are no DANGER replace operations
378
   *
379
   * @param string $table
380
   * @param array  $fieldsAndValues
381
   *
382
   * @return array|bool|mysqli_result|null
383
   */
384
  public function doReplaceSet($table, $fieldsAndValues) {
385
    return $this->doSet($table, $fieldsAndValues, DB_INSERT_REPLACE);
386
  }
387
388
  /**
389
   * Values should be passed as-is
390
   *
391
   * @param string   $table
392
   * @param array    $fields
393
   * @param string[] $values
394
   *
395
   * @return array|bool|mysqli_result|null
396
   * @deprecated
397
   */
398
  public function doReplaceValuesDeprecated($table, $fields, &$values) {
399
    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...
400
  }
401
402
403
  // UPDATERS
404
  public function doUpdateComplex($query) {
405
    return $this->doSql($query);
406
  }
407
408
  public function doUpdateReallyComplex($query) {
409
    return $this->doSql($query);
410
  }
411
412
  /**
413
   * Executes self-contained SQL UPDATE query
414
   *
415
   * Self-contained - means no params used
416
   * Such queries usually used to make large amount of in-base calculations
417
   *
418
   * @param $query
419
   *
420
   * @return array|bool|mysqli_result|null
421
   */
422
  public function doUpdateSqlNoParam($query) {
423
    return $this->doSql($query);
424
  }
425
426
  protected function doUpdateWhere($table, $fieldsSet, $fieldsAdjust = array(), $where = array(), $isOneRecord = DB_RECORDS_ALL) {
427
//    $query = DbQuery::build($this)
0 ignored issues
show
Unused Code Comprehensibility introduced by
51% 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...
428
//      ->setTable($table)
429
//      ->setValues($fieldsSet)
430
//      ->setAdjustDanger($fieldsAdjust)
431
//
432
//      // TODO - separate danger WHEREs
433
//      ->setWhereArray($where)
434
//      ->setOneRow($isOneRecord)
435
//
436
//      ->update();
437
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
   * For update_old - would be deprecated
478
   *
479
   * @param string $query
480
   *
481
   * @return array|bool|mysqli_result|null
482
   * @deprecated
483
   */
484
  public function doUpdateOld($query) {
485
    return $this->doSql($query);
486
  }
487
488
489
490
  // DELETERS
491
  /**
492
   * @param string $table
493
   * @param array  $where
494
   * @param bool   $isOneRecord
495
   *
496
   * @return DbQuery
497
   */
498
  protected function buildDeleteQuery($table, $where, $isOneRecord = DB_RECORDS_ALL) {
499
    return DbQuery::build($this)
500
      ->setTable($table)
501
      ->setWhereArray($where)
502
      ->setOneRow($isOneRecord);
503
  }
504
505
  /**
506
   * @param string $table
507
   * @param array  $where - simple WHERE statement list which can be combined with AND
508
   * @param bool   $isOneRecord
509
   *
510
   * @return array|bool|mysqli_result|null
511
   */
512
  public function doDeleteWhere($table, $where, $isOneRecord = DB_RECORDS_ALL) {
513
    return $this->doSql($this->buildDeleteQuery($table, $where, $isOneRecord)->delete());
514
  }
515
516
  /**
517
   * Early deprecated function for complex delete conditions
518
   *
519
   * Used for malformed $where conditions
520
   * Also whereDanger can contain references for other {{tables}}
521
   *
522
   * @param string $table
523
   * @param array  $where
524
   * @param array  $whereDanger
525
   *
526
   * @return array|bool|mysqli_result|null
527
   * @deprecated
528
   */
529
  public function doDeleteDanger($table, $where, $whereDanger) {
530
    return $this->doSql($this->buildDeleteQuery($table, $where, DB_RECORDS_ALL)->setWhereArrayDanger($whereDanger)->delete());
0 ignored issues
show
Deprecated Code introduced by
The method DBAL\DbQuery::setWhereArrayDanger() has been deprecated.

This method has been deprecated.

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

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