Issues (1369)

classes/DBAL/db_mysql.php (22 issues)

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
  // Order in which tables should be locked
47
  const TABLE_LOCK_ORDER = [
48
    'users',
49
    'planets',
50
    'que',
51
    'unit',
52
    'fleets',
53
  ];
54
55
  /**
56
   * DB schemes
57
   *
58
   * @var Schema|null $schema
59
   */
60
  protected static $schema = null;
61
62
  /**
63
   * Статус соединения с MySQL
64
   *
65
   * @var bool
66
   */
67
  public $connected = false;
68
  /**
69
   * Префикс названий таблиц в БД
70
   *
71
   * @var string
72
   */
73
  public $db_prefix = '';
74
  public $dbName = '';
75
  /**
76
   * Настройки БД
77
   *
78
   * @var array
79
   */
80
  protected $dbsettings = [];
81
//  /**
82
//   * Драйвер для прямого обращения к MySQL
83
//   *
84
//   * @var db_mysql_v5 $driver
85
//   */
86
//  public $driver = null;
87
88
  /**
89
   * Общее время запросов
90
   *
91
   * @var float $time_mysql_total
92
   */
93
  public $time_mysql_total = 0.0;
94
95
  /**
96
   * @var bool $inTransaction
97
   */
98
  protected $inTransaction = false;
99
100
101
  /**
102
   * Соединение с MySQL
103
   *
104
   * @var mysqli $link
105
   */
106
  public $link;
107
108
109
  /**
110
   * DBAL\db_mysql constructor.
111
   *
112
   * @param GlobalContainer $gc
113
   */
114
  public function __construct($gc) {
0 ignored issues
show
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

114
  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...
115
//    $this->transaction = new \DBAL\DbTransaction($gc, $this);
116
//    $this->snCache = new $gc->snCacheClass($gc, $this);
117
//    $this->operator = new DbRowDirectOperator($this);
118
  }
119
120
  /**
121
   * @return Schema
122
   */
123
  public function schema() {
124
    if (!isset(self::$schema)) {
125
      self::$schema = new Schema($this);
126
    }
127
128
    return self::$schema;
129
  }
130
131
  public function load_db_settings() {
132
    $dbsettings = array();
133
134
    require(SN_CONFIG_PATH);
135
136
    $this->setDbSettings($dbsettings);
137
  }
138
139
  public function sn_db_connect($external_db_settings = null) {
140
    $this->db_disconnect();
141
142
    if (!empty($external_db_settings) && is_array($external_db_settings)) {
143
      $this->setDbSettings($external_db_settings);
144
    }
145
146
    if (empty($this->dbsettings)) {
147
      $this->load_db_settings();
148
    }
149
150
    // TODO - фатальные (?) ошибки на каждом шагу
151
    if (!empty($this->dbsettings)) {
152
      // $driver_name = 'DBAL\\' . (empty($this->dbsettings['sn_driver']) ? 'db_mysql_v5' : $this->dbsettings['sn_driver']);
153
      // $this->driver = new $driver_name();
154
      $this->db_prefix = $this->dbsettings['prefix'];
155
156
      $this->connected = $this->connected || $this->mysql_connect_driver($this->dbsettings);
157
158
      if ($this->connected && empty($this->schema()->getSnTables())) {
159
        die('DB error - cannot find any table. Halting...');
0 ignored issues
show
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...
160
      }
161
162
      $this->doQueryFast('SET SESSION TRANSACTION ISOLATION LEVEL ' . self::TRANSACTION_LEVEL_SERIALIZABLE);
163
    } else {
164
      $this->connected = false;
165
    }
166
167
    return $this->connected;
168
  }
169
170
  public function mysql_connect_driver($settings) {
171
    global $debug;
172
173
    static $need_keys = array('server', 'user', 'pass', 'name', 'prefix');
174
175
    if ($this->connected) {
176
      return true;
177
    }
178
179
    if (empty($settings) || !is_array($settings) || array_intersect($need_keys, array_keys($settings)) != $need_keys) {
180
      $debug->error_fatal('There is miss-configuration in your config.php. Check it again');
181
    }
182
183
    @$this->link = mysqli_connect($settings['server'], $settings['user'], $settings['pass'], $settings['name']);
0 ignored issues
show
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

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

285
    return $fetch ? $this->db_fetch(/** @scrutinizer ignore-type */ $sqlquery) : $sqlquery;
Loading history...
286
  }
287
288
  /**
289
   * Get all records for a query as array of arrays or objects
290
   *
291
   * @param string $query          SQL query to execute
292
   * @param bool   $skipQueryCheck Should query skip security check
293
   * @param bool   $asArray        Should records be returned as array? If false - records would be returned as StdObject
294
   *
295
   * @return array
296
   */
297
  public function dbGetAll($query, $skipQueryCheck = false, $asArray = true) {
298
    $queryResult = $this->doquery($query, false, $skipQueryCheck);
299
300
    $result = [];
301
    while ($row = $this->db_fetch($queryResult)) {
0 ignored issues
show
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

301
    while ($row = $this->db_fetch(/** @scrutinizer ignore-type */ $queryResult)) {
Loading history...
302
      $result[] = $asArray ? $row : (object)$row;
303
    }
304
305
    return $result;
306
  }
307
308
  public function doQueryAndFetch($query) {
309
    return $this->doquery($query, true);
310
  }
311
312
  public function doQueryFast($query, $fetch = false) {
313
    $sql = $this->prefixReplace($query);
314
315
    set_error_handler([$this, 'handlerQueryWarning']);
316
    $sqlQuery = $this->db_sql_query($sql) or SN::$debug->error(SN::$db->db_error() . "<br />$sql<br />", 'SQL Error');
0 ignored issues
show
The method error() does not exist on null. ( Ignorable by Annotation )

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

316
    $sqlQuery = $this->db_sql_query($sql) or SN::$debug->/** @scrutinizer ignore-call */ error(SN::$db->db_error() . "<br />$sql<br />", 'SQL Error');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
317
    restore_error_handler();
318
319
    return $fetch ? $this->db_fetch($sqlQuery) : $sqlQuery;
320
  }
321
322
  /**
323
   * @param string $query
324
   * @param bool   $skip_query_check
325
   *
326
   * @return DbMysqliResultIterator
327
   */
328
  public function selectIterator($query, $skip_query_check = false) {
329
    return new DbMysqliResultIterator($this->doquery($query, false, $skip_query_check));
330
  }
331
332
  /**
333
   * @param string $query
334
   * @param bool   $skip_query_check
335
   *
336
   * @return int|null
337
   */
338
  public function selectValue($query, $skip_query_check = false) {
339
    $row = $this->doquery($query, true, $skip_query_check);
340
341
    return !empty($row) ? intval(reset($row)) : null;
342
  }
343
344
  /**
345
   * @param DbQuery $dbQuery
346
   *
347
   * @return array|null
348
   */
349
  public function dbqSelectAndFetch(DbQuery $dbQuery) {
350
    return $this->doQueryAndFetch($dbQuery->select());
351
  }
352
353
354
  public function security_watch_user_queries($query) {
355
    // TODO Заменить это на новый логгер
356
    global $config, $is_watching, $user, $debug;
357
358
    if (!$is_watching && $config->game_watchlist_array && in_array($user['id'], $config->game_watchlist_array)) {
359
      if (!preg_match('/^(select|commit|rollback|start transaction)/i', $query)) {
360
        $is_watching = true;
361
        $msg         = "\$query = \"{$query}\"\n\r";
362
        if (!empty($_POST)) {
363
          $msg .= "\n\r" . dump($_POST, '$_POST');
364
        }
365
        if (!empty($_GET)) {
366
          $msg .= "\n\r" . dump($_GET, '$_GET');
367
        }
368
        $debug->warning($msg, "Watching user {$user['id']}", 399, array('base_dump' => true));
369
        $is_watching = false;
370
      }
371
    }
372
  }
373
374
375
  /** @noinspection SpellCheckingInspection */
376
  public function security_query_check_bad_words($query) {
377
    global $user, $dm_change_legit, $mm_change_legit;
378
379
    switch (true) {
380
      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...
381
      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...
382
      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...
383
      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...
384
      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...
385
      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...
386
      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...
387
      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...
388
      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...
389
      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...
390
        $report = "Hacking attempt (" . date("d.m.Y H:i:s") . " - [" . time() . "]):\n";
391
        $report .= ">Database Inforamation\n";
392
        $report .= "\tID - " . $user['id'] . "\n";
393
        $report .= "\tUser - " . $user['username'] . "\n";
394
        $report .= "\tAuth level - " . $user['authlevel'] . "\n";
395
        $report .= "\tAdmin Notes - " . $user['adminNotes'] . "\n";
396
        $report .= "\tCurrent Planet - " . $user['current_planet'] . "\n";
397
        $report .= "\tUser IP - " . $user['user_lastip'] . "\n";
398
        $report .= "\tUser IP at Reg - " . $user['ip_at_reg'] . "\n";
399
        $report .= "\tUser Agent- " . $_SERVER['HTTP_USER_AGENT'] . "\n";
400
        $report .= "\tCurrent Page - " . $user['current_page'] . "\n";
401
        $report .= "\tRegister Time - " . $user['register_time'] . "\n";
402
        $report .= "\n";
403
404
        $report .= ">Query Information\n";
405
        $report .= "\tQuery - " . $query . "\n";
406
        $report .= "\n";
407
408
        $report .= ">\$_SERVER Information\n";
409
        $report .= "\tIP - " . $_SERVER['REMOTE_ADDR'] . "\n";
410
        $report .= "\tHost Name - " . $_SERVER['HTTP_HOST'] . "\n";
411
        $report .= "\tUser Agent - " . $_SERVER['HTTP_USER_AGENT'] . "\n";
412
        $report .= "\tRequest Method - " . $_SERVER['REQUEST_METHOD'] . "\n";
413
        $report .= "\tCame From - " . $_SERVER['HTTP_REFERER'] . "\n";
414
        $report .= "\tPage is - " . $_SERVER['SCRIPT_NAME'] . "\n";
415
        $report .= "\tUses Port - " . $_SERVER['REMOTE_PORT'] . "\n";
416
        $report .= "\tServer Protocol - " . $_SERVER['SERVER_PROTOCOL'] . "\n";
417
418
        $report .= "\n--------------------------------------------------------------------------------------------------\n";
419
420
        $fp = fopen(SN_ROOT_PHYSICAL . 'badqrys.txt', 'a');
421
        fwrite($fp, $report);
422
        fclose($fp);
423
424
        $message = 'Привет, я не знаю то, что Вы пробовали сделать, но команда, которую Вы только послали базе данных, не выглядела очень дружественной и она была заблокированна.<br /><br />Ваш IP, и другие данные переданны администрации сервера. Удачи!.';
425
        die($message);
0 ignored issues
show
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...
426
        /** @noinspection PhpUnreachableStatementInspection */
427
      break;
428
    }
429
  }
430
431
  public function mysql_get_table_list() {
432
    return $this->db_sql_query('SHOW TABLES;');
433
  }
434
435
  public function mysql_get_innodb_status() {
436
    return $this->db_sql_query('SHOW ENGINE INNODB STATUS;');
437
  }
438
439
  /**
440
   * @param string $tableName_unsafe
441
   *
442
   * @return array[]
443
   */
444
  public function mysql_get_fields($tableName_unsafe) {
445
    $result = [];
446
447
    $prefixedTableName_safe = $this->db_escape($this->db_prefix . $tableName_unsafe);
448
    $q1                     = $this->db_sql_query("SHOW FULL COLUMNS FROM `{$prefixedTableName_safe}`;");
449
    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

449
    while ($r1 = /** @scrutinizer ignore-deprecated */ db_fetch($q1)) {
Loading history...
450
      $dbf = new DbFieldDescription();
451
      $dbf->fromMySqlDescription($r1);
452
453
      $result[$r1['Field']] = $dbf;
454
    }
455
456
    return $result;
457
  }
458
459
  /**
460
   * @param string $tableName_unsafe
461
   *
462
   * @return DbIndexDescription[]
463
   */
464
  public function mysql_get_indexes($tableName_unsafe) {
465
    /**
466
     * @var DbIndexDescription[] $result
467
     */
468
    $result = [];
469
470
    $prefixedTableName_safe = $this->db_escape($this->db_prefix . $tableName_unsafe);
471
    $q1                     = $this->db_sql_query("SHOW INDEX FROM {$prefixedTableName_safe};");
472
    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

472
    while ($r1 = /** @scrutinizer ignore-deprecated */ db_fetch($q1)) {
Loading history...
473
      $indexName = $r1['Key_name'];
474
      if (empty($result[$indexName])) {
475
        $result[$indexName] = new DbIndexDescription();
476
      }
477
      $result[$indexName]->addField($r1);
478
    }
479
480
    return $result;
481
  }
482
483
  /**
484
   * @param string $tableName_unsafe
485
   *
486
   * @return array[]
487
   */
488
  public function mysql_get_constraints($tableName_unsafe) {
489
    $result = [];
490
491
    $prefixedTableName_safe = $this->db_escape($this->db_prefix . $tableName_unsafe);
492
493
    $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;");
494
    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

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

793
      /** @scrutinizer ignore-deprecated */ doquery($query);
Loading history...
794
    }
795
796
    return true;
797
  }
798
799
}
800