Completed
Push — work-fleets ( ea0fb4...874fb8 )
by SuperNova.WS
06:59
created

db_mysql::doUpdateTableSet()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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

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

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

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

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

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

An additional type check may prevent trouble.

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

This method has been deprecated.

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

This method has been deprecated.

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

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