Completed
Push — work-fleets ( b4179a...b8fd67 )
by SuperNova.WS
05:25
created

db_mysql   D

Complexity

Total Complexity 81

Size/Duplication

Total Lines 438
Duplicated Lines 3.2 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 10
Bugs 0 Features 0
Metric Value
dl 14
loc 438
rs 4.8717
c 10
b 0
f 0
wmc 81
lcom 1
cbo 4

30 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 2 1
A load_db_settings() 0 7 1
C sn_db_connect() 0 29 8
A driver_connect() 0 11 3
A db_disconnect() 0 8 2
A replaceTablePlaceholders() 0 10 3
A logQuery() 0 11 3
A commentQuery() 0 19 4
A execute() 0 3 1
A fetchOne() 0 4 1
B doquery() 0 20 6
B security_watch_user_queries() 0 21 7
C security_query_check_bad_words() 0 53 17
B db_get_table_list() 0 20 5
A db_sql_query() 7 7 1
A db_fetch() 7 7 1
A db_fetch_row() 0 3 1
A db_escape() 0 3 1
A driver_disconnect() 0 3 1
A db_error() 0 3 1
A db_insert_id() 0 3 1
A db_num_rows() 0 3 1
A db_affected_rows() 0 3 1
A db_get_client_info() 0 3 1
A db_get_server_info() 0 3 1
A db_get_host_info() 0 3 1
A db_get_server_stat() 0 11 2
A db_core_show_status() 0 13 3
A mysql_get_table_list() 0 3 1
A mysql_get_innodb_status() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like db_mysql often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use db_mysql, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * User: Gorlum
5
 * Date: 01.09.2015
6
 * Time: 15:58
7
 */
8
class db_mysql {
9
  const DB_MYSQL_TRANSACTION_SERIALIZABLE = 'SERIALIZABLE';
10
  const DB_MYSQL_TRANSACTION_REPEATABLE_READ = 'REPEATABLE READ';
11
  const DB_MYSQL_TRANSACTION_READ_COMMITTED = 'READ COMMITTED';
12
  const DB_MYSQL_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
  public function __construct() {
63
  }
64
65
  public function load_db_settings() {
66
    $dbsettings = array();
67
68
    require(SN_ROOT_PHYSICAL . "config" . DOT_PHP_EX);
69
70
    $this->dbsettings = $dbsettings;
71
  }
72
73
  public function sn_db_connect($external_db_settings = null) {
74
    $this->db_disconnect();
75
76
    if(!empty($external_db_settings) && is_array($external_db_settings)) {
77
      $this->dbsettings = $external_db_settings;
78
    }
79
80
    if(empty($this->dbsettings)) {
81
      $this->load_db_settings();
82
    }
83
84
    // TODO - фатальные (?) ошибки на каждом шагу. Хотя - скорее Эксепшны
85
    if(!empty($this->dbsettings)) {
86
      $driver_name = empty($this->dbsettings['sn_driver']) ? 'db_mysql_v5' : $this->dbsettings['sn_driver'];
87
      $this->driver = new $driver_name();
88
      $this->db_prefix = $this->dbsettings['prefix'];
89
90
      $this->connected = $this->connected || $this->driver_connect();
91
92
      if($this->connected) {
93
        $this->table_list = $this->db_get_table_list();
94
        // TODO Проверка на пустоту
95
      }
96
    } else {
97
      $this->connected = false;
98
    }
99
100
    return $this->connected;
101
  }
102
103
  protected function driver_connect() {
104
    if(!is_object($this->driver)) {
105
      classSupernova::$debug->error_fatal('DB Error - No driver for MySQL found!');
106
    }
107
108
    if(!method_exists($this->driver, 'mysql_connect')) {
109
      classSupernova::$debug->error_fatal('DB Error - WRONG MySQL driver!');
110
    }
111
112
    return $this->driver->mysql_connect($this->dbsettings);
113
  }
114
115
  public function db_disconnect() {
116
    if($this->connected) {
117
      $this->connected = !$this->driver_disconnect();
118
      $this->connected = false;
119
    }
120
121
    return !$this->connected;
122
  }
123
124
  /**
125
   * @param $query
126
   *
127
   * @return mixed
128
   */
129
  public function replaceTablePlaceholders($query) {
130
    $sql = $query;
131
    if(strpos($sql, '{{') !== false) {
132
      foreach($this->table_list as $tableName) {
133
        $sql = str_replace("{{{$tableName}}}", $this->db_prefix . $tableName, $sql);
134
      }
135
    }
136
137
    return $sql;
138
  }
139
140
  /**
141
   * @param       $query
142
   * @param       $fetch
143
   */
144
  protected function logQuery($query, $fetch) {
145
    if(!classSupernova::$config->debug) {
146
      return;
147
    }
148
149
    $this->queryCount++;
150
    $arr = debug_backtrace();
151
    $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...
152
    $line = $arr[0]['line'];
153
    classSupernova::$debug->add("<tr><th>Query {$this->queryCount}: </th><th>$query</th><th>{$file} @ {$line}</th><th>&nbsp;</th><th> " . ($fetch ? '+' : '&nbsp;') . " </th></tr>");
154
  }
155
156
157
  /**
158
   * @param $sql
159
   *
160
   * @return void
161
   */
162
  protected function commentQuery(&$sql) {
163
    if(!defined('DEBUG_SQL_COMMENT')) {
164
      return;
165
    }
166
    $backtrace = debug_backtrace();
167
    $sql_comment = classSupernova::$debug->compact_backtrace($backtrace, defined('DEBUG_SQL_COMMENT_LONG'));
168
169
    $sql_commented = '/* ' . implode("<br />", $sql_comment) . '<br /> */ ' . preg_replace("/\s+/", ' ', $sql);
170
    if(defined('DEBUG_SQL_ONLINE')) {
171
      classSupernova::$debug->warning($sql_commented, 'SQL Debug', LOG_DEBUG_SQL);
172
    }
173
174
    if(defined('DEBUG_SQL_ERROR')) {
175
      array_unshift($sql_comment, preg_replace("/\s+/", ' ', $sql));
176
      classSupernova::$debug->add_to_array($sql_comment);
177
    }
178
179
    $sql = $sql_commented;
180
  }
181
182
  /**
183
   * @param DbSqlStatement $statement
184
   *
185
   * @return array|bool|mysqli_result|null
186
   */
187
  public function execute($statement) {
188
    return $this->doquery((string)$statement);
189
  }
190
191
  /**
192
   * @param DbSqlStatement $statement
193
   *
194
   * @return array|null
195
   */
196
  public function fetchOne($statement) {
197
    $query = $this->execute($statement);
198
    return $this->db_fetch($query);
199
  }
200
201
  public function doquery($query, $table = '', $fetch = false, $skip_query_check = false) {
202
    if(!is_string($table)) {
203
      $fetch = $table;
204
    }
205
206
    if(!$this->connected) {
207
      $this->sn_db_connect();
208
    }
209
210
    $query = trim($query);
211
    $this->security_watch_user_queries($query);
212
    !$skip_query_check ? $this->security_query_check_bad_words($query) : false;
213
    $this->logQuery($query, $fetch);
214
215
    $sql = $this->replaceTablePlaceholders($query);
216
    $this->commentQuery($sql);
217
    !($sqlquery = $this->db_sql_query($sql)) ? classSupernova::$debug->error(db_error() . "<br />$sql<br />", 'SQL Error') : false;
218
219
    return $fetch ? $this->db_fetch($sqlquery) : $sqlquery;
220
  }
221
222
223
  // TODO Заменить это на новый логгер
224
  protected function security_watch_user_queries($query) {
225
    global $user;
226
227
    if(
228
      !$this->isWatching // Not already watching
229
      && !empty(classSupernova::$config->game_watchlist_array) // There is some players in watchlist
230
      && in_array($user['id'], classSupernova::$config->game_watchlist_array) // Current player is in watchlist
231
      && !preg_match('/^(select|commit|rollback|start transaction)/i', $query) // Current query should be watched
232
    ) {
233
      $this->isWatching = true;
234
      $msg = "\$query = \"{$query}\"\n\r";
235
      if(!empty($_POST)) {
236
        $msg .= "\n\r" . dump($_POST, '$_POST');
237
      }
238
      if(!empty($_GET)) {
239
        $msg .= "\n\r" . dump($_GET, '$_GET');
240
      }
241
      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...
242
      $this->isWatching = false;
243
    }
244
  }
245
246
247
  public function security_query_check_bad_words($query) {
248
    global $user, $dm_change_legit, $mm_change_legit;
249
250
    switch(true) {
251
      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...
252
      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...
253
      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...
254
      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...
255
      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...
256
      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...
257
      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...
258
      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...
259
      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...
260
      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...
261
        $report = "Hacking attempt (" . date("d.m.Y H:i:s") . " - [" . time() . "]):\n";
262
        $report .= ">Database Inforamation\n";
263
        $report .= "\tID - " . $user['id'] . "\n";
264
        $report .= "\tUser - " . $user['username'] . "\n";
265
        $report .= "\tAuth level - " . $user['authlevel'] . "\n";
266
        $report .= "\tAdmin Notes - " . $user['adminNotes'] . "\n";
267
        $report .= "\tCurrent Planet - " . $user['current_planet'] . "\n";
268
        $report .= "\tUser IP - " . $user['user_lastip'] . "\n";
269
        $report .= "\tUser IP at Reg - " . $user['ip_at_reg'] . "\n";
270
        $report .= "\tUser Agent- " . $_SERVER['HTTP_USER_AGENT'] . "\n";
271
        $report .= "\tCurrent Page - " . $user['current_page'] . "\n";
272
        $report .= "\tRegister Time - " . $user['register_time'] . "\n";
273
        $report .= "\n";
274
275
        $report .= ">Query Information\n";
276
        $report .= "\tQuery - " . $query . "\n";
277
        $report .= "\n";
278
279
        $report .= ">\$_SERVER Information\n";
280
        $report .= "\tIP - " . $_SERVER['REMOTE_ADDR'] . "\n";
281
        $report .= "\tHost Name - " . $_SERVER['HTTP_HOST'] . "\n";
282
        $report .= "\tUser Agent - " . $_SERVER['HTTP_USER_AGENT'] . "\n";
283
        $report .= "\tRequest Method - " . $_SERVER['REQUEST_METHOD'] . "\n";
284
        $report .= "\tCame From - " . $_SERVER['HTTP_REFERER'] . "\n";
285
        $report .= "\tPage is - " . $_SERVER['SCRIPT_NAME'] . "\n";
286
        $report .= "\tUses Port - " . $_SERVER['REMOTE_PORT'] . "\n";
287
        $report .= "\tServer Protocol - " . $_SERVER['SERVER_PROTOCOL'] . "\n";
288
289
        $report .= "\n--------------------------------------------------------------------------------------------------\n";
290
291
        $fp = fopen(SN_ROOT_PHYSICAL . 'badqrys.txt', 'a');
292
        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.

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...
293
        fclose($fp);
294
295
        $message = 'Привет, я не знаю то, что Вы пробовали сделать, но команда, которую Вы только послали базе данных, не выглядела очень дружественной и она была заблокированна.<br /><br />Ваш IP, и другие данные переданны администрации сервера. Удачи!.';
296
        die($message);
297
      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...
298
    }
299
  }
300
301
  /**
302
   * @param bool $prefixed_only
303
   *
304
   * @return array
305
   */
306
  public function db_get_table_list($prefixed_only = true) {
307
    $query = $this->mysql_get_table_list();
308
309
    $prefix_length = strlen($this->db_prefix);
310
311
    $tl = array();
312
    while($row = $this->db_fetch($query)) {
313
      foreach($row as $table_name) {
314
        if(strpos($table_name, $this->db_prefix) === 0) {
315
          $table_name = substr($table_name, $prefix_length);
316
        } elseif($prefixed_only) {
317
          continue;
318
        }
319
        // $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...
320
        $tl[$table_name] = $table_name;
321
      }
322
    }
323
324
    return $tl;
325
  }
326
327
328
  /**
329
   * L1 perform the query
330
   *
331
   * @param $query_string
332
   *
333
   * @return bool|mysqli_result
334
   */
335 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...
336
    $microtime = microtime(true);
337
    $result = $this->driver->mysql_query($query_string);
338
    $this->time_mysql_total += microtime(true) - $microtime;
339
340
    return $result;
341
  }
342
343
  /**
344
   * L1 fetch assoc array
345
   *
346
   * @param $query
347
   *
348
   * @return array|null
349
   */
350 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...
351
    $microtime = microtime(true);
352
    $result = $this->driver->mysql_fetch_assoc($query);
353
    $this->time_mysql_total += microtime(true) - $microtime;
354
355
    return $result;
356
  }
357
358
  public function db_fetch_row(&$query) {
359
    return $this->driver->mysql_fetch_row($query);
360
  }
361
362
  public function db_escape($unescaped_string) {
363
    return $this->driver->mysql_real_escape_string($unescaped_string);
364
  }
365
366
  public function driver_disconnect() {
367
    return $this->driver->mysql_close_link();
368
  }
369
370
  public function db_error() {
371
    return $this->driver->mysql_error();
372
  }
373
374
  public function db_insert_id() {
375
    return $this->driver->mysql_insert_id();
376
  }
377
378
  public function db_num_rows(&$result) {
379
    return $this->driver->mysql_num_rows($result);
380
  }
381
382
  public function db_affected_rows() {
383
    return $this->driver->mysql_affected_rows();
384
  }
385
386
  /**
387
   * @return string
388
   */
389
  public function db_get_client_info() {
390
    return $this->driver->mysql_get_client_info();
391
  }
392
393
  /**
394
   * @return string
395
   */
396
  public function db_get_server_info() {
397
    return $this->driver->mysql_get_server_info();
398
  }
399
400
  /**
401
   * @return string
402
   */
403
  public function db_get_host_info() {
404
    return $this->driver->mysql_get_host_info();
405
  }
406
407
  public function db_get_server_stat() {
408
    $result = array();
409
410
    $status = explode('  ', $this->driver->mysql_stat());
411
    foreach($status as $value) {
412
      $row = explode(': ', $value);
413
      $result[$row[0]] = $row[1];
414
    }
415
416
    return $result;
417
  }
418
419
  /**
420
   * @return array
421
   * @throws Exception
422
   */
423
  public function db_core_show_status() {
424
    $result = array();
425
426
    $query = $this->db_sql_query('SHOW STATUS;');
427
    if(is_bool($query)) {
428
      throw new Exception('Result of SHOW STATUS command is boolean - which should never happen. Connection to DB is lost?');
429
    }
430
    while($row = db_fetch($query)) {
431
      $result[$row['Variable_name']] = $row['Value'];
432
    }
433
434
    return $result;
435
  }
436
437
  public function mysql_get_table_list() {
438
    return $this->db_sql_query('SHOW TABLES;');
439
  }
440
441
  public function mysql_get_innodb_status() {
442
    return $this->db_sql_query('SHOW ENGINE INNODB STATUS;');
443
  }
444
445
}
446