Test Failed
Push — trunk ( 132e87...b3f953 )
by SuperNova.WS
11:54
created

db_mysql::db_transaction_start()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 10
nc 2
nop 1
dl 0
loc 17
rs 9.9332
c 0
b 0
f 0
ccs 0
cts 0
cp 0
crap 6
1
<?php /** @noinspection PhpRedundantOptionalArgumentInspection */
2
3
/** @noinspection PhpDeprecationInspection */
4
/** @noinspection PhpUnnecessaryCurlyVarSyntaxInspection */
5
6
namespace DBAL;
7
8
use classConfig;
9
use Core\GlobalContainer;
10
use debug;
11
use mysqli;
12
use mysqli_result;
13
use SN;
14
use Unit\DBStaticUnit;
15
16
/**
17
 * User: Gorlum
18
 * Date: 01.09.2015
19
 * Time: 15:58
20
 */
21
class db_mysql {
22
  const TRANSACTION_LEVEL_SERIALIZABLE = 'SERIALIZABLE';
23
  const TRANSACTION_LEVEL_REPEATABLE_READ = 'REPEATABLE READ';
24
  const TRANSACTION_LEVEL_READ_COMMITTED = 'READ COMMITTED';
25
  const TRANSACTION_LEVEL_READ_UNCOMMITTED = 'READ UNCOMMITTED';
26
27
  const TRANSACTION_LEVELS_ALLOWED = [
28
    self::TRANSACTION_LEVEL_SERIALIZABLE,
29
    self::TRANSACTION_LEVEL_REPEATABLE_READ,
30
    self::TRANSACTION_LEVEL_READ_COMMITTED,
31
    self::TRANSACTION_LEVEL_READ_UNCOMMITTED,
32
  ];
33
  const DB_TRANSACTION_WHATEVER = false;
34
  const DB_TRANSACTION_SHOULD_BE = true;
35
  const DB_TRANSACTION_SHOULD_NOT_BE = null;
36
  public static $transaction_id = 0;
37
  public static $db_in_transaction = false;
38
  public static $transactionDepth = 0;
39
40
  const TABLE_USERS = 'users';
41
42
  const TABLE_ID_FIELD = [
43
    'fleets' => 'fleet_id',
44
  ];
45
46
  /**
47
   * DB schemes
48
   *
49
   * @var Schema|null $schema
50
   */
51
  protected static $schema = null;
52
53
  /**
54
   * Статус соединения с MySQL
55
   *
56
   * @var bool
57
   */
58
  public $connected = false;
59
  /**
60
   * Префикс названий таблиц в БД
61
   *
62
   * @var string
63
   */
64
  public $db_prefix = '';
65
  public $dbName = '';
66
  /**
67
   * Настройки БД
68
   *
69
   * @var array
70
   */
71
  protected $dbsettings = [];
72
//  /**
73
//   * Драйвер для прямого обращения к MySQL
74
//   *
75
//   * @var db_mysql_v5 $driver
76
//   */
77
//  public $driver = null;
78
79
  /**
80
   * Общее время запросов
81
   *
82
   * @var float $time_mysql_total
83
   */
84
  public $time_mysql_total = 0.0;
85
86
  /**
87
   * @var bool $inTransaction
88
   */
89
  protected $inTransaction = false;
90
91
92
  /**
93
   * Соединение с MySQL
94
   *
95
   * @var mysqli $link
96
   */
97
  public $link;
98
99
100
  /**
101
   * DBAL\db_mysql constructor.
102
   *
103
   * @param GlobalContainer $gc
104
   */
105
  public function __construct($gc) {
0 ignored issues
show
Unused Code introduced by
The parameter $gc is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

105
  public function __construct(/** @scrutinizer ignore-unused */ $gc) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
106
//    $this->transaction = new \DBAL\DbTransaction($gc, $this);
107
//    $this->snCache = new $gc->snCacheClass($gc, $this);
108
//    $this->operator = new DbRowDirectOperator($this);
109
  }
110
111
  /**
112
   * @return Schema
113
   */
114
  public function schema() {
115
    if (!isset(self::$schema)) {
116
      self::$schema = new Schema($this);
117
    }
118
119
    return self::$schema;
120
  }
121
122
  public function load_db_settings() {
123
    $dbsettings = array();
124
125
    require(SN_CONFIG_PATH);
126
127
    $this->setDbSettings($dbsettings);
128
  }
129
130
  public function sn_db_connect($external_db_settings = null) {
131
    $this->db_disconnect();
132
133
    if (!empty($external_db_settings) && is_array($external_db_settings)) {
134
      $this->setDbSettings($external_db_settings);
135
    }
136
137
    if (empty($this->dbsettings)) {
138
      $this->load_db_settings();
139
    }
140
141
    // TODO - фатальные (?) ошибки на каждом шагу
142
    if (!empty($this->dbsettings)) {
143
      // $driver_name = 'DBAL\\' . (empty($this->dbsettings['sn_driver']) ? 'db_mysql_v5' : $this->dbsettings['sn_driver']);
144
      // $this->driver = new $driver_name();
145
      $this->db_prefix = $this->dbsettings['prefix'];
146
147
      $this->connected = $this->connected || $this->mysql_connect_driver($this->dbsettings);
148
149
      if ($this->connected && empty($this->schema()->getSnTables())) {
150
        die('DB error - cannot find any table. Halting...');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
151
      }
152
153
      $this->doQueryFast('SET SESSION TRANSACTION ISOLATION LEVEL ' . self::TRANSACTION_LEVEL_SERIALIZABLE);
154
    } else {
155
      $this->connected = false;
156
    }
157
158
    return $this->connected;
159
  }
160
161
  public function mysql_connect_driver($settings) {
162
    global $debug;
163
164
    static $need_keys = array('server', 'user', 'pass', 'name', 'prefix');
165
166
    if ($this->connected) {
167
      return true;
168
    }
169
170
    if (empty($settings) || !is_array($settings) || array_intersect($need_keys, array_keys($settings)) != $need_keys) {
171
      $debug->error_fatal('There is miss-configuration in your config.php. Check it again');
172
    }
173
174
    @$this->link = mysqli_connect($settings['server'], $settings['user'], $settings['pass'], $settings['name']);
0 ignored issues
show
Bug introduced by
The call to mysqli_connect() has too few arguments starting with port. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

174
    @$this->link = /** @scrutinizer ignore-call */ mysqli_connect($settings['server'], $settings['user'], $settings['pass'], $settings['name']);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
175
    if (!is_object($this->link) || $this->link->connect_error) {
176
      $debug->error_fatal('DB Error - cannot connect to server error #' . $this->link->connect_errno, $this->link->connect_error);
177
    }
178
179
    $this->db_sql_query("/*!40101 SET NAMES 'utf8' */")
180
    or $debug->error_fatal('DB error - cannot set names 1 error #' . $this->link->errno, $this->link->error);
181
    $this->db_sql_query("SET NAMES 'utf8';")
182
    or $debug->error_fatal('DB error - cannot set names 2 error #' . $this->link->errno, $this->link->error);
183
184
    //mysql_select_db($settings['name']) or $debug->error_fatal('DB error - cannot find DB on server', $this->mysql_error());
185
    $this->db_sql_query('SET SESSION TRANSACTION ISOLATION LEVEL ' . self::TRANSACTION_LEVEL_SERIALIZABLE . ';')
186
    or $debug->error_fatal('DB error - cannot set desired isolation level error #' . $this->link->errno, $this->link->error);
187
188
    $this->connected = true;
189
190
    return true;
191
  }
192
193
  public function db_disconnect() {
194
    if ($this->connected) {
195
      /** @noinspection PhpFieldImmediatelyRewrittenInspection */
196
      $this->connected = !$this->driver_disconnect();
197
      $this->connected = false;
198
    }
199
200
    return !$this->connected;
201
  }
202
203
  /**
204
   * @param int    $errno
205
   * @param string $errStr
206
   * @param string $errFile
207
   * @param int    $errLine
208
   * @param array  $errContext
209
   *
210
   * @noinspection PhpUnusedParameterInspection
211
   */
212
  public function handlerQueryWarning($errno, $errStr, $errFile, $errLine, $errContext) {
213
    static $alreadyHandled;
214
215
    // Error was suppressed with the @-operator
216
    if (0 === error_reporting()) {
217
      return false;
218
    }
219
220
    if (!$alreadyHandled) {
221
      print(SN_TIME_SQL . '<br />Server is busy. Please try again in several minutes...<br />Сервер занят. Попробуйте снова через несколько минут...<br />Server zanyat. Poprobujte snova cherez neskolko minut...');
222
      $alreadyHandled = true;
223
    }
224
225
    return true;
226
  }
227
228
  public function prefixReplace($sql) {
229
    if (strpos($sql, '{{') !== false) {
230
      foreach ($this->schema()->getSnTables() as $tableName) {
231
        $sql = str_replace("{{{$tableName}}}", $this->db_prefix . $tableName, $sql);
232
      }
233
    }
234
235
    return $sql;
236
  }
237
238
  /** @noinspection SpellCheckingInspection */
239
  public function doquery($query, $fetch = false, $skip_query_check = false) {
240
    /**
241
     * @var debug       $debug
242
     * @var classConfig $config
243
     */
244
    global $numqueries, $debug, $config;
245
246
    if (!$this->connected) {
247
      $this->sn_db_connect();
248
    }
249
250
    $query = trim($query);
251
    $this->security_watch_user_queries($query);
252
    $skip_query_check or $this->security_query_check_bad_words($query);
253
254
    $sql = $this->prefixReplace($query);
255
256
    if ($config->debug) {
257
      $numqueries++;
258
      $arr   = debug_backtrace();
259
      $array = explode('/', $arr[0]['file']);
260
      $file  = end($array);
261
      $line  = $arr[0]['line'];
262
      $debug->add("<tr><th>Query $numqueries: </th><th>$query</th><th>$file($line)</th><th>&nbsp;</th><th>$fetch</th></tr>");
263
    }
264
265
    if (defined('DEBUG_SQL_COMMENT')) {
266
      $sql = $debug->comment_query(debug_backtrace(), $sql);
267
    }
268
269
    set_error_handler([$this, 'handlerQueryWarning']);
270
    $sqlquery = $this->db_sql_query($sql);
271
    if (!$sqlquery) {
272
      $debug->error(SN::$db->db_error() . "\n$sql\n", 'SQL Error');
273
    }
274
    restore_error_handler();
275
276
    return $fetch ? $this->db_fetch($sqlquery) : $sqlquery;
0 ignored issues
show
Bug introduced by
It seems like $sqlquery can also be of type true; however, parameter $query_result of DBAL\db_mysql::db_fetch() does only seem to accept mysqli_result, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

276
    return $fetch ? $this->db_fetch(/** @scrutinizer ignore-type */ $sqlquery) : $sqlquery;
Loading history...
277
  }
278
279
  /**
280
   * Get all records for a query as array of arrays or objects
281
   *
282
   * @param string $query          SQL query to execute
283
   * @param bool   $skipQueryCheck Should query skip security check
284
   * @param bool   $asArray        Should records be returned as array? If false - records would be returned as StdObject
285
   *
286
   * @return array
287
   */
288
  public function dbGetAll($query, $skipQueryCheck = false, $asArray = true) {
289
    $queryResult = $this->doquery($query, false, $skipQueryCheck);
290
291
    $result = [];
292
    while ($row = $this->db_fetch($queryResult)) {
0 ignored issues
show
Bug introduced by
It seems like $queryResult can also be of type true; however, parameter $query_result of DBAL\db_mysql::db_fetch() does only seem to accept mysqli_result, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

292
    while ($row = $this->db_fetch(/** @scrutinizer ignore-type */ $queryResult)) {
Loading history...
293
      $result[] = $asArray ? $row : (object)$row;
294
    }
295
296
    return $result;
297
  }
298
299
  public function doQueryAndFetch($query) {
300
    return $this->doquery($query, true);
301
  }
302
303
  public function doQueryFast($query, $fetch = false) {
304
    $sql = $this->prefixReplace($query);
305
306
    set_error_handler([$this, 'handlerQueryWarning']);
307
    $sqlQuery = $this->db_sql_query($sql) or SN::$debug->error(SN::$db->db_error() . "<br />$sql<br />", 'SQL Error');
308
    restore_error_handler();
309
310
    return $fetch ? $this->db_fetch($sqlQuery) : $sqlQuery;
311
  }
312
313
  /**
314
   * @param string $query
315
   * @param bool   $skip_query_check
316
   *
317
   * @return DbMysqliResultIterator
318
   */
319
  public function selectIterator($query, $skip_query_check = false) {
320
    return new DbMysqliResultIterator($this->doquery($query, false, $skip_query_check));
321
  }
322
323
  /**
324
   * @param string $query
325
   * @param bool   $skip_query_check
326
   *
327
   * @return int|null
328
   */
329
  public function selectValue($query, $skip_query_check = false) {
330
    $row = $this->doquery($query, true, $skip_query_check);
331
332
    return !empty($row) ? intval(reset($row)) : null;
333
  }
334
335
  /**
336
   * @param DbQuery $dbQuery
337
   *
338
   * @return array|null
339
   */
340
  public function dbqSelectAndFetch(DbQuery $dbQuery) {
341
    return $this->doQueryAndFetch($dbQuery->select());
342
  }
343
344
345
  public function security_watch_user_queries($query) {
346
    // TODO Заменить это на новый логгер
347
    global $config, $is_watching, $user, $debug;
348
349
    if (!$is_watching && $config->game_watchlist_array && in_array($user['id'], $config->game_watchlist_array)) {
350
      if (!preg_match('/^(select|commit|rollback|start transaction)/i', $query)) {
351
        $is_watching = true;
352
        $msg         = "\$query = \"{$query}\"\n\r";
353
        if (!empty($_POST)) {
354
          $msg .= "\n\r" . dump($_POST, '$_POST');
355
        }
356
        if (!empty($_GET)) {
357
          $msg .= "\n\r" . dump($_GET, '$_GET');
358
        }
359
        $debug->warning($msg, "Watching user {$user['id']}", 399, array('base_dump' => true));
360
        $is_watching = false;
361
      }
362
    }
363
  }
364
365
366
  /** @noinspection SpellCheckingInspection */
367
  public function security_query_check_bad_words($query) {
368
    global $user, $dm_change_legit, $mm_change_legit;
369
370
    switch (true) {
371
      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...
372
      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...
373
      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...
374
      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...
375
      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...
376
      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...
377
      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...
378
      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...
379
      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...
380
      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...
381
        $report = "Hacking attempt (" . date("d.m.Y H:i:s") . " - [" . time() . "]):\n";
382
        $report .= ">Database Inforamation\n";
383
        $report .= "\tID - " . $user['id'] . "\n";
384
        $report .= "\tUser - " . $user['username'] . "\n";
385
        $report .= "\tAuth level - " . $user['authlevel'] . "\n";
386
        $report .= "\tAdmin Notes - " . $user['adminNotes'] . "\n";
387
        $report .= "\tCurrent Planet - " . $user['current_planet'] . "\n";
388
        $report .= "\tUser IP - " . $user['user_lastip'] . "\n";
389
        $report .= "\tUser IP at Reg - " . $user['ip_at_reg'] . "\n";
390
        $report .= "\tUser Agent- " . $_SERVER['HTTP_USER_AGENT'] . "\n";
391
        $report .= "\tCurrent Page - " . $user['current_page'] . "\n";
392
        $report .= "\tRegister Time - " . $user['register_time'] . "\n";
393
        $report .= "\n";
394
395
        $report .= ">Query Information\n";
396
        $report .= "\tQuery - " . $query . "\n";
397
        $report .= "\n";
398
399
        $report .= ">\$_SERVER Information\n";
400
        $report .= "\tIP - " . $_SERVER['REMOTE_ADDR'] . "\n";
401
        $report .= "\tHost Name - " . $_SERVER['HTTP_HOST'] . "\n";
402
        $report .= "\tUser Agent - " . $_SERVER['HTTP_USER_AGENT'] . "\n";
403
        $report .= "\tRequest Method - " . $_SERVER['REQUEST_METHOD'] . "\n";
404
        $report .= "\tCame From - " . $_SERVER['HTTP_REFERER'] . "\n";
405
        $report .= "\tPage is - " . $_SERVER['SCRIPT_NAME'] . "\n";
406
        $report .= "\tUses Port - " . $_SERVER['REMOTE_PORT'] . "\n";
407
        $report .= "\tServer Protocol - " . $_SERVER['SERVER_PROTOCOL'] . "\n";
408
409
        $report .= "\n--------------------------------------------------------------------------------------------------\n";
410
411
        $fp = fopen(SN_ROOT_PHYSICAL . 'badqrys.txt', 'a');
412
        fwrite($fp, $report);
413
        fclose($fp);
414
415
        $message = 'Привет, я не знаю то, что Вы пробовали сделать, но команда, которую Вы только послали базе данных, не выглядела очень дружественной и она была заблокированна.<br /><br />Ваш IP, и другие данные переданны администрации сервера. Удачи!.';
416
        die($message);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
417
        /** @noinspection PhpUnreachableStatementInspection */
418
      break;
419
    }
420
  }
421
422
  public function mysql_get_table_list() {
423
    return $this->db_sql_query('SHOW TABLES;');
424
  }
425
426
  public function mysql_get_innodb_status() {
427
    return $this->db_sql_query('SHOW ENGINE INNODB STATUS;');
428
  }
429
430
  /**
431
   * @param string $tableName_unsafe
432
   *
433
   * @return array[]
434
   */
435
  public function mysql_get_fields($tableName_unsafe) {
436
    $result = [];
437
438
    $prefixedTableName_safe = $this->db_escape($this->db_prefix . $tableName_unsafe);
439
    $q1                     = $this->db_sql_query("SHOW FULL COLUMNS FROM `{$prefixedTableName_safe}`;");
440
    while ($r1 = db_fetch($q1)) {
0 ignored issues
show
Deprecated Code introduced by
The function db_fetch() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

440
    while ($r1 = /** @scrutinizer ignore-deprecated */ db_fetch($q1)) {
Loading history...
441
      $dbf = new DbFieldDescription();
442
      $dbf->fromMySqlDescription($r1);
443
444
      $result[$r1['Field']] = $dbf;
445
    }
446
447
    return $result;
448
  }
449
450
  /**
451
   * @param string $tableName_unsafe
452
   *
453
   * @return DbIndexDescription[]
454
   */
455
  public function mysql_get_indexes($tableName_unsafe) {
456
    /**
457
     * @var DbIndexDescription[] $result
458
     */
459
    $result = [];
460
461
    $prefixedTableName_safe = $this->db_escape($this->db_prefix . $tableName_unsafe);
462
    $q1                     = $this->db_sql_query("SHOW INDEX FROM {$prefixedTableName_safe};");
463
    while ($r1 = db_fetch($q1)) {
0 ignored issues
show
Deprecated Code introduced by
The function db_fetch() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

463
    while ($r1 = /** @scrutinizer ignore-deprecated */ db_fetch($q1)) {
Loading history...
464
      $indexName = $r1['Key_name'];
465
      if (empty($result[$indexName])) {
466
        $result[$indexName] = new DbIndexDescription();
467
      }
468
      $result[$indexName]->addField($r1);
469
    }
470
471
    return $result;
472
  }
473
474
  /**
475
   * @param string $tableName_unsafe
476
   *
477
   * @return array[]
478
   */
479
  public function mysql_get_constraints($tableName_unsafe) {
480
    $result = [];
481
482
    $prefixedTableName_safe = $this->db_escape($this->db_prefix . $tableName_unsafe);
483
484
    $q1 = $this->db_sql_query("SELECT * FROM `information_schema`.`KEY_COLUMN_USAGE` WHERE `TABLE_SCHEMA` = '" . SN::$db->db_escape(SN::$db_name) . "' AND `TABLE_NAME` = '{$prefixedTableName_safe}' AND `REFERENCED_TABLE_NAME` IS NOT NULL;");
485
    while ($r1 = db_fetch($q1)) {
0 ignored issues
show
Deprecated Code introduced by
The function db_fetch() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

485
    while ($r1 = /** @scrutinizer ignore-deprecated */ db_fetch($q1)) {
Loading history...
486
      $indexName = $r1['CONSTRAINT_NAME'];
487
488
      $table_referenced = str_replace($this->db_prefix, '', $r1['REFERENCED_TABLE_NAME']);
489
490
      $result[$indexName]['name']                       = $indexName;
491
      $result[$indexName]['signature'][]                = "{$r1['COLUMN_NAME']}=>{$table_referenced}.{$r1['REFERENCED_COLUMN_NAME']}";
492
      $r1['REFERENCED_TABLE_NAME']                      = $table_referenced;
493
      $r1['TABLE_NAME']                                 = $tableName_unsafe;
494
      $result[$indexName]['fields'][$r1['COLUMN_NAME']] = $r1;
495
    }
496
497
    foreach ($result as &$constraint) {
498
      $constraint['signature'] = implode(',', $constraint['signature']);
499
    }
500
501
    return $result;
502
  }
503
504
505
  /**
506
   * @param $query_string
507
   *
508
   * @return bool|mysqli_result
509
   */
510
  public function db_sql_query($query_string) {
511
    $mt = microtime(true);
512
513
    $result = $this->link->query($query_string);
514
515
    $this->time_mysql_total += microtime(true) - $mt;
516
517
    return $result;
518
//    return $this->driver->mysql_query($query_string);
519
  }
520
521
  /**
522
   * @param mysqli_result $query_result
523
   *
524
   * @return array|null
525
   */
526
  public function db_fetch($query_result) {
527
    $mt = microtime(true);
528
529
    $result = mysqli_fetch_assoc($query_result);
530
531
    $this->time_mysql_total += microtime(true) - $mt;
532
533
    return $result;
534
  }
535
536
//  public function db_fetch_row(&$query) {
537
//    return mysqli_fetch_row($query);
538
//  }
539
540
  public function db_escape($unescaped_string) {
541
    return mysqli_real_escape_string($this->link, $unescaped_string);
542
  }
543
544
  public function driver_disconnect() {
545
    if (is_object($this->link)) {
546
      $this->link->close();
547
      $this->connected = false;
548
      unset($this->link);
549
    }
550
551
    return true;
552
  }
553
554
  public function db_error() {
555
    return mysqli_error($this->link);
556
  }
557
558
  /**
559
   * @return int|string
560
   */
561
  public function db_insert_id() {
562
    return mysqli_insert_id($this->link);
563
  }
564
565
  public function db_num_rows($result) {
566
    return mysqli_num_rows($result);
567
  }
568
569
  public function db_affected_rows() {
570
    return mysqli_affected_rows($this->link);
571
  }
572
573
  public function getClientInfo() {
574
    return mysqli_get_client_info($this->link);
575
  }
576
577
  public function getServerInfo() {
578
    return mysqli_get_server_info($this->link);
579
  }
580
581
  public function getHostInfo() {
582
    return mysqli_get_host_info($this->link);
583
  }
584
585
  public function getServerStat() {
586
    return mysqli_stat($this->link);
587
  }
588
589
  /**
590
   * @param array $dbSettings
591
   */
592
  public function setDbSettings($dbSettings) {
593
    $this->dbsettings = $dbSettings;
594
    $this->dbName     = $this->dbsettings['name'];
595
596
    return $this;
597
  }
598
599
  public function getDbSettings() {
600
    return $this->dbsettings;
601
  }
602
603
  /**
604
   * @param $level
605
   *
606
   * @return void
607
   */
608
  public function transactionStart($level = '') {
609
    $this->inTransaction = true;
610
611
    if ($level && in_array($level, self::TRANSACTION_LEVELS_ALLOWED)) {
612
      $this->db_sql_query("SET TRANSACTION ISOLATION LEVEL {$level};");
613
    }
614
615
    $this->doquery('START TRANSACTION; /* transaction start */', false);
616
  }
617
618
  /**
619
   * @return void
620
   */
621
  public function transactionCommit() {
622
    $this->doquery('COMMIT; /* transaction commit */');
623
    $this->inTransaction = false;
624
  }
625
626
  /**
627
   * @return void
628
   */
629
  public function transactionRollback() {
630
    $this->doquery('ROLLBACK; /* transaction rollback */');
631
    $this->inTransaction = false;
632
  }
633
634
  /**
635
   * Check if transaction started
636
   *
637
   * @return bool
638
   */
639
  public function transactionCheck() {
640
    return $this->inTransaction;
641
  }
642
643
  /**
644
   * Wrap callback in transaction
645
   *
646
   * @param callable $callback
647
   * @param string   $level
648
   *
649
   * @return mixed
650
   */
651
  public function transactionWrap($callback, $level = '') {
652
    $this->transactionStart($level);
653
    $result = $callback();
654
    $this->transactionCommit();
655
656
    return $result;
657
  }
658
659
  public static function db_transaction_start($level = '') {
660
    self::db_transaction_check(db_mysql::DB_TRANSACTION_SHOULD_NOT_BE);
661
662
    SN::$gc->db->transactionStart($level);
663
664
    self::$transaction_id++;
665
666
    if (SN::$config->db_manual_lock_enabled) {
667
      SN::$config->db_loadItem('var_db_manually_locked');
668
      SN::$config->db_saveItem('var_db_manually_locked', SN_TIME_SQL);
669
    }
670
671
    self::$db_in_transaction = true;
672
    self::$transactionDepth++;
673
    DBStaticUnit::cache_clear();
674
675
    return self::$transaction_id;
676
  }
677
678
  public static function db_transaction_rollback() {
679
    // static::db_transaction_check(true); // TODO - вообще-то тут тоже надо проверять есть ли транзакция
680
    DBStaticUnit::cache_clear();
681
682
    SN::$gc->db->transactionRollback();
683
684
    self::$db_in_transaction = false;
685
    self::$transactionDepth--;
686
687
    return self::$transaction_id;
688
  }
689
690
  /**
691
   * Блокирует указанные таблицу/список таблиц
692
   *
693
   * @param string|array $tables Таблица/список таблиц для блокировки. Названия таблиц - без префиксов
694
   *                             <p>string - название таблицы для блокировки</p>
695
   *                             <p>array - массив, где ключ - имя таблицы, а значение - условия блокировки элементов</p>
696
   */
697
  public static function db_lock_tables($tables) {
698
    $tables = is_array($tables) ? $tables : array($tables => '');
699
    foreach ($tables as $table_name => $condition) {
700
      SN::$db->doquery(
701
        "SELECT 1 FROM {{{$table_name}}}" . ($condition ? ' WHERE ' . $condition : '')
702
      );
703
    }
704
  }
705
706
  public static function db_transaction_commit() {
707
    self::db_transaction_check(db_mysql::DB_TRANSACTION_SHOULD_BE);
708
709
    DBStaticUnit::cache_clear();
710
    SN::$gc->db->transactionCommit();
711
712
    self::$db_in_transaction = false;
713
    self::$transactionDepth--;
714
715
    return self::$transaction_id++;
716
  }
717
718
  /**
719
   * Эта функция проверяет статус транзакции
720
   *
721
   * Это - низкоуровневая функция. В нормальном состоянии движка её сообщения никогда не будут видны
722
   *
723
   * @param null|true|false $status Должна ли быть запущена транзакция в момент проверки
724
   *                                <p>null - транзакция НЕ должна быть запущена</p>
725
   *                                <p>true - транзакция должна быть запущена - для совместимости с $for_update</p>
726
   *                                <p>false - всё равно - для совместимости с $for_update</p>
727
   *
728
   * @return bool Текущий статус транзакции
729
   */
730
  public static function db_transaction_check($status = self::DB_TRANSACTION_WHATEVER) {
731
    $error_msg = false;
732
    if ($status && !self::$db_in_transaction) {
733
      $error_msg = 'No transaction started for current operation';
734
    } elseif ($status === null && self::$db_in_transaction) {
735
      $error_msg = 'Transaction is already started';
736
    }
737
738
    if ($error_msg) {
739
      // TODO - Убрать позже
740
      print('<h1>СООБЩИТЕ ЭТО АДМИНУ: sn_db_transaction_check() - ' . $error_msg . '</h1>');
741
      $backtrace = debug_backtrace();
742
      array_shift($backtrace);
743
      pdump($backtrace);
744
      die($error_msg);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
745
    }
746
747
    return self::$db_in_transaction;
748
  }
749
750
  /**
751
   * Lock specified records in specified tables
752
   *
753
   * @param array[] $locks ['$tableName' => [$idToLock, ...],],
754
   *
755
   * @return array|bool|mysqli_result|null
756
   */
757
  public function lockRecords($locks) {
758
    $query = [];
759
760
    foreach ($locks as $tableName => $lockedIds) {
761
      if (!empty($lockedIds)) {
762
        // Detecting primary key name
763
        $idFieldName = !empty(static::TABLE_ID_FIELD[$tableName]) ? static::TABLE_ID_FIELD[$tableName] : 'id';
764
        // Making ID mysql-safe
765
        array_walk($lockedIds, function (&$id) { $id = idval($id); });
766
        /** @noinspection SqlResolve */
767
        $query[] = "(SELECT 1 FROM `{{{$tableName}}}` WHERE `{$idFieldName}` IN (" . implode(',', $lockedIds) . ") FOR UPDATE)";
768
      }
769
    }
770
771
    return !empty($query) ? doquery(implode(' UNION ', $query)) : false;
0 ignored issues
show
Deprecated Code introduced by
The function doquery() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

771
    return !empty($query) ? /** @scrutinizer ignore-deprecated */ doquery(implode(' UNION ', $query)) : false;
Loading history...
772
  }
773
774
}
775