Completed
Push — work-fleets ( c94fa6...bf14b2 )
by SuperNova.WS
06:23
created

db_mysql::doUpdateTableAdjustDanger()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 4
dl 0
loc 3
rs 10
ccs 0
cts 0
cp 0
crap 2
1
<?php
2
3
define('DB_INSERT_PLAIN', 0);
4
define('DB_INSERT_REPLACE', 1);
5
define('DB_INSERT_IGNORE', 2);
6
7
define('DB_RECORDS_ALL', false);
8
define('DB_RECORD_ONE', true);
9
10
/**
11
 * Created by Gorlum 01.09.2015 15:58
12
 */
13
class db_mysql {
14
  const TRANSACTION_SERIALIZABLE = 'SERIALIZABLE';
15
  const TRANSACTION_REPEATABLE_READ = 'REPEATABLE READ';
16
  const TRANSACTION_READ_COMMITTED = 'READ COMMITTED';
17
  const TRANSACTION_READ_UNCOMMITTED = 'READ UNCOMMITTED';
18
19
  /**
20
   * Статус соеднения с MySQL
21
   *
22
   * @var bool
23
   */
24
  public $connected = false;
25
  /**
26
   * Префикс названий таблиц в БД
27
   *
28
   * @var string
29
   */
30
  public $db_prefix = '';
31
  /**
32
   * Список таблиц в БД
33
   *
34
   * @var array
35
   */
36
  public $table_list = array();
37
38
  /**
39
   * Настройки БД
40
   *
41
   * @var array
42
   */
43
  protected $dbsettings = array();
44
  /**
45
   * Драйвер для прямого обращения к MySQL
46
   *
47
   * @var db_mysql_v5 $driver
48
   */
49
  public $driver = null;
50
51
  /**
52
   * Общее время запросов
53
   *
54
   * @var float $time_mysql_total
55
   */
56
  public $time_mysql_total = 0.0;
57
58
  /**
59
   * Amount of queries on this DB
60
   *
61
   * @var int
62
   */
63
  public $queryCount = 0;
64
65
  public $isWatching = false;
66
67
  /**
68
   * @var \DBAL\DbTransaction $transaction
69
   */
70
  protected $transaction;
71
72
  /**
73
   * Should query check be skipped?
74
   *
75
   * Used for altering scheme of DB
76
   *
77
   * @var bool $skipQueryCheck
78
   */
79
  protected $skipQueryCheck = false;
80
81
  /**
82
   * @var SnCache $snCache
83
   */
84
  public $snCache;
85
86
  /**
87
   * db_mysql constructor.
88
   *
89
   * @param \Common\GlobalContainer $gc
90
   */
91
  public function __construct($gc) {
92
    $this->transaction = new \DBAL\DbTransaction($gc, $this);
93
    $this->snCache = new $gc->snCacheClass($gc, $this);
94
  }
95
96
  public function load_db_settings($configFile = '') {
97
    $dbsettings = array();
98
99
    empty($configFile) ? $configFile = SN_ROOT_PHYSICAL . "config" . DOT_PHP_EX : false;
100
101
    require $configFile;
102
103
    $this->dbsettings = $dbsettings;
104
  }
105
106
  /**
107
   * @param null|array $external_db_settings
108
   *
109
   * @return bool
110
   */
111
  public function sn_db_connect($external_db_settings = null) {
112
    $this->db_disconnect();
113
114
    if (!empty($external_db_settings) && is_array($external_db_settings)) {
115
      $this->dbsettings = $external_db_settings;
116
    }
117
118
    if (empty($this->dbsettings)) {
119
      $this->load_db_settings(SN_ROOT_PHYSICAL . "config" . DOT_PHP_EX);
120
    }
121
122
    // TODO - фатальные (?) ошибки на каждом шагу. Хотя - скорее Эксепшны
123
    if (!empty($this->dbsettings)) {
124
      $driver_name = empty($this->dbsettings['sn_driver']) ? 'db_mysql_v5' : $this->dbsettings['sn_driver'];
125
      $this->driver = new $driver_name();
126
      $this->db_prefix = $this->dbsettings['prefix'];
127
128
      $this->connected = $this->connected || $this->driver_connect();
129
130
      if ($this->connected) {
131
        $this->table_list = $this->db_get_table_list();
132
        // TODO Проверка на пустоту
133
      }
134
    } else {
135
      $this->connected = false;
136
    }
137
138
    return $this->connected;
139
  }
140
141
  protected function driver_connect() {
142
    if (!is_object($this->driver)) {
143
      classSupernova::$debug->error_fatal('DB Error - No driver for MySQL found!');
144
    }
145
146
    if (!method_exists($this->driver, 'mysql_connect')) {
147
      classSupernova::$debug->error_fatal('DB Error - WRONG MySQL driver!');
148
    }
149
150
    return $this->driver->mysql_connect($this->dbsettings);
151
  }
152
153
  public function db_disconnect() {
154
    if ($this->connected) {
155
      $this->connected = !$this->driver_disconnect();
156
      $this->connected = false;
157
    }
158
159
    return !$this->connected;
160
  }
161
162
  /**
163
   * @param string $query
164
   *
165
   * @return mixed|string
166
   */
167
  public function replaceTablePlaceholders($query) {
168
    $sql = $query;
169
    if (strpos($sql, '{{') !== false) {
170
      foreach ($this->table_list as $tableName) {
171
        $sql = str_replace("{{{$tableName}}}", $this->db_prefix . $tableName, $sql);
172
      }
173
    }
174
175
    return $sql;
176
  }
177
178
  /**
179
   * @param $query
180
   */
181
  protected function logQuery($query) {
182
    if (!classSupernova::$config->debug) {
183
      return;
184
    }
185
186
    $this->queryCount++;
187
    $arr = debug_backtrace();
188
    $file = end(explode('/', $arr[0]['file']));
0 ignored issues
show
Bug introduced by
explode('/', $arr[0]['file']) cannot be passed to end() as the parameter $array expects a reference.
Loading history...
189
    $line = $arr[0]['line'];
190
    classSupernova::$debug->add("<tr><th>Query {$this->queryCount}: </th><th>$query</th><th>{$file} @ {$line}</th><th>&nbsp;</th></tr>");
191
  }
192
193
194
  /**
195
   * @return string
196
   */
197
  public function queryTrace() {
198
    if (!defined('DEBUG_SQL_COMMENT') || constant('DEBUG_SQL_ERROR') !== true) {
199
      return '';
200
    }
201
202
    $backtrace = debug_backtrace();
203
    $sql_comment = classSupernova::$debug->compact_backtrace($backtrace, defined('DEBUG_SQL_COMMENT_LONG'));
204
205
    if (defined('DEBUG_SQL_ERROR') && constant('DEBUG_SQL_ERROR') === true) {
206
      classSupernova::$debug->add_to_array($sql_comment);
207
    }
208
209
    $sql_commented = implode("\r\n", $sql_comment);
210
    if (defined('DEBUG_SQL_ONLINE') && constant('DEBUG_SQL_ONLINE') === true) {
211
      classSupernova::$debug->warning($sql_commented, 'SQL Debug', LOG_DEBUG_SQL);
212
    }
213
214
    return $sql_commented;
215
  }
216
217
  /**
218
   * @param string $query
219
   *
220
   * @return array|bool|mysqli_result|null
221
   * @internal param bool $skip_query_check
222
   *
223
   */
224
  protected function doquery($query) {
225
    if (!$this->connected) {
226
      $this->sn_db_connect();
227
    }
228
229
    $stringQuery = $query;
230
    $stringQuery = trim($stringQuery);
231
    // You can't do it - 'cause you can break commented statement with line-end comments
232
    // $stringQuery = preg_replace("/\s+/", ' ', $stringQuery);
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
233
234
    $this->security_watch_user_queries($stringQuery);
235
    $this->security_query_check_bad_words($stringQuery);
236
    $this->logQuery($stringQuery);
237
238
    $stringQuery = $this->replaceTablePlaceholders($stringQuery);
239
240
    $queryTrace = $this->queryTrace();
241
242
    $queryResult = null;
243
    try {
244
      $queryResult = $this->db_sql_query($stringQuery . DbSqlHelper::quoteComment($queryTrace));
245
      if (!$queryResult) {
246
        throw new Exception();
247
      }
248
    } catch (Exception $e) {
249
      classSupernova::$debug->error($this->db_error() . "<br />{$query}<br />", 'SQL Error');
250
    }
251
252
    return $queryResult;
253
  }
254
255
256
  // Just wrappers to distinguish query types
257
  /**
258
   * Executes non-data manipulation statements
259
   *
260
   * Can execute queries with check skip
261
   * Honor current state of query checking
262
   *
263
   * @param string $query
264
   * @param bool   $skip_query_check
265
   *
266
   * @return array|bool|mysqli_result|null
267
   */
268
  public function doExecute($query, $skip_query_check = false) {
269
    $prevState = false;
270
    if ($skip_query_check) {
271
      $prevState = $this->skipQueryCheck;
272
      $this->skipQueryCheck = true;
273
    }
274
    // TODO - disable watch ??
275
    $result = $this->doquery($query);
276
    if ($skip_query_check) {
277
      $this->skipQueryCheck = $prevState;
278
    }
279
280
    return $result;
281
  }
282
283
284
  public function doSelect($query) {
285
    return $this->doExecute($query);
286
  }
287
288
  /**
289
   * @param string $query
290
   *
291
   * @return array|null
292
   */
293
  public function doSelectFetch($query) {
294
    return $this->db_fetch($this->doSelect($query));
0 ignored issues
show
Bug introduced by
$this->doSelect($query) cannot be passed to db_fetch() as the parameter $query expects a reference.
Loading history...
Bug introduced by
It seems like $this->doSelect($query) targeting db_mysql::doSelect() can also be of type boolean; however, db_mysql::db_fetch() does only seem to accept object<mysqli_result>, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

This method has been deprecated.

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

This method has been deprecated.

Loading history...
401
  }
402
403
404
  public function doUpdateComplex($query) {
405
    return $this->doExecute($query);
406
  }
407
408
  public function doUpdateReallyComplex($query) {
409
    return $this->doExecute($query);
410
  }
411
412
  /**
413
   * Executes self-contained SQL UPDATE query
414
   *
415
   * Self-contained - means no params used
416
   * Such queries usually used to make large amount of in-base calculations
417
   *
418
   * @param $query
419
   *
420
   * @return array|bool|mysqli_result|null
421
   */
422
  public function doUpdateSqlNoParam($query) {
423
    return $this->doExecute($query);
424
  }
425
426
  protected function doUpdateWhere($table, $fieldsSet, $fieldsAdjust = array(), $where = array(), $isOneRecord = DB_RECORDS_ALL) {
427
    $tableSafe = $this->db_escape($table);
428
429
    $safeFields = array();
430
    // Adjusts overwritten by Sets
431
    if($safeAdjust = implode(',', $this->safeFieldsAdjust($fieldsAdjust))) {
432
      $safeFields[] = &$safeAdjust;
433
    }
434
    if($safeFieldsEqualValues = implode(',', $this->safeFieldsEqualValues($fieldsSet))) {
435
      $safeFields[] = &$safeFieldsEqualValues;
436
    }
437
    $safeFieldsString = implode(',', $safeFields);
438
439
    // TODO - Exception of $safeFieldsString
440
441
    $safeWhereAnd = implode(' AND ', $this->safeFieldsEqualValues($where));
442
    $query = "UPDATE `{{{$tableSafe}}}` SET {$safeFieldsString}"
443
      . (!empty($safeWhereAnd) ? " WHERE {$safeWhereAnd}" : '')
444
      . ($isOneRecord == DB_RECORD_ONE ? ' LIMIT 1' : '');
445
446
    return $this->doExecute($query);
447
  }
448
449
  public function doUpdateRowSet($table, $fieldsAndValues, $where) {
450
    return $this->doUpdateWhere($table, $fieldsAndValues, array(), $where, DB_RECORD_ONE);
451
  }
452
453
  public function doUpdateTableSet($table, $fieldsAndValues, $where = array()) {
454
    return $this->doUpdateWhere($table, $fieldsAndValues, array(), $where, DB_RECORDS_ALL);
455
  }
456
457
  public function doUpdateRowAdjust($table, $fieldsSet, $fieldsAdjust, $where) {
458
    return $this->doUpdateWhere($table, $fieldsSet, $fieldsAdjust, $where, DB_RECORD_ONE);
459
  }
460
461
  public function doUpdateTableAdjust($table, $fieldsSet, $fieldsAdjust, $where) {
462
    return $this->doUpdateWhere($table, $fieldsSet, $fieldsAdjust, $where, DB_RECORDS_ALL);
463
  }
464
465
  /**
466
   * In this call means that used some conditions in $where different than field = value
467
   *
468
   * It should be rewrote later
469
   *
470
   * @param $table
471
   * @param $fieldsSet
472
   * @param $fieldsAdjust
473
   * @param $where
474
   *
475
   * @return array|bool|mysqli_result|null
476
   * @deprecated
477
   */
478
  public function doUpdateTableAdjustDanger($table, $fieldsSet, $fieldsAdjust, $where) {
479
    return $this->doUpdateWhere($table, $fieldsSet, $fieldsAdjust, $where, DB_RECORDS_ALL);
480
  }
481
482
  public function doUpdateAdjustDeprecated($query) {
483
    return $this->doExecute($query);
484
  }
485
486
487
488
  /**
489
   * For update_old - would be deprecated
490
   *
491
   * @param string $query
492
   *
493
   * @return array|bool|mysqli_result|null
494
   * @deprecated
495
   */
496
  public function doUpdateOld($query) {
497
    return $this->doExecute($query);
498
  }
499
500
501
  /**
502
   * @param string $query
503
   *
504
   * @return array|bool|mysqli_result|null
505
   */
506
  public function doDelete($query) {
507
    return $this->doExecute($query);
508
  }
509
510
  /**
511
   * @param string $table
512
   * @param array  $where - simple WHERE statement list which can be combined with AND
513
   * @param bool   $isOneRecord
514
   *
515
   * @return array|bool|mysqli_result|null
516
   */
517
  public function doDeleteWhere($table, $where, $isOneRecord = false) {
518
    $tableSafe = $this->db_escape($table);
519
    $safeWhere = implode(' AND ', $this->safeFieldsEqualValues($where));
520
    $query = "DELETE FROM `{{{$tableSafe}}}` WHERE {$safeWhere}"
521
      . ($isOneRecord ? ' LIMIT 1' : '');
522
523
    return $this->doDelete($query);
524
  }
525
526
  /**
527
   * @param string $table
528
   * @param array  $where - simple WHERE statement list which can be combined with AND
529
   *
530
   * @return array|bool|mysqli_result|null
531
   */
532
  public function doDeleteRowWhere($table, $where) {
533
    return $this->doDeleteWhere($table, $where, true);
534
  }
535
536
  /**
537
   * @param string $query
538
   *
539
   * @return array|bool|mysqli_result|null
540
   */
541
  public function doDeleteComplex($query) {
542
    return $this->doDelete($query);
543
  }
544
545
  /**
546
   * Early deprecated function for complex delete conditions
547
   *
548
   * Usually used for mallformed $where conditions
549
   *
550
   * @param $table
551
   * @param $where
552
   *
553
   * @return array|bool|mysqli_result|null
554
   * @deprecated
555
   */
556
  public function doDeleteDeprecated($table, $where) {
557
    return $this->doDeleteWhere($table, $where, false);
558
  }
559
560
561
  protected function castAsDbValue($value) {
562
    switch(gettype($value)) {
563
      case TYPE_INTEGER:
564
      case TYPE_DOUBLE:
565
        // do nothing
566
      break;
567
568
      case TYPE_BOOLEAN:
569
        $value = $value ? 1 : 0;
570
      break;
571
572
      case TYPE_NULL:
573
        $value = 'NULL';
574
      break;
575
576
      /** @noinspection PhpMissingBreakStatementInspection */
577
      case TYPE_ARRAY:
578
        $value = serialize($value);
579
      // Continuing with serialized array value
580
      case TYPE_STRING:
581
        // Empty type is string
582
      case TYPE_EMPTY:
583
        // No-type defaults to string
584
      default:
585
        $value = "'" . $this->db_escape((string)$value) . "'";
586
      break;
587
    }
588
589
    return $value;
590
  }
591
592
  /**
593
   * Make field list safe
594
   *
595
   * Support expressions - expression index should be strictly integer!
596
   *
597
   * @param array $fields - array of pair $fieldName => $fieldValue
598
   *
599
   * @return array
600
   */
601 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...
602
    $result = array();
603
604
    if (!is_array($fields) || empty($fields)) {
605
      return $result;
606
    }
607
608
    foreach ($fields as $fieldName => $fieldValue) {
609
      // Integer $fieldName means "leave as is" - for expressions and already processed fields
610
      if (is_int($fieldName)) {
611
        $result[$fieldName] = $fieldValue;
612
      } else {
613
        $result[$fieldName] = "`{$fieldName}` = " . $this->castAsDbValue($fieldValue);
614
      }
615
    }
616
617
    return $result;
618
  }
619
620
  /**
621
   * Make fields adjustment safe
622
   *
623
   * Convert "key => value" pair to string "`key` = `key` + (value)"
624
   * Supports expressions - expression index should be strictly integer!
625
   *
626
   * @param array $fields - array of pair $fieldName => $fieldValue
627
   *
628
   * @return array
629
   */
630 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...
631
    $result = array();
632
633
    if (!is_array($fields) || empty($fields)) {
634
      return $result;
635
    }
636
637
    foreach ($fields as $fieldName => $fieldValue) {
638
      // Integer $fieldName means "leave as is" - for expressions and already processed fields
639
      if (is_int($fieldName)) {
640
        $result[$fieldName] = $fieldValue;
641
      } else {
642
        $result[$fieldName] = "`{$fieldName}` = `{$fieldName}` + (" . $this->castAsDbValue($fieldValue) . ")";
643
      }
644
    }
645
646
    return $result;
647
  }
648
649
  // TODO - redo as callable usage with array_map/array_walk
650 View Code Duplication
  public function safeValues($values) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
651
    $result = array();
652
653
    if (!is_array($values) || empty($values)) {
654
      return $result;
655
    }
656
657
    foreach ($values as $key => $value) {
658
      $result[$key] = $this->castAsDbValue($value);
659
    }
660
661
    return $result;
662
  }
663
664
  // TODO - redo as callable usage with array_map/array_walk
665 View Code Duplication
  public function safeFields($fields) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
666
    $result = array();
667
668
    if (!is_array($fields) || empty($fields)) {
669
      return $result;
670
    }
671
672
    foreach ($fields as $key => $value) {
673
      $result[$key] = "`" . $this->db_escape($value) . "`";
674
    }
675
676
    return $result;
677
  }
678
679
680
  /**
681
   * Returns iterator to iterate through mysqli_result
682
   *
683
   * @param string $query
684
   *
685
   * return DbResultIterator
686
   *
687
   * @return DbEmptyIterator|DbMysqliResultIterator
688
   */
689
  public function doSelectIterator($query) {
690
    $queryResult = $this->doSelect($query);
691
692
    if ($queryResult instanceof mysqli_result) {
693
      $result = new DbMysqliResultIterator($queryResult);
694
    } else {
695
      $result = new DbEmptyIterator();
696
    }
697
698
    return $result;
699
  }
700
701
  /**
702
   * @param DbQueryConstructor $stmt
703
   * @param bool               $skip_query_check
704
   */
705
  public function doStmtLockAll($stmt, $skip_query_check = false) {
706
    $this->doExecute(
707
      $stmt
708
        ->select()
709
        ->field(1)
710
        ->setForUpdate()
711
        ->__toString(),
712
      $skip_query_check
713
    );
714
  }
715
716
  // TODO Заменить это на новый логгер
717
  protected function security_watch_user_queries($query) {
718
    global $user;
719
720
    if (
721
      !$this->isWatching // Not already watching
722
      && !empty(classSupernova::$config->game_watchlist_array) // There is some players in watchlist
723
      && in_array($user['id'], classSupernova::$config->game_watchlist_array) // Current player is in watchlist
724
      && !preg_match('/^(select|commit|rollback|start transaction)/i', $query) // Current query should be watched
725
    ) {
726
      $this->isWatching = true;
727
      $msg = "\$query = \"{$query}\"\n\r";
728
      if (!empty($_POST)) {
729
        $msg .= "\n\r" . dump($_POST, '$_POST');
730
      }
731
      if (!empty($_GET)) {
732
        $msg .= "\n\r" . dump($_GET, '$_GET');
733
      }
734
      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...
735
      $this->isWatching = false;
736
    }
737
  }
738
739
740
  public function security_query_check_bad_words($query) {
741
    if ($this->skipQueryCheck) {
742
      return;
743
    }
744
745
    global $user, $dm_change_legit, $mm_change_legit;
746
747
    switch(true) {
748
      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...
749
      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...
750
      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...
751
      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...
752
      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...
753
      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...
754
      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...
755
      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...
756
      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...
757
      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...
758
        $report = "Hacking attempt (" . date("d.m.Y H:i:s") . " - [" . time() . "]):\n";
759
        $report .= ">Database Inforamation\n";
760
        $report .= "\tID - " . $user['id'] . "\n";
761
        $report .= "\tUser - " . $user['username'] . "\n";
762
        $report .= "\tAuth level - " . $user['authlevel'] . "\n";
763
        $report .= "\tAdmin Notes - " . $user['adminNotes'] . "\n";
764
        $report .= "\tCurrent Planet - " . $user['current_planet'] . "\n";
765
        $report .= "\tUser IP - " . $user['user_lastip'] . "\n";
766
        $report .= "\tUser IP at Reg - " . $user['ip_at_reg'] . "\n";
767
        $report .= "\tUser Agent- " . $_SERVER['HTTP_USER_AGENT'] . "\n";
768
        $report .= "\tCurrent Page - " . $user['current_page'] . "\n";
769
        $report .= "\tRegister Time - " . $user['register_time'] . "\n";
770
        $report .= "\n";
771
772
        $report .= ">Query Information\n";
773
        $report .= "\tQuery - " . $query . "\n";
774
        $report .= "\n";
775
776
        $report .= ">\$_SERVER Information\n";
777
        $report .= "\tIP - " . $_SERVER['REMOTE_ADDR'] . "\n";
778
        $report .= "\tHost Name - " . $_SERVER['HTTP_HOST'] . "\n";
779
        $report .= "\tUser Agent - " . $_SERVER['HTTP_USER_AGENT'] . "\n";
780
        $report .= "\tRequest Method - " . $_SERVER['REQUEST_METHOD'] . "\n";
781
        $report .= "\tCame From - " . $_SERVER['HTTP_REFERER'] . "\n";
782
        $report .= "\tPage is - " . $_SERVER['SCRIPT_NAME'] . "\n";
783
        $report .= "\tUses Port - " . $_SERVER['REMOTE_PORT'] . "\n";
784
        $report .= "\tServer Protocol - " . $_SERVER['SERVER_PROTOCOL'] . "\n";
785
786
        $report .= "\n--------------------------------------------------------------------------------------------------\n";
787
788
        $fp = fopen(SN_ROOT_PHYSICAL . 'badqrys.txt', 'a');
789
        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 781
  1. Fetching key HTTP_REFERER from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 781
  2. Path: Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned in includes/classes/db_mysql.php on line 779
  1. Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 779
  2. $report is assigned
    in includes/classes/db_mysql.php on line 781
  3. Path: Fetching key HTTP_HOST from $_SERVER, and $report is assigned in includes/classes/db_mysql.php on line 778
  1. Fetching key HTTP_HOST from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 778
  2. $report is assigned
    in includes/classes/db_mysql.php on line 779
  3. $report is assigned
    in includes/classes/db_mysql.php on line 781
  4. Path: Read from $_GET in includes/classes/db_mysql.php on line 732
  1. Read from $_GET
    in includes/classes/db_mysql.php on line 732
  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 732
  4. $msg is passed to debug::warning()
    in includes/classes/db_mysql.php on line 734
  5. Data is escaped by mysqli_real_escape_string() for sql context(s)
    in vendor/includes/classes/db_mysql_v5.php on line 87
  6. $query is assigned
    in includes/classes/debug.php on line 258
  7. $query is passed to db_mysql::doExecute()
    in includes/classes/debug.php on line 263
  8. $query is passed to db_mysql::doquery()
    in includes/classes/db_mysql.php on line 275
  9. $stringQuery is assigned
    in includes/classes/db_mysql.php on line 229
  10. $stringQuery is passed through trim(), and $stringQuery is assigned
    in includes/classes/db_mysql.php on line 230
  11. $stringQuery is passed to db_mysql::security_query_check_bad_words()
    in includes/classes/db_mysql.php on line 235
  12. $report is assigned
    in includes/classes/db_mysql.php on line 773
  13. $report is assigned
    in includes/classes/db_mysql.php on line 778
  14. $report is assigned
    in includes/classes/db_mysql.php on line 779
  15. $report is assigned
    in includes/classes/db_mysql.php on line 781
  5. Path: Read from $_POST in includes/classes/db_mysql.php on line 729
  1. Read from $_POST
    in includes/classes/db_mysql.php on line 729
  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 729
  4. $msg is passed to debug::warning()
    in includes/classes/db_mysql.php on line 734
  5. Data is escaped by mysqli_real_escape_string() for sql context(s)
    in vendor/includes/classes/db_mysql_v5.php on line 87
  6. $query is assigned
    in includes/classes/debug.php on line 258
  7. $query is passed to db_mysql::doExecute()
    in includes/classes/debug.php on line 263
  8. $query is passed to db_mysql::doquery()
    in includes/classes/db_mysql.php on line 275
  9. $stringQuery is assigned
    in includes/classes/db_mysql.php on line 229
  10. $stringQuery is passed through trim(), and $stringQuery is assigned
    in includes/classes/db_mysql.php on line 230
  11. $stringQuery is passed to db_mysql::security_query_check_bad_words()
    in includes/classes/db_mysql.php on line 235
  12. $report is assigned
    in includes/classes/db_mysql.php on line 773
  13. $report is assigned
    in includes/classes/db_mysql.php on line 778
  14. $report is assigned
    in includes/classes/db_mysql.php on line 779
  15. $report is assigned
    in includes/classes/db_mysql.php on line 781
  6. Path: Read from $_POST in includes/general.php on line 258
  1. Read from $_POST
    in includes/general.php on line 258
  2. sys_get_param() returns tainted data
    in includes/general.php on line 290
  3. Data is passed through strip_tags(), and Data is passed through trim()
    in vendor/includes/general.php on line 1289
  4. sys_get_param_str_unsafe() returns tainted data, and sys_get_param_str_unsafe('uni_name') is passed through strip_tags(), and strip_tags(sys_get_param_str_unsafe('uni_name')) is passed through sprintf(), and sprintf(\classLocale::$lang['uni_msg_admin_rename'], $user['id'], $user['username'], $uni_price, $uni_system ? \classLocale::$lang['uni_system_of'] : \classLocale::$lang['uni_galaxy_of'], $uni_galaxy, $uni_system ? ":{$uni_system}" : '', strip_tags(sys_get_param_str_unsafe('uni_name'))) is passed to debug::warning()
    in includes/includes/uni_rename.php on line 55
  5. Data is escaped by mysqli_real_escape_string() for sql context(s)
    in vendor/includes/classes/db_mysql_v5.php on line 87
  6. $query is assigned
    in includes/classes/debug.php on line 258
  7. $query is passed to db_mysql::doExecute()
    in includes/classes/debug.php on line 263
  8. $query is passed to db_mysql::doquery()
    in includes/classes/db_mysql.php on line 275
  9. $stringQuery is assigned
    in includes/classes/db_mysql.php on line 229
  10. $stringQuery is passed through trim(), and $stringQuery is assigned
    in includes/classes/db_mysql.php on line 230
  11. $stringQuery is passed to db_mysql::security_query_check_bad_words()
    in includes/classes/db_mysql.php on line 235
  12. $report is assigned
    in includes/classes/db_mysql.php on line 773
  13. $report is assigned
    in includes/classes/db_mysql.php on line 778
  14. $report is assigned
    in includes/classes/db_mysql.php on line 779
  15. $report is assigned
    in includes/classes/db_mysql.php on line 781
  7. Path: Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned in includes/classes/db_mysql.php on line 767
  1. Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 767
  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 778
  4. $report is assigned
    in includes/classes/db_mysql.php on line 779
  5. $report is assigned
    in includes/classes/db_mysql.php on line 781

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