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

db_mysql::safeFieldsEqualValues()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 18
Code Lines 10

Duplication

Lines 18
Ratio 100 %

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

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

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

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

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

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

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

An additional type check may prevent trouble.

Loading history...
290
  }
291
292
  /**
293
   * @param string $query
294
   *
295
   * @return mixed|null
296
   */
297
  public function doSelectFetchValue($query) {
298
    $row = $this->doSelectFetch($query);
299
300
    return is_array($row) ? reset($row) : null;
301
  }
302
303
304
  public function doInsertComplex($query) {
305
    return $this->doSql($query);
306
  }
307
308
  protected function doSet($table, $fieldsAndValues, $replace = DB_INSERT_PLAIN) {
309
    $tableSafe = $this->db_escape($table);
310
    $safeFieldsAndValues = implode(',', $this->safeFieldsEqualValues($fieldsAndValues));
311
//    $command = $replace == DB_INSERT_REPLACE ? 'REPLACE' : 'INSERT';
312
//    $command .= $replace == DB_INSERT_IGNORE ? ' IGNORE' : '';
313
    switch ($replace) {
314
      case DB_INSERT_IGNORE:
315
        $command = 'INSERT IGNORE';
316
      break;
317
      case DB_INSERT_REPLACE:
318
        $command = 'REPLACE';
319
      break;
320
      default:
321
        $command = 'INSERT';
322
      break;
323
    }
324
    $query = "{$command} INTO `{{{$tableSafe}}}` SET {$safeFieldsAndValues}";
325
326
    return $this->doSql($query);
327
  }
328
329
  /**
330
   * @param string $table
331
   * @param array  $fieldsAndValues
332
   * @param int    $replace - DB_INSERT_PLAIN || DB_INSERT_IGNORE
333
   *
334
   * @return array|bool|mysqli_result|null
335
   */
336
  public function doInsertSet($table, $fieldsAndValues, $replace = DB_INSERT_PLAIN) {
337
    return $this->doSet($table, $fieldsAndValues, $replace);
338
  }
339
340
  public function doReplaceSet($table, $fieldsAndValues) {
341
    return $this->doSet($table, $fieldsAndValues, DB_INSERT_REPLACE);
342
  }
343
344
  /**
345
   * Values should be passed as-is
346
   *
347
   * @param string   $table
348
   * @param array    $fields
349
   * @param string[] $values
350
   * @param bool     $replace
351
   *
352
   * @return array|bool|mysqli_result|null
353
   * @deprecated
354
   */
355
  protected function doValuesDeprecated($table, $fields, &$values, $replace = DB_INSERT_PLAIN) {
356
    $tableSafe = $this->db_escape($table);
357
    $safeFields = implode(',', $this->safeFields($fields));
358
    $safeValues = implode(',', $values);
359
    $command = $replace == DB_INSERT_REPLACE ? 'REPLACE' : 'INSERT';
360
    $query = "{$command} INTO `{{{$tableSafe}}}` ({$safeFields}) VALUES {$safeValues}";
361
362
    return $this->doSql($query);
363
  }
364
365
  // TODO - batch insert and replace here
366
367
  // TODO - перед тем, как переделывать данные из депрекейтов - убедится, что
368
  // null - это null, а не строка'NULL'
369
370
  /**
371
   * Values should be passed as-is
372
   *
373
   * @param string   $table
374
   * @param array    $fields
375
   * @param string[] $values
376
   *
377
   * @return array|bool|mysqli_result|null
378
   * @deprecated
379
   */
380
  public function doInsertValuesDeprecated($table, $fields, &$values) {
381
    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...
382
  }
383
384
  /**
385
   * Values should be passed as-is
386
   *
387
   * @param string   $table
388
   * @param array    $fields
389
   * @param string[] $values
390
   *
391
   * @return array|bool|mysqli_result|null
392
   * @deprecated
393
   */
394
  public function doReplaceValuesDeprecated($table, $fields, &$values) {
395
    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...
396
  }
397
398
399
  public function doUpdateComplex($query) {
400
    return $this->doSql($query);
401
  }
402
403
  public function doUpdateReallyComplex($query) {
404
    return $this->doSql($query);
405
  }
406
407
  /**
408
   * Executes self-contained SQL UPDATE query
409
   *
410
   * Self-contained - means no params used
411
   * Such queries usually used to make large amount of in-base calculations
412
   *
413
   * @param $query
414
   *
415
   * @return array|bool|mysqli_result|null
416
   */
417
  public function doUpdateSqlNoParam($query) {
418
    return $this->doSql($query);
419
  }
420
421
  protected function doUpdateWhere($table, $fieldsSet, $fieldsAdjust = array(), $where = array(), $isOneRecord = DB_RECORDS_ALL) {
422
    $tableSafe = $this->db_escape($table);
423
424
    $safeFields = array();
425
    // Adjusts overwritten by Sets
426
    if ($safeAdjust = implode(',', $this->safeFieldsAdjust($fieldsAdjust))) {
427
      $safeFields[] = &$safeAdjust;
428
    }
429
    if ($safeFieldsEqualValues = implode(',', $this->safeFieldsEqualValues($fieldsSet))) {
430
      $safeFields[] = &$safeFieldsEqualValues;
431
    }
432
    $safeFieldsString = implode(',', $safeFields);
433
434
    // TODO - Exception of $safeFieldsString
435
436
    $safeWhereAnd = implode(' AND ', $this->safeFieldsEqualValues($where));
437
    $query = "UPDATE `{{{$tableSafe}}}` SET {$safeFieldsString}"
438
      . (!empty($safeWhereAnd) ? " WHERE {$safeWhereAnd}" : '')
439
      . ($isOneRecord == DB_RECORD_ONE ? ' LIMIT 1' : '');
440
441
    return $this->doSql($query);
442
  }
443
444
  public function doUpdateRowSet($table, $fieldsAndValues, $where) {
445
    return $this->doUpdateWhere($table, $fieldsAndValues, array(), $where, DB_RECORD_ONE);
446
  }
447
448
  public function doUpdateTableSet($table, $fieldsAndValues, $where = array()) {
449
    return $this->doUpdateWhere($table, $fieldsAndValues, array(), $where, DB_RECORDS_ALL);
450
  }
451
452
  public function doUpdateRowAdjust($table, $fieldsSet, $fieldsAdjust, $where) {
453
    return $this->doUpdateWhere($table, $fieldsSet, $fieldsAdjust, $where, DB_RECORD_ONE);
454
  }
455
456
  public function doUpdateTableAdjust($table, $fieldsSet, $fieldsAdjust, $where) {
457
    return $this->doUpdateWhere($table, $fieldsSet, $fieldsAdjust, $where, DB_RECORDS_ALL);
458
  }
459
460
  /**
461
   * In this call means that used some conditions in $where different than field = value
462
   *
463
   * It should be rewrote later
464
   *
465
   * @param $table
466
   * @param $fieldsSet
467
   * @param $fieldsAdjust
468
   * @param $where
469
   *
470
   * @return array|bool|mysqli_result|null
471
   * @deprecated
472
   */
473
  public function doUpdateTableAdjustDanger($table, $fieldsSet, $fieldsAdjust, $where) {
474
    return $this->doUpdateWhere($table, $fieldsSet, $fieldsAdjust, $where, DB_RECORDS_ALL);
475
  }
476
477
  /**
478
   * For update_old - would be deprecated
479
   *
480
   * @param string $query
481
   *
482
   * @return array|bool|mysqli_result|null
483
   * @deprecated
484
   */
485
  public function doUpdateOld($query) {
486
    return $this->doSql($query);
487
  }
488
489
490
491
  // DELETERS
492
  /**
493
   * @param string $table
494
   * @param array  $where
495
   * @param bool   $isOneRecord
496
   *
497
   * @return DbQuery
498
   */
499
  protected function buildDeleteQuery($table, $where, $isOneRecord = DB_RECORDS_ALL) {
500
    return DbQuery::build($this)
501
      ->table($table)
502
      ->whereArray($where)
503
      ->oneRow($isOneRecord);
504
  }
505
506
  /**
507
   * @param string $table
508
   * @param array  $where - simple WHERE statement list which can be combined with AND
509
   * @param bool   $isOneRecord
510
   *
511
   * @return array|bool|mysqli_result|null
512
   */
513
  public function doDeleteWhere($table, $where, $isOneRecord = DB_RECORDS_ALL) {
514
    return $this->doSql($this->buildDeleteQuery($table, $where, $isOneRecord)->delete());
515
  }
516
517
  /**
518
   * Early deprecated function for complex delete conditions
519
   *
520
   * Used for malformed $where conditions
521
   * Also whereDanger can contain references for other {{tables}}
522
   *
523
   * @param string $table
524
   * @param array  $where
525
   * @param array  $whereDanger
526
   *
527
   * @return array|bool|mysqli_result|null
528
   * @deprecated
529
   */
530
  public function doDeleteDanger($table, $where, $whereDanger) {
531
    return $this->doSql($this->buildDeleteQuery($table, $where, false)->whereArrayDanger($whereDanger)->delete());
0 ignored issues
show
Deprecated Code introduced by
The method DBAL\DbQuery::whereArrayDanger() has been deprecated.

This method has been deprecated.

Loading history...
532
  }
533
534
  /**
535
   * @param string $table
536
   * @param array  $where - simple WHERE statement list which can be combined with AND
537
   *
538
   * @return array|bool|mysqli_result|null
539
   */
540
  public function doDeleteRow($table, $where) {
541
    return $this->doDeleteWhere($table, $where, true);
542
  }
543
544
  /**
545
   * Perform simple delete queries on fixed tables w/o params
546
   *
547
   * @param string $query
548
   *
549
   * @return array|bool|mysqli_result|null
550
   */
551
  public function doDeleteSimple($query) {
552
    return $this->doSql($query);
553
  }
554
555
556
  // Misc functions
557
  //
558 View Code Duplication
  protected function castAsDbValue($value) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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

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