Completed
Push — work-fleets ( 1920ae...cfff9f )
by SuperNova.WS
07:02
created

db_mysql::safeFieldsAndValues()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 10
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 18
ccs 0
cts 11
cp 0
crap 30
rs 8.8571

1 Method

Rating   Name   Duplication   Size   Complexity  
A db_mysql::doDeleteDeprecated() 0 3 1
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
  /**
409
   * Executes self-contained SQL UPDATE query
410
   *
411
   * Self-contained - means no params used
412
   * Such queries usually used to make large amount of in-base calculations
413
   *
414
   * @param $query
415
   *
416
   * @return array|bool|mysqli_result|null
417
   */
418
  public function doUpdateSqlNoParam($query) {
419
    return $this->doExecute($query);
420
  }
421
422
  protected function doUpdateWhere($table, $fieldsAndValues, $where = array(), $isOneRecord = DB_RECORDS_ALL) {
423
    $tableSafe = $this->db_escape($table);
424
    $safeFieldsEqualValues = implode(',', $this->safeFieldsEqualValues($fieldsAndValues));
425
    $safeWhereAnd = implode(' AND ', $this->safeFieldsEqualValues($where));
426
    $query = "UPDATE `{{{$tableSafe}}}` SET {$safeFieldsEqualValues}"
427
      . (!empty($safeWhereAnd) ? " WHERE {$safeWhereAnd}" : '')
428
      . ($isOneRecord == DB_RECORD_ONE ? ' LIMIT 1' : '');
429
430
    return $this->doExecute($query);
431
  }
432
433
  public function doUpdateRowWhere($table, $fieldsAndValues, $where) {
434
    return $this->doUpdateWhere($table, $fieldsAndValues, $where, DB_RECORD_ONE);
435
  }
436
437
  public function doUpdateTable($table, $fieldsAndValues, $where = array()) {
438
    return $this->doUpdateWhere($table, $fieldsAndValues, $where, DB_RECORDS_ALL);
439
  }
440
441
  public function doUpdateAdjust($query) {
442
    return $this->doExecute($query);
443
  }
444
445
446
  /**
447
   * For update_old - would be deprecated
448
   *
449
   * @param string $query
450
   *
451
   * @return array|bool|mysqli_result|null
452
   */
453
  public function doUpdateOld($query) {
454
    return $this->doExecute($query);
455
  }
456
457
458
  /**
459
   * @param string $query
460
   *
461
   * @return array|bool|mysqli_result|null
462
   */
463
  public function doDelete($query) {
464
    return $this->doExecute($query);
465
  }
466
467
  /**
468
   * @param string $table
469
   * @param array  $where - simple WHERE statement list which can be combined with AND
470
   * @param bool   $isOneRecord
471
   *
472
   * @return array|bool|mysqli_result|null
473
   */
474
  public function doDeleteWhere($table, $where, $isOneRecord = false) {
475
    $tableSafe = $this->db_escape($table);
476
    $safeWhere = implode(' AND ', $this->safeFieldsEqualValues($where));
477
    $query = "DELETE FROM `{{{$tableSafe}}}` WHERE {$safeWhere}"
478
      . ($isOneRecord ? ' LIMIT 1' : '');
479
480
    return $this->doDelete($query);
481
  }
482
483
  /**
484
   * @param string $table
485
   * @param array  $where - simple WHERE statement list which can be combined with AND
486
   *
487
   * @return array|bool|mysqli_result|null
488
   */
489
  public function doDeleteRowWhere($table, $where) {
490
    return $this->doDeleteWhere($table, $where, true);
491
  }
492
493
  /**
494
   * @param string $query
495
   *
496
   * @return array|bool|mysqli_result|null
497
   */
498
  public function doDeleteComplex($query) {
499
    return $this->doDelete($query);
500
  }
501
502
  /**
503
   * Early deprecated function for complex delete conditions
504
   *
505
   * Usually used for mallformed $where conditions
506
   *
507
   * @param $table
508
   * @param $where
509
   *
510
   * @return array|bool|mysqli_result|null
511
   * @deprecated
512
   */
513
  public function doDeleteDeprecated($table, $where) {
514
    return $this->doDeleteWhere($table, $where, false);
515
  }
516
517
518
  protected function castAsDbValue($value) {
519
    switch(gettype($value)) {
520
      case TYPE_INTEGER:
521
      case TYPE_DOUBLE:
522
        // do nothing
523
      break;
524
525
      case TYPE_BOOLEAN:
526
        $value = $value ? 1 : 0;
527
      break;
528
529
      case TYPE_NULL:
530
        $value = 'NULL';
531
      break;
532
533
      /** @noinspection PhpMissingBreakStatementInspection */
534
      case TYPE_ARRAY:
535
        $value = serialize($value);
536
      // Continuing with serialized array value
537
      case TYPE_STRING:
538
        // Empty type is string
539
      case TYPE_EMPTY:
540
        // No-type defaults to string
541
      default:
542
        $value = "'" . $this->db_escape((string)$value) . "'";
543
      break;
544
    }
545
546
    return $value;
547
  }
548
549
  /**
550
   * Make field list safe
551
   *
552
   * Support expressions - expression index should be strictly integer!
553
   *
554
   * @param array $fields - array of pair $fieldName => $fieldValue
555
   *
556
   * @return array
557
   */
558
  protected function safeFieldsEqualValues($fields) {
559
    $result = array();
560
561
    if (!is_array($fields) || empty($fields)) {
562
      return $result;
563
    }
564
565
    foreach ($fields as $fieldName => $fieldValue) {
566
      // Integer $fieldName means "leave as is" - for expressions and already processed fields
567
      if (is_int($fieldName)) {
568
        $result[$fieldName] = $fieldValue;
569
      } else {
570
        $result[$fieldName] = "`{$fieldName}` = " . $this->castAsDbValue($fieldValue);
571
      }
572
    }
573
574
    return $result;
575
  }
576
577
  // TODO - redo as callable usage with array_map/array_walk
578 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...
579
    $result = array();
580
581
    if (!is_array($values) || empty($values)) {
582
      return $result;
583
    }
584
585
    foreach ($values as $key => $value) {
586
      $result[$key] = $this->castAsDbValue($value);
587
    }
588
589
    return $result;
590
  }
591
592
  // TODO - redo as callable usage with array_map/array_walk
593 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...
594
    $result = array();
595
596
    if (!is_array($fields) || empty($fields)) {
597
      return $result;
598
    }
599
600
    foreach ($fields as $key => $value) {
601
      $result[$key] = "`" . $this->db_escape($value) . "`";
602
    }
603
604
    return $result;
605
  }
606
607
608
  /**
609
   * Returns iterator to iterate through mysqli_result
610
   *
611
   * @param string $query
612
   *
613
   * return DbResultIterator
614
   *
615
   * @return DbEmptyIterator|DbMysqliResultIterator
616
   */
617
  public function doSelectIterator($query) {
618
    $queryResult = $this->doSelect($query);
619
620
    if ($queryResult instanceof mysqli_result) {
621
      $result = new DbMysqliResultIterator($queryResult);
622
    } else {
623
      $result = new DbEmptyIterator();
624
    }
625
626
    return $result;
627
  }
628
629
  /**
630
   * @param DbQueryConstructor $stmt
631
   * @param bool               $skip_query_check
632
   */
633
  public function doStmtLockAll($stmt, $skip_query_check = false) {
634
    $this->doExecute(
635
      $stmt
636
        ->select()
637
        ->field(1)
638
        ->setForUpdate()
639
        ->__toString(),
640
      $skip_query_check
641
    );
642
  }
643
644
  // TODO Заменить это на новый логгер
645
  protected function security_watch_user_queries($query) {
646
    global $user;
647
648
    if (
649
      !$this->isWatching // Not already watching
650
      && !empty(classSupernova::$config->game_watchlist_array) // There is some players in watchlist
651
      && in_array($user['id'], classSupernova::$config->game_watchlist_array) // Current player is in watchlist
652
      && !preg_match('/^(select|commit|rollback|start transaction)/i', $query) // Current query should be watched
653
    ) {
654
      $this->isWatching = true;
655
      $msg = "\$query = \"{$query}\"\n\r";
656
      if (!empty($_POST)) {
657
        $msg .= "\n\r" . dump($_POST, '$_POST');
658
      }
659
      if (!empty($_GET)) {
660
        $msg .= "\n\r" . dump($_GET, '$_GET');
661
      }
662
      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...
663
      $this->isWatching = false;
664
    }
665
  }
666
667
668
  public function security_query_check_bad_words($query) {
669
    if ($this->skipQueryCheck) {
670
      return;
671
    }
672
673
    global $user, $dm_change_legit, $mm_change_legit;
674
675
    switch(true) {
676
      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...
677
      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...
678
      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...
679
      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...
680
      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...
681
      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...
682
      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...
683
      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...
684
      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...
685
      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...
686
        $report = "Hacking attempt (" . date("d.m.Y H:i:s") . " - [" . time() . "]):\n";
687
        $report .= ">Database Inforamation\n";
688
        $report .= "\tID - " . $user['id'] . "\n";
689
        $report .= "\tUser - " . $user['username'] . "\n";
690
        $report .= "\tAuth level - " . $user['authlevel'] . "\n";
691
        $report .= "\tAdmin Notes - " . $user['adminNotes'] . "\n";
692
        $report .= "\tCurrent Planet - " . $user['current_planet'] . "\n";
693
        $report .= "\tUser IP - " . $user['user_lastip'] . "\n";
694
        $report .= "\tUser IP at Reg - " . $user['ip_at_reg'] . "\n";
695
        $report .= "\tUser Agent- " . $_SERVER['HTTP_USER_AGENT'] . "\n";
696
        $report .= "\tCurrent Page - " . $user['current_page'] . "\n";
697
        $report .= "\tRegister Time - " . $user['register_time'] . "\n";
698
        $report .= "\n";
699
700
        $report .= ">Query Information\n";
701
        $report .= "\tQuery - " . $query . "\n";
702
        $report .= "\n";
703
704
        $report .= ">\$_SERVER Information\n";
705
        $report .= "\tIP - " . $_SERVER['REMOTE_ADDR'] . "\n";
706
        $report .= "\tHost Name - " . $_SERVER['HTTP_HOST'] . "\n";
707
        $report .= "\tUser Agent - " . $_SERVER['HTTP_USER_AGENT'] . "\n";
708
        $report .= "\tRequest Method - " . $_SERVER['REQUEST_METHOD'] . "\n";
709
        $report .= "\tCame From - " . $_SERVER['HTTP_REFERER'] . "\n";
710
        $report .= "\tPage is - " . $_SERVER['SCRIPT_NAME'] . "\n";
711
        $report .= "\tUses Port - " . $_SERVER['REMOTE_PORT'] . "\n";
712
        $report .= "\tServer Protocol - " . $_SERVER['SERVER_PROTOCOL'] . "\n";
713
714
        $report .= "\n--------------------------------------------------------------------------------------------------\n";
715
716
        $fp = fopen(SN_ROOT_PHYSICAL . 'badqrys.txt', 'a');
717
        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 709
  1. Fetching key HTTP_REFERER from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 709
  2. Path: Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned in includes/classes/db_mysql.php on line 707
  1. Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 707
  2. $report is assigned
    in includes/classes/db_mysql.php on line 709
  3. Path: Fetching key HTTP_HOST from $_SERVER, and $report is assigned in includes/classes/db_mysql.php on line 706
  1. Fetching key HTTP_HOST from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 706
  2. $report is assigned
    in includes/classes/db_mysql.php on line 707
  3. $report is assigned
    in includes/classes/db_mysql.php on line 709
  4. Path: Read from $_GET in includes/classes/db_mysql.php on line 660
  1. Read from $_GET
    in includes/classes/db_mysql.php on line 660
  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 660
  4. $msg is passed to debug::warning()
    in includes/classes/db_mysql.php on line 662
  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 701
  13. $report is assigned
    in includes/classes/db_mysql.php on line 706
  14. $report is assigned
    in includes/classes/db_mysql.php on line 707
  15. $report is assigned
    in includes/classes/db_mysql.php on line 709
  5. Path: Read from $_POST in includes/classes/db_mysql.php on line 657
  1. Read from $_POST
    in includes/classes/db_mysql.php on line 657
  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 657
  4. $msg is passed to debug::warning()
    in includes/classes/db_mysql.php on line 662
  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 701
  13. $report is assigned
    in includes/classes/db_mysql.php on line 706
  14. $report is assigned
    in includes/classes/db_mysql.php on line 707
  15. $report is assigned
    in includes/classes/db_mysql.php on line 709
  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 701
  13. $report is assigned
    in includes/classes/db_mysql.php on line 706
  14. $report is assigned
    in includes/classes/db_mysql.php on line 707
  15. $report is assigned
    in includes/classes/db_mysql.php on line 709
  7. Path: Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned in includes/classes/db_mysql.php on line 695
  1. Fetching key HTTP_USER_AGENT from $_SERVER, and $report is assigned
    in includes/classes/db_mysql.php on line 695
  2. $report is assigned
    in includes/classes/db_mysql.php on line 701
  3. $report is assigned
    in includes/classes/db_mysql.php on line 706
  4. $report is assigned
    in includes/classes/db_mysql.php on line 707
  5. $report is assigned
    in includes/classes/db_mysql.php on line 709

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